部分重要的SOCKET选项
首先介绍两个专门用来读取和设置socket文件描述符属性的系统调用
getsockopt() && setsockopt()
函数原形:
//读取socket文件描述符的属性
int getsockopt(int sockfd , int level , int option_name , void* option_value , socklen_t* restrict option_len);
//设置socket文件描述符的属性
int setsockopt(int sockfd , int level , int option_name , const void* option_value , socklen_t option_len)
参数:
①、sockfd:
指定被操作的目标socket
②、level:
指定要操作哪个协议的选项,比如IPV4,IPV6、TCP等。
③、option_name:
指定选项的名字
④、option_value:
对于getsockopt()来说,指向返回选项值的缓冲。
对于setsockopt(),指向包含新选项值的缓冲。
④、option_len:
对于getsockopt()来说,作为入口参数时,选项值的最大长度。作为出口参数时,选值的实际长度。
对于setsockopt(),现选项的长度。
返回值
成功时返回0,失败时返回-1,并设置errno。
socket选项表
注意
对于服务器而言,部分socket选项只能在调用listen之前针对监听socket设置才有效。这是因为连接socket只能由accept调用返回,而accept从listen监听队列中获取的连接是已经完成三次握手的,这说明服务器已经想被接受的链接上发送出了TCP同步报文段。但有的socket选项却应该在TCP同步报文段中设置(比如:TCP的最大报文段)。
对于这种情况,应该对监听socket设置这些选项,那么accept返回的连接socket将自动继承这些选项。这些选项包括:SO_DEBUG , SO_DONTROUTE , SO_KEEPALIVE , SO_LINGER , SO_OOBINLINE , SO_RCVBUF , SO_RCVLOWAT , SO_SENDBUF , SO_SNDLOWAT , TCP_MAXSIZE 和 TCP_NODELAY。
而对于客户端而言,这些socket选项应该在调用connect函数之前设置,因为connect函数调用成功返回后,TCP三握手已经完成。
几个重要的socket选项
SO_REUSEADDR选项
TCP断开连接四挥手的最后一步,作为主动断开连接的一方有一个time_wait的状态,这个状态一般持续2MSL,但是,在TCP连接没有完全断开之前不允许重新监听是不合理的。因为,我们重新监听的连接,虽然是占用同一个端口,但IP地址不同。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
此外,还可以通过修改内核参数 /proc/sys/net/ipv4/tcp_tw_recycle(临时修改) ,也可以通过修改配置文件/etc/sysctl.conf。来快速回收被关闭的socket,从而使TCP连接根本不进入TIME_WAIT状态,进而允许应用程序立即重用本地的socket地址。
//修改配置文件/etc/sysctl.conf
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
使用命令“/sbin/sysctl –p”使之立即生效。
SO_RCVBUF和SO_SNDBUF选项
SO_RCVBUF和SO_SNDBUF选项分别表示TCP接收缓冲区和发送缓冲区的大小。不过,当我们用setsockopt来设置TCP的接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不得小于其个最小值。TCP接收缓冲区的最小值是256字节,而发送缓冲区的最小值是2048字节(不过,不同的系统可能有不同的默认最小值)。
此外,我们可以直接修改内核参数/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem来强制TCP接收缓冲区和发送缓冲区的大小没有最小值限制。
例如设置TCP发送缓冲区的大小
int sendbuf = BUFFSIZE;
int len = sizeof( sendbuf );
setsockopt( sock, SOL_SOCKET, SO_SNDBUF, &sendbuf, sizeof( sendbuf ) );
说明:服务器端必须在listen之前设置,客户端,必须在connect之前设置,原因上文有说明。
SO_RCVLOWAT和SO_SNDLOWAT选项
SO_RCVLOWAT和SO_SNDLOWAT选项分别表示TCP接收缓冲区和发送缓冲区的低水位标记。它们一般被I/O复用系统调用,用来判断socket是否可读或可写。
当TCP接收缓冲区中可读数据的总数大于其低水位标记时,I/O复用系统调用将通知应用程序可以从对应的socket上读取数据;当TCP发送缓冲区中的空闲空间(可以写入数据的空间)大于其低水位标记时,I/O复用系统调用将通知应用程序可以往对应的socket上写入数据。
默认情况下,TCP接收缓冲区的低水位标记和TCP发送缓冲区的低水位标记均为1字节。
SO_LINGER选项
默认情况下,当我们使用close系统调用来关闭一个socket时,close将立即返回,TCP模块负责把该socket对应的TCP发送缓冲区中残留的数据发送给对方。SO_LINGER选项用于解决当需要关闭套接字时,协议栈发送缓冲区中尚有未发送完的数据,等待这些数据发送完,所需要的最长时间。
设置SO_LINGER选项的值时,我们需要给setsockopt(getsockopt)系统调用传递一个linger类型的结构体,其定义如下:
struct linger
{
int l_onoff; //开启(非0)还是关闭(0)该选项
int l_linger; // 滞留时间
};
根据linger结构体中两个成员变量的不同值,close 系统调用可能产生如下3种行为之一:
l_onoff 等于0:此时SO_LINGER选项不起作用,close用默认行为关闭socket。
l_onoff 不为0,l_linger等于0:此时close 系统调用立即返回,TCP模块将丢弃被关闭的socket对应的TCP发送缓冲区中残留的数据,同时给对方一个复位报文段。因此,这种情况给服务器提供了异常终止一个连接的方法。
l_onoff不为0,l_linger大于0 :此时close的行为取决于两个条件:
(1)被关闭的socket对应的TCP发送缓冲区中是否还有残留的数据;
(2)该socket是阻塞的还是非阻塞的。 对于阻塞的socket,close将等待一段长为l_linger的时间,直到TCP模块发送完所有残留数据并得到对方的确认。如果这段之间内TCP模块没有发送完残留数据并得到对方的确认,那么close系统调用将返回-1并设置errno为EWOULDBLOCK。 如果socket是非阻塞的,close将立即返回,此时我们需要根据其返回值和errno来判断残留数据是否已经发送完毕。
SO_KEEPALIVE
SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入。
设置该选项后,如果2小时内在此套接口的任一方向都没有数据交换,TCP就自动给对方发一个保持存活探测分节(keepalive probe)。这是一个对方必须响应的TCP分节.它会导致以下三种情况:
①、对方接收一切正常:以期望的ACK响应。2小时后,TCP将发出另一个探测分节。
②、对方已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭。
③、对方无任何响应:源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为EHOSTUNREACH。
设置方法:
keepAlive = 1;
setsockopt(listenfd, SOL_SOCKET, SO_KEEPALIVE, (void*)&keepAlive, sizeof(keepAlive));
如果我们不能接受如此之长的等待时间,从TCP-Keepalive-HOWTO上可以知道一共有两种方式可以设置:
一种是修改内核关于网络方面的 配置参数/proc/sys/net/ipv4/tcp_keepalive_time
//修改配置文件/etc/sysctl.conf
// TCP发送keepalive探测消息的间隔时间(秒),用于确认TCP连接是否有效
net.ipv4.tcp_keepalive_time = 1000
// 探测消息未获得响应时,重发该消息的间隔时间(秒)。
net.ipv4.tcp_keepalive_intvl = 10
//在认定TCP连接失效之前,最多发送多少个keepalive探测消息。
net.ipv4.tcp_keepalive_probes = 3
另外一种就是SOL_TCP字段的TCP_KEEPIDLE, TCP_KEEPINTVL, TCP_KEEPCNT三个选项:
int keepIdle = 1000; // TCP发送keepalive探测消息的间隔时间(秒),用于确认TCP连接是否有效
int keepInterval = 10; //探测消息未获得响应时,重发该消息的间隔时间(秒)。
int keepCount = 3; //在认定TCP连接失效之前,最多发送多少个keepalive探测消息。
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
SO_KEEPALIVE设置空闲2小时才发送一个“保持存活探测分节”,不能保证实时检测。对于判断网络断开时间太长,对于需要及时响应的程序不太适应。
当然也可以修改时间间隔参数,但是会影响到所有打开此选项的套接口!关联了完成端口的socket可能会忽略掉该套接字选项。
UNIX网络编程不推荐使用SO_KEEPALIVE来做心跳检测,还是在业务层以心跳包做检测比较好,也方便控制。
SO_SNDTIMEO && SO_RCVTIMEO
这两个选项用于设置阻塞模式下套接字,SO_SNDTIMEO用于在send数据由于对端tcp窗口太小,发不出去时的最长阻塞时间;SO_RCVTIMEO用于recv函数因为接收缓冲区无数据而阻塞的最长时间。
设置方法:
struct timeval tv;
tv.tv_sec=5;
tv.tv_usec=0;
Setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));