不同操作系统中的Socket

- Windows Socket (Winsock)
  - 简称Winsock，是在Windows环境下使用的一套网络编程规范，基于4.3BSD的BSD Socket API制定1991年Winsock 1.1，16位，由WINSOCK.DLL支持，主要用在Windows 95中1997年Winsock 2.2 版，32位，由WSOCK32.DLL支持，主要用在Windows 98及以后的版本中已经成为Windows环境下网络编程的事实标准三类函数与BSD Socket相兼容的基本函数与BSD Socket相兼容的网络信息检索函数Windows专用扩展函数
  - Winsock是一个基于Socket模型的API，在Windows系统中广泛使用它在Berkeley接口函数的基础上，还增加了基于消息驱动机制的Windows扩展函数Winsock1.1只支持TCP/IP网络，Winsock2.2增加了对更多协议的支持
  - 需要包含头文件Winsock2.h，需要使用库ws2_32.lib，包含办法可以用语句来告诉编译时调用该库　　#pragma comment(lib,”ws2_32.lib”);
- Linux Socket (BSD Socket)
  - 基本上就是BSD Socket需要使用的头文件数据类型：#include <sys/types.h>函数定义：#include <sys/socket.h>

## Socket常用函数

网络连接函数

- socket　创建套接字
- bind　绑定本机端口
- connect　建立连接
- listen　监听端口
- accept　接受连接
- recv, recvfrom　数据接收
- send, sendto　数据发送
- close, shutdown　关闭套接字

### 网络字节序和主机字节序

例子：在内存中双字0x01020304(DWORD)的存储方式内存地址       

|4000 |4001| 4002| 4003|
| --- |---|---|---|
| LE  |  04 |    03 |    02  |   01|
|BE    | 01    | 02    | 03     |04|

网络字节顺序是TCP/IP中规定的一种数据表示格式，它与具体的CPU类型、操作系统等无关。主机字节顺序与具体的CPU类型、操作系统等有关！

转换函数

- IP地址转换函数
  - inet_addr()　点分十进制数表示的IP地址转换为网络字节序的IP地址
  - inet_ntoa()　网络字节序的IP地址转换为点分十进制数表示的IP地址
- 字节排序函数
  - htonl　4字节主机字节序转换为网络字节序
  - ntohl　 4字节网络字节序转换为主机字节序
  - htons　2字节主机字节序转换为网络字节序
  - ntohs　2字节网络字节序转换为主机字节序

网络信息检索函数

- gethostname　获得主机名
- getpeername　获得与套接字相连的远程协议地址
- getsockname　获得套接字本地协议地址
- **gethostbyname　根据主机名取得主机信息**
- gethostbyaddr　根据主机地址取得主机信息
- getprotobyname　根据协议名取得主机协议信息
- getprotobynumber　根据协议号取得主机协议信息
- getservbyname　根据服务名取得相关服务信息
- getservbyport　根据端口号取得相关服务信息
- getsockopt/setsockopt　获取/设置一个套接字选项
-  ioctlsocket　设置套接字的工作方式



### Socket类型

```
流式套接字(SOCK_STREAM)
提供了一个面向连接、可靠的数据传输服务，数据无差错、无重复的发送且按发送顺序接收。 
数据报套接字(SOCK_DGRAM)
提供无连接服务。数据包以独立数据包的形式被发送，不提供无差错保证，数据可能丢失，重复或失序。
原始套接字(SOCK_RAW)
可以对较低层次协议，如IP、ICMP直接访问。
```

![image-20251113112406263](https://chenalna.oss-cn-hangzhou.aliyuncs.com/img/image-20251113112406263.png)

### Windows Socket

#### Windows Socket的启动

```c
使用Winsock 
API编制的网络应用程序中，在调用任何一个Winsock函数之前都必须检查协议栈安装情况，使用函数WSAStartup()完成操作。		
    int WSAStartup(				
    WORD wVersionRequested,				
    LPWSADATA lpWSAData			
);
wVersionRequested是一个WORD型(双字节型)数值，指定使用的版本号，对Winsock2.2而言，此参数的值为0x0202，也可以用宏MAKEWORD(2,2)来获得lpWSAData是一个指向WSADATA结构的指针，它返回关于Winsock实现的详细信息。


#include <Winsock2.h>
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested=MAKEWORD(2,2);
if(WSAStartup(wVersionRequested,&wsaData)!=0)
{
		//Winsock初始化错误
		return;
}
if(wsaData.wVersion!=wVersionRequested)
{
		//Winsock版本不匹配
		WSACleanup();
		return;
}//说明WinsockDLL正确加载，可以执行后续代码

```

#### 创建套接字 Socket

应用程序在使用套接字通信前，必须要拥有一个套接字，使用socket()函数来给应用程序创建一个套接字。

```C
			SOCKET socket(
				int af,
				int type,
				int protocol
			);
```







```
af参数说明套接字接口要使用的协议地址族，地址族与协议族含义相同。如果想建立一个TCP或UDP，只能用常量AF_INET表示使用互联网协议(IP)地址。Winsock还支持其他协议，但一般很少使用。
type参数描述套接字的类型，af是AF_INET的时候只能为SOCK_STREAM、SOCK_DGRAM或SOCK_RAW
protocol说明该套接字使用的特定协议，当协议地址族af和协议类型type确定后，协议字段可以使用的值是限定的
```

| 协议           | 地址族     | 套接字类型              | 套接字类型使用的值 | 协议字段    |
| -------------- | ---------- | ----------------------- | ------------------ | ----------- |
| 互联网协议(IP) | AF_INET    | TCP                     | SOCK_STREAM        | IPPROTO_TCP |
| UDP            | SOCK_DGRAM | IPPROTO_UDP             |                    |             |
| Raw            | SOCK_RAW   | IPPROTO_RAWIPPROTO_ICMP |                    |             |

####指定本地地址——bind()

```
当socket()创建了一个套接字后，需要将该套接字与该主机上提供服务的某端口联系在一起，bind()函数用于完成这样的绑定。
			int bind(
				SOCKET s,
				const struct sockaddr FAR * name,
				int namelen
			);

s       标识一个未绑定的套接字描述字，
            它是socket()函数调用成功时返回的值

name是一个与指定协议有关的地址结构指针，
            存储了套接字的地址信息，
            Winsock中使用sockaddr_in结构指定IP地址和端口信息

	struct sockaddr_in{
        short 	sin_family; //一般为AF_INET，表示使用IP地址族；
        u_short 	sin_port;    //是以网络字节序表示的16位端口号；
        struct in_addr	sin_addr;    //是网络字节序的32位IP地址；
        char		sin_zero[8];//一般不用，用0填充。
	   }
namelen表示地址参数(name)的长度

IP地址参数为INADDR_ANY，则由系统内核来自动指定，port为0，则由系统指派一个1024~65535之间唯一的端口号

#include <Winsock2.h>
SOCKET s;
sockaddr_in tcpaddr;
int iSockErr;
int port=5000; //端口号
s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
tcpaddr.sin_family=AF_INET;
tcpaddr.sin_port=htons(port);
tcpaddr.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(s,(LPSOCKADDR)&tcpaddr,sizeof(tcpaddr))==SOCKET_ERROR){
	iSockErr=WSAGetLastError();
	//根据不同的错误类型进行不同的处理
	return;
}
函数调用成功，进行其他处理。


```

#### 服务器启动监听——listen()函数

```
在一个服务器端用socket()调用成功创建了一个套接字，并用bind()函数和一个指定的地址关联后，就需要指示该套接字进入监听连接请求状态，这需要通过listen()函数来实现
		int listen(
			SOCKET s,
			int backlog
		);
	s代表一个已绑定了地址，但还未建立连接的套接字描述字
	backlog指定了正在等待连接的最大队列长度
```

