• 套接字选项



    WinSock提供了一些获取和设置套接口选项的函数。获取选项可以得到套接口相关的一些参数、操作方式等;设置选项能够改变套接口的参数、控制底层协议的行为等。本章主要介绍:getsockopt/setsockopt、WSAIoctl和ioctlsocket,并对这些函数常用的设置选项进行介绍。

    setsockopt

    getsockopt/setsockopt分别用于获取和设置套接字的选项,这里主要以setsockopt为例进行讲解。

    https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-setsockopt

    int setsockopt(
      [in] SOCKET     s,
      [in] int        level,
      [in] int        optname,
      [in] const char *optval,
      [in] int        optlen
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    参数①

    需要设置Socket句柄

    参数②

    由于选项是层级结构,因此这里给出的命名是level,即选项所属的level(类别)
    https://docs.microsoft.com/en-us/windows/win32/winsock/socket-options
    常见的level有:
    (画表)
    IPPROTO_IP:IPv4协议选项
    IPPROTO_IPV6:IPv6协议选项
    IPPROTO_RM:可靠/稳定多播选项
    IPPROTO_TCP:TCP选项
    IPPROTO_UDP:UDP选项
    NSPROTO_IPX:IPX选项
    SOL_APPLETALK:AppleTalk选项
    SOL_IRLMP:红外连接管理协议选项
    SOL_SOCKET:套接字选项

    设置选项使用的函数是:setsockopt;获取选项使用的函数是:getsockopt
    有些选项是不可设置的,相当于只读的选项。
    当然也有些选项是既可以设置又可以读取的。

    参数③

    要设置的选项名

    参数④

    要设置的选项值,使用指针的方式定义
    有两类选项值:布尔值和非布尔值。对于布尔值的选项,可以设置一个非零的整型用于标记启用或允许该选项,设置0用于标记关闭或禁止该选项。对于非布尔值的选项,需要将选项值对应的值或者结构体指针设置到参数④。

    参数⑤

    参数④的大小

    返回值

    成功,返回0
    失败,返回SOCKET_ERROR

    注意:
    1.setsockopt一般应在bind的操作后调用,否则setsockopt即使设置也不会检查TCP/IP选项,直到调用bind后,才会检查TCP/IP选项,这种情况下,setsockopt只会返回执行成功。
    2.打开的句柄调用setsockopt后,再调用sendto,则相当于调用bind。
    3.选项的获取或设置需要考虑调用顺序,例如未报错时调用获取SO_ERROR则不会获得错误信息。

    IPPROTO_IP

    https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options
    只写:
    IP_ADD_MEMBERSHIP:加入多播组
    IP_DROP_MEMBERSHIP:离开多播组

    可读可写
    IP_HDRINCL:用于表示是否使用自定义IP头,只支持原始套接字
    IP_OPTIONS:设置IP包头中optional字段中的数据。
    IP_TOS:设置IP包头中TOS字段中的数据。
    IP_TTL:设置IP包头中TTL字段中的数据。
    IP_DONTFRAGMENT:设置是否忽略MTU的限制,不分片,只支持UDP、ICMP。启用该选项后,发送数据大于本地接口的MTU会失败,错误码为:WSAEMSGSIZE。

    IPPROTO_TCP

    TCP_NODELAY:设置是否使用Nagle算法,即是否缓冲区只要有数据,不等待,立即发送。在实施通信应用用应设置为立即发送减少延迟。

    SOL_SOCKET

    https://docs.microsoft.com/en-us/windows/win32/winsock/sol-socket-socket-options
    只读:
    SO_ACCEPTCONN:用于查看套接字是否处于listen状态。
    SO_CONNECT_TIME:用于查询客户端请求连接所经过的时间,以秒为单位。只支持TCP,通常用来判断等待时间过长的连接,并关闭之。
    SO_PROTOCOL_INFO:用于查询底层协议信息,作用与WSAEnumProtocols类似。
    SO_ERROR:用于查询最后一次Socket上发生的错误码,并清除错误。
    SO_TYPE:用于查询Socket的类型,例如:SOCK_STREAM、SOCK_DGRAM等。
    SO_MAX_MSG_SIZE:得到一次可以发送的最大数据报的大小,只能用于数据报套接口。

    可读可写
    SO_BROADCAST:用于查看或者设置Socket是否可以发送广播数据,只能用于IPX、UDP,不可用于TCP。在不支持广播的网络(例如:点对点链路)上设置该选项无效。
    SO_CONDITIONAL_ACCEPT:用于设置服务器Socket是否自动Accept客户端的连接请求,默认是False,服务器会自动Accept客户端的连接请求(完成三次握手),设置为True后,需要应用程序调用WSAAccept(https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaaccept)回调函数完成Accept客户端的连接请求。
    SO_DONTLINGER:设置该选项可使得在关闭套接字(closesocket)后,套接字仍然保持打开状态,并将未发送完的数据继续发送完毕后关闭,只支持TCP。
    SO_LINGER:接上一个选项,设置套接字保持打开的时间长度,单位为秒。
    SO_REUSEADDR:用于将多个Socket绑定到相同IP地址和端口号
    SO_EXCLUSIVEADDRUSE:用于设置防止多个Socket绑定到相同IP地址和端口号,该选项必须在bind前设置才有效。SO_EXCLUSIVEADDRUSE设置后SO_REUSEADDR无效。
    SO_KEEPALIVE:设置后客户端会每隔1秒钟向服务器发送心跳数据,保持正常连接,最长可维持发送2小时的心跳数据,只支持TCP。
    SO_OOBINLINE:设置该选项使得带外数据(OOB)作为带内数据一样处理,只支持打开OOB的TCP。
    SO_RCVBUF:用于设置套接字接收数据缓冲区的大小,这个数据缓冲区是Socket自带用于存放接收数据的,不是Recv函数中指定的缓冲区。(加解释)
    SO_SNDBUF:同上,但设置的是发送数据缓冲区的大小。
    SO_RCVTIMEO:recv阻塞调用时等待时间,单位是毫秒,默认值为0,代表一直等待。
    SO_SNDTIMEO:同上,但设置的是send阻塞调用时等待时间。

    ioctlsocket

    ioctlsocket命令和下面要讲的WSAIoctl类似,前者主要是基于Winsock 1.0版本,兼容性较好;而后者基于Winsock 2.0版本,功能更加强大,支持的选项更多。
    https://docs.microsoft.com/zh-cn/windows/win32/api/winsock/nf-winsock-ioctlsocket

    int ioctlsocket(
      [in]      SOCKET s,
      [in]      long   cmd,
      [in, out] u_long *argp
    ); 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    参数①

    Socket句柄

    参数②

    操作码/命令
    1.FIONBIO:这个命令将socket置于非阻塞状态。在重叠IO等通信模式下,只要执行涉及非阻塞命令的函数,对应Socket就会自动进入非阻塞状态,不需要额外调用FIONBIO进行设置。
    2.FIONREAD:返回指定Socket上可以读取的字节数。也可直接调用recv直接收取对应数据。
    3.SIOCATMARK:返回Socket是否有OOB数据待读取,该命令只适用于设置了SO_OOBINLINE的SOCK_STREAM类型Socket。

    参数③

    操作码要设置的值

    返回值

    成功,返回0;
    失败,返回SOCKET_ERROR。

    WSAIoctl

    https://docs.microsoft.com/en-us/windows/win32/api/Winsock2/nf-winsock2-wsaioctl

    int WSAAPI WSAIoctl(
      [in]  SOCKET                             s,
      [in]  DWORD                              dwIoControlCode,
      [in]  LPVOID                             lpvInBuffer,
      [in]  DWORD                              cbInBuffer,
      [out] LPVOID                             lpvOutBuffer,
      [in]  DWORD                              cbOutBuffer,
      [out] LPDWORD                            lpcbBytesReturned,
      [in]  LPWSAOVERLAPPED                    lpOverlapped,
      [in]  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    参数①

    Socket句柄

    参数②

    操作码/命令
    https://docs.microsoft.com/en-us/windows/win32/winsock/winsock-ioctls

    1.SIO_ENABLE_CIRCULAR_QUEUEING:用于设置Socket缓冲区满后,是否用新数据替换缓冲队列的数据,仅支持UDP。
    2.SIO_FLUSH,清空Socket发送缓冲区。
    3.SIO_GET_EXTENSION_FUNCTION_POINTER,获取扩展函数指针地址,例如AcceptEx。
    4.SIO_KEEPALIVE_VALS,和setsockopt函数的SO_KEEPALIVE选项有关系,这个命令可以设置一个TCP连接多长时间不活动就断开的时长,以及心跳数据的间隔。
    5.SIO_RCVALL:设置Socket忽略端口号,接收发送到当前接口上的所有 IPv4 或 IPv6 数据包。该操作码需要管理员权限,套接字类型为SOCK_RAW
    6.SIO_ROUTING_INTERFACE_QUERY:根据目标地址返回到达该目标地址的接口对应的IP地址。
    7.SIO_ROUTING._INTERFACE_CHANGE:查询当前Socket绑定的网络接口对应IP地址是否发生变化。可在回调函数中写入具体处理变化的代码。
    8.SIO_ADDRESS LIST_QUERY,可以查询当前计算机某个协议的所有接口。(看例子)
    9.SIO_ADDRESS_LIST_CHANGE,可以查询本地某个协议IP地址是否有变化。可在回调函数中写入具体处理变化的代码。
    10.

    参数③

    操作码输入缓存的指针

    参数④

    参数③的大小

    参数⑤

    操作码输出缓存的指针

    参数⑥

    参数⑤的大小

    参数⑦

    实际返回值大小

    参数⑧

    异步模式下的重叠结构体

    参数⑨

    要执行的回调函数,定义方式如下:

    void CALLBACK CompletionRoutine(
      IN DWORD dwError, 
      IN DWORD cbTransferred, 
      IN LPWSAOVERLAPPED lpOverlapped, 
      IN DWORD dwFlags 
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    返回值

    成功,返回0;
    失败,返回SOCKET_ERROR。

    IO_ADDRESS LIST_QUERY使用实例:

    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include
    #include
    
    #pragma comment(lib, "ws2_32.lib")
    
    
    int main()
    {
    	WORD wVersionRequested = MAKEWORD(2, 2);//版本
    	WSADATA wsaDATA;
    
    	//打开网络库
    	if (WSAStartup(wVersionRequested, &wsaDATA) != 0)
    	{
    		printf("打开网络库失败!\n");
    		return -1;
    	}
    
    	SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//创建Socket句柄
    
    	struct sockaddr_in si;
    	si.sin_family = AF_INET;
    	si.sin_port = htons(9527);//用htons宏将整型转为端口号的无符号整型
    
    	si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    
    	if (SOCKET_ERROR == bind(sock, (const struct sockaddr*)&si, sizeof(si)))
    	{
    		int err = WSAGetLastError();//取错误码
    		printf("服务器bind失败错误码为:%d\n", err);
    		closesocket(sock);//释放
    		WSACleanup();//清理网络库
    
    		return 0;
    	}
    	printf("服务器端bind成功!\n");
    	
    	char Buf[0x1000] = { 0 };
    	DWORD dwBytes;
    	WSAOVERLAPPED ov = { 0 };
    	ov.hEvent = WSACreateEvent();
    	int ret = WSAIoctl(sock, SIO_ADDRESS_LIST_QUERY, NULL, 0, Buf, 0x1000, &dwBytes, NULL, NULL);
    	
    	if (0 != ret)
    	{
    		int err = WSAGetLastError();//取错误码
    		printf("WSAIoctl失败错误码为:%d\n", err);
    		closesocket(sock);//释放
    		WSACleanup();//清理网络库
    
    		return 0;
    	}
    
    	SOCKET_ADDRESS_LIST* slist = NULL;
    	SOCKADDR_IN* pAddrInet;
    	char* pAddrString;
    	slist = (SOCKET_ADDRESS_LIST*)Buf;
    
    	int num = slist->iAddressCount;
    
    	if (num > 0)
    	{
    		for (int i = 0; i < num; i++)
    		{
    			pAddrInet = ((SOCKADDR_IN*)slist->Address[i].lpSockaddr);
    			pAddrString = inet_ntoa(pAddrInet->sin_addr);
    			printf("IP%d:%s\r\n", i+1,pAddrString);
    		}
    	}
    	else
    	{
    		printf("没有读取到IP信息!/r/n/n");
    	}
    	closesocket(sock);//关闭Socket句柄
    	WSACleanup();//关闭网络库
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
  • 相关阅读:
    Linux常用基本命令大全
    nfs挂载错误wrong fs type, bad option, bad superblock
    软考初级网络管理员__网络单选题
    力扣(145.)补9.5
    昆仑芯 AI 加速卡 R200 与龙蜥操作系统完成产品兼容认证
    福州大学《嵌入式系统综合设计》实验四:边缘检测
    用了国产接口管理神器 Apifox 之后,我果断从 Postman “脱坑”了
    【单片机】13-实时时钟DS1302
    Java手写背包问题算法
    数据库分区是什么?
  • 原文地址:https://blog.csdn.net/oldmao_2001/article/details/125993736