欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

部分重要的SOCKET选项

程序员文章站 2024-03-21 09:33:34
...

  首先介绍两个专门用来读取和设置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选项
注意
  对于服务器而言,部分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));