套接字选项


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
);

参数①

需要设置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
); 

参数①

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
);

参数①

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地址是否有变化。可在回调函数中写入具体处理变化的代码。

参数③

操作码输入缓存的指针

参数④

参数③的大小

参数⑤

操作码输出缓存的指针

参数⑥

参数⑤的大小

参数⑦

实际返回值大小

参数⑧

异步模式下的重叠结构体

参数⑨

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

void CALLBACK CompletionRoutine(
  IN DWORD dwError, 
  IN DWORD cbTransferred, 
  IN LPWSAOVERLAPPED lpOverlapped, 
  IN DWORD dwFlags 
);

返回值

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

IO_ADDRESS LIST_QUERY使用实例:

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<winsock2.h>
#include<stdio.h>

#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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

oldmao_2000

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值