UNIX网络编程——套接字选项
getsockopt 和 setsockopt 函数
这两个函数仅用于套接字。
#include <sys/socket.h>
int getsockopt (int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt (int sockfd, int level, int optname, const void *optval, socklen_t optlen);
均返回:若成功则为0,若出错则为-1
sockfd 必须指向一个打开的套接字描述符。
level (级别)指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码。
optval 是一个指向某个变量 (*optval)的指针,
setsockopt 从 *optval 中取得选项待设置的新值,
getsockopt 则把已获取的选项当前值存放到 *optval 中。
*optval 的大小由最后一个参数指定,它对于 setsockopt 是一个值参数,对于 getsockopt 是一个值-结果参数。
选项名 | 说明 | 数据类型 |
---|---|---|
SO_ERROR | 获取待处理错误并清除 | int |
SO_KEEPALIVE | 周期性测试连接是否存活 | int[bool] |
SO_RCVBUF | 接收缓冲区大小 | int |
SO_SNDBUF | 发送缓冲区大小 | int |
SO_RCVLOWAT | 接收缓冲区低水位标记 | int |
SO_SNDLOWAT | 发送缓冲区低水位标记 | int |
SO_RCVTIMEO | 接收超时 | timeval{} |
SO_SNDTIMEO | 发送超时 | timeval{} |
SO_REUSEADDR | 允许重用本地地址 | int[bool] |
SO_REUSEPORT | 允许重用本地端口 | int[bool] |
SO_TYPE | 取得套接字类型 | int |
套接字状态
对于某些套接字选项,针对套接字的状态,什么时候设置或获取选项有时序上的考虑。
下面的套接字选项是由TCP已连接套接字从监听套接字继承来的:
SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG 和 TCP_NODELAY 。
如果想在三路握手完成时确保这些套接字选项中的某一个是给已连接套接字设置的,那么必须先给监听套接字设置该选项。
通用套接字选项
SO_ERROR 套接字选项
套接字上产生某个错误时,源自 Berkeley 的内涵中的协议模块将该套接字的名为 so_error 的变量设为标准的 Unix Exxx 值中的一个,称它为该套接字的待处理错误。
内核能够以下面两种方式之一立即通知进程这个错误。
- (1)若进程阻塞于含有此套接字的select上,立即返回。(读返回/写返回)
- (2)若进程用信号驱动I/O模型,就给进程或进程组产生一个 SIGIO 信号。
进程然后可以通过访问 SO_ERROR 套接字选项获取 so_error 的值。由 getsockopt 返回的整数值就是该套接字的待处理错误。so_error 随后由内核复位为0 。
如阻塞于此套接字的read,read立即返回-1,errno被设为套接字错误码。
如正对此套接字执行write,write立即返回-1,errno被设为套接字错误码。
SO_KEEPALIVE 套接字选项
给一个套接字设置保持存活选项后,如果2小时内在该套接字的任一方向上都没有数据交换,TCP就自动给对端发一个保持存活探测分节。
这是一个对端必须响应的TCP分节,它会导致以下三种情况之一。
- 1.对端以期望的ACK响应。
- 2.对端以RST响应。
该套接字的待处理错误被置为ECONNRESET,套接字本身关闭。 - 3.如无响应,且重传多次仍然无响应。
该套接字的待处理错误被置为ETIMEOUT,套接字本身关闭。
如果套接字收到一个ICMP错误作为响应,则返回相应错误,
套接字本身被关闭。
一般由服务器使用。
用于针对已经崩溃或断网的客户端,释放本地与其通信的相关设施。
SO_LINGER 套接字选项
指定close函数对面向连接的协议如何操作。
SO_LINGER 套接字选项使得我们可以改变这个默认设置。
本选项要求在用户进程与内核键传递如下结构,它在头文件 <sys/socket.h> 中定义:
struct linger
{
int l_onoff;
int l_linger;
};
对setsockopt的调用将根据其中两个结构成员的值形成下列3中情形之一:
- 1.如l_onoff为0,则关闭本选项。按默认设置处理close。
- 2.如l_onoff为非0且l_linger为0,则close某个连接时TCP将中止该连接(丢弃保留在套接字发送缓冲区的数据,发一个RST给对端。无正常的连接终止序列)
- 3.如l_onoff为非0且l_linger也为非0,则执行close,进程将休眠,直到以下任一条件被满足。
a.该套接字的所有数据都已发送完且均被对方确认。
b.延滞时间到.此时close返回值为EWOULDBLOCK。
对非阻塞套接字,上述说明无效,立即返回。
客户可用设置 SO_LINGER 套接字选项,指定一个正的延滞时间。这时,客户的 close 要到它的数据和FIN已被服务器主机的TCP确认后才返回。
问题:在服务器应用进程读剩余数据之前,服务器主机可能崩溃,
并且客户应用进程永远不会指定。
下图展示了当给 SO_LINGER选项设置偏低的延滞时间值时可能发生的现象。
这里有一个基本原则:设置 SO_LINGER套接字选项后,close 的成功返回只是告诉我们先前发送的数据(和FIN)已由对端TCP确认,而不能告诉我们对端应用进程是否已读取数据。如果不设置该套接字选项,那么我们连对端TCP是否确认了数据都不知道。
让客户知道服务器已读取其数据的一个方法是改为调用 shutdown (并设置它的第二个参数为 SHUT_WR)而不是调用 close,并等待对端 close连接的当地端(服务器端)。
当关闭连接的本地端(客户端)时,根据所调用的函数(close或shutdown)以及是否设置了 SO_LINGER 套接字选项,可在以下3个不同的时机返回。
- (1)close 立即返回,根本不等待。
- (2)close 一直拖延到接收了对于客户端FIN的ACK才返回。
- (3)后跟一个read调用的shutdown一直等到接收了对端的FIN才返回。
获知对端应用进程已读取我们的数据的另外一个方法是使用应用级确认,简称应用ACK。
下图显示了可能的分组交换过程。
shutdown 和 SO_LINGER 各种情况的总结。
函数 | 说明 |
---|---|
shutdown, SHUT_RD | 丢弃接收缓冲区现有内容,此后在套接字上不能再发出接收请求.可以继续往套接字发送数据. |
shutdown, SHUT_WR | 套接字发送缓冲区中内容由内核后续发到对端,再发FIN.但shutdown后,不可再向其套接字写入待发送数据.可以继续接收发给此套接字的数据. |
close , l_onoff=0 | 此后无法通过此套接字执行数据发送和接收.如close后底层对象引用计数变为0,释放底层对象.且与其关联的发送缓冲区数据有内核后续发到对端,再发FIN.与其关联的接收缓冲区数据立即丢弃. |
close , l_onoff = 1 l_linger=0 | 此后无法通过此套接字执行数据发送和接收如close后底层对象引用计数变为0,释放底层对象.且与其关联的发送缓冲区,接收缓冲区数据均被丢弃.主动发RST给对端.立即将此连接状态设为CLOSED |
close , l_onoff = 1 l_linger != 0 | 此后无法通过此套接字执行数据发送和接收.如close后底层对象引用计数变为0,释放底层对象.且与其关联的发送缓冲区数据由内核后续发给对端,再发FIN与其关联的接收缓冲区数据被丢弃.如在变为CLOSED状态前,指定的时间到,close返回EWOULDBLOCK. |
SO_RCVBUF和SO_SNDBUF 套接字选项
每个套接字都有一个发送缓冲区和一个接收缓冲区。
对TCP,套接字接收缓冲区中可用空间大小限定了TCP通告对端的窗口大小。
不允许对端发超过本端所通告窗口大小的数据,可保证接收缓冲区不会溢出。(流量控制)
对UDP,当接收到的数据报装不进套接字接收缓冲区时,直接丢弃。
这两个套接字选项允许改变两个缓冲区的默认大小。
TCP的窗口规模选项是在建立连接时用SYN分节与对端互换得到的。
对客户,SO_RCVBUF选项需在调connect之前设置。
对服务器,该选项需在调listen前给监听套接字设置。
TCP套接字缓冲区的大小至少应该是相应连接的MSS值的4倍。
对全双工管道,管道的容量称为带宽-延迟积,通常将带宽(bit/s)和RTT(秒)相乘,再将结果由位转换为字节。
SO_RCVLOWAT 和 SO_SNDLOWAT 套接字选项
每个套接字还有一个接收低水位标记(默认1)和一个发送低水位标记(默认2048)。
接收低水位标记定义让select返回’可读’时,套接字接收缓冲区所需的最小数据量。
发送低水位标记是定义让select返回’可写’时,套接字发送缓冲区可用空间最小大小。
UDP也使用发送低水位标记,由于UDP套接字的发送缓冲区中可用空间的字节数从不改变(UDP不为应用传来的数据报保留副本),只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位标记,该UDP套接字就总是可写。
SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项
允许给套接字的接收和发送设置一个超时值。
关联的参数是struct timeval 。
通过设置其值为0来禁止超时。(默认情形)
SO_REUSEADDR 和 SO_REUSEPORT 套接字选项
对SO_REUSEADDR 套接字选项能起到以下4个不同的功用。
- 1.允许一个监听套接字绑定众所周知端口A,即使有一个已经连接套接字本地端端口地址为A。
所有TCP服务器均应指定此选项。
若为指定,对监听套接字执行bind到A会失败。 - 2.存在多个IP地址作为本机地址的主机上。
我们可以分别以每个本地IP+已经端口A,通配IP+已知端口A
针对本机某个TCP服务提供多个监听套接字用于提供连接服务。
若未设置SO_REUSEADDR选项,对非首个绑定在端口A的监听套接字执行bind时,将出错。 - 3.允许单个进行针对一个端口A,分别以本机不同IP产生监听套接字。
UDP服务器可用此方法确定接收到数据报的具体目的IP地址。 - 4.一般对于UDP。
其余SO_REUSEADDR下,即使建立多个绑定套同一本机IP+已知端口A的套接字也是允许的。
对SO_REUSEPORT。
- 允许在本机,以同一本机IP+已知端口A创建多个套接字
需要每个套接字均指定此选项。
IPv4 套接字选项
这些套接字选项由IPv4处理,它们的级别(即 getsockopt 和 setsockopt 函数的第二个参数)为 IPPROTO_IP。
IP_RECVDSTADDR 套接字选项
导致所收到UDP数据报的目的IP地址由recvmsg函数作为辅助数据返回。
IP_RECVIF 套接字选项
导致所收到UDP数据报的接收接口索引由recvmsg作为辅助数据返回。
IPv6 套接字选项
这些套接字选项由 IPv6 处理,它们的级别(即 getsockopt 和 setsockopt 函数的第二个参数)为 IPPROTO_IPV6。
IPV6_DONTFRAG 套接字选项
开启本选项将禁止为UDP套接字或原始套接字自动插入分片首部,外出分组中大小超过发送接口MTU的那些分组将被丢弃。
IPV6_PATHMTU 套接字选项
用于获取路径MTU发现功能确定的当前MTU。
IPV6_USE_MIN_MTU 套接字选项
若本选项设为1,表示路径MTU发现不必执行,因为分组将以IPV6最小MTU发送。
若本选项设为0,路径MTU发现需执行。
若本选项设为-1,路径MTU发现对单播目的地执行。对多播目的地将以IPV6最小MTU发送。
TCP 套接字选项
TCP有两个套接字选项,它们的级别(即 getsockopt 和 setsockopt 函数的第二个参数)为IPPROTO_TCP。
fcntl 函数
与代表“file control”(文件控制)的名字相符,fcntl 函数可执行各种描述符控制操作。
学习参考资料:
《UNIX网络编程 卷1:套接字联网API》 第3版