• Linux socket编程 套接字选项


    1. 套接字选项概述

    有很多方法来获取和设置套接字的选项, 以影响套接字行为:

    • getsockopt和setsocketopt;
    • fcntl;
    • ioctl;

    2. getsockopt和setsockopt

    2个函数仅用于套接字, 分别用于获取和设置套接字选项

    #include <sys/types.h>          /* See NOTES */
    #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);
    
    • 参数
      sockfd 一个打开的套接字描述符
      level 级别, 指定系统中解释选项的代码或为通用套接字代码, 或为某个特定于协议的代码(e.g. IPv4/6, TCP, STCP)
      optval 指向某个变量(optval)的指针, setsockopt从optval中取得选项待设置的新值, getsockopt则把已获得的选项当前值存放到*optval中
      optlen 对setsockopt是值参数, 对getsockopt是值-结果参数, 用于指明optval的大小

    套接字选项粗分为2个基本类型: 启用或进制某个特性的二元选项, 称为标志选项; 取得并返回我们可以设置或检查的特定值的选项, 称为值选项.

    套接字层和IP层的套接字选项汇总表:

    leve级别 optname选项名 get set 说明 标志 数据类型
    SOL_SOCKET SO_BROADCAST · · 允许发送广播数据报 · int
    SOL_SOCKET SO_DEBUG · · 开启调试跟踪 · int
    SOL_SOCKET SO_DONTROUTE · · 绕过外出路由表查询 · int
    SOL_SOCKET SO_ERROR · 获取待处理错误并清除 int
    SOL_SOCKET SO_KEEPALIVE · · 周期性测试连接是否仍存活 · int
    SOL_SOCKET SO_LINEGER · · 若有数据待发送, 则延迟关闭 linger{}
    SOL_SOCKET SO_OOBINLINE · · 让接收到的带外数据继续在线留存 · int
    SOL_SOCKET SO_RCVBUF · · 接收缓冲区大小 int
    SOL_SOCKET SO_SNDBUF · · 发送缓冲区大小 int
    SOL_SOCKET SO_RCVLOWAT · · 接收缓冲区低水位标记 int
    SOL_SOCKET SO_SNDLOWAT · · 发送缓冲区低水位标记 int
    SOL_SOCKET SO_RCVTIMEO · · 接收超时 timeval{}
    SOL_SOCKET SO_SNDTIMEO · · 发送超时 timeval{}
    SOL_SOCKET SO_REUSEADDR · · 允许重用本地地址 · int
    SOL_SOCKET SO_REUSEPORT · · 允许重用本地端口 · int
    SOL_SOCKET SO_TYPE · 取得套接字类型 int
    SOL_SOCKET SO_USELOOPBACK · · 路由套接字取得所发送数据的副本 · int
    IPPROTO_IP IP_HDRINCL · · 随数据包含的IP首部 · int
    IPPROTO_IP IP_OPTIONS · · IP首部选项
    IPPROTO_IP IP_RECVDSTADDR · · 返回目的IP地址 · int
    IPPROTO_IP IP_RECVIF · · 返回接收接口索引 · int
    IPPROTO_IP IP_TOS · · 服务类型和优先权 int
    IPPROTO_IP IP_TTL · · 存活时间 int
    IPPROTO_IP IP_MULTICAST_IP · · 指定外出接口 in_addr{}
    IPPROTO_IP IP_MULTICAST_TTL · · 指定外出TTL u_char
    IPPROTO_IP IP_MULTICAST_LOOP · · 指定是否环回 u_char
    IPPROTO_IP IP_ADD_MEMBERSHIP · 加入多播组 ip_mreq{}
    IPPROTO_IP IP_DROP_MEMBERSHIP · 离开多播组 ip_mreq{}
    IPPROTO_IP IP_BLOCK_SOURCE · 阻塞多播源 ip_mreq_source{}
    IPPROTO_IP IP_UNBLOCK_SOURCE · 开通多播源 ip_mreq_source{}
    IPPROTO_IP IP_ADD_SOURCE_MEMBERSHIP · 加入源特定多播组 ip_mreq_source{}
    IPPROTO_IP IP_DROP_SOURCE_MEMBERSHIP · 离开源特定多播组 ip_mreq_source{}
    IPPROTO_ICMPV6 ICMP6_FILTER · · 指定待传递的ICMPv6消息类型 icmp6_filter{}
    IPPROTO_IPV6 IPV6_CHECKSUM · · 用于原始套接字的校验和字段偏移 int
    IPPROTO_IPV6 IPV6_DONTFRAG · · 丢弃大的分组而非将其分片 · int
    IPPROTO_IPV6 IPV6_NEXTHOP · · 指定下一跳地址 sockaddr_in6{}
    IPPROTO_IPV6 IPV6_PATHMTU · 获取当前路径MTU ip6_mtuinfo{}
    IPPROTO_IPV6 IPV6_RECVDSTOPTS · · 接收目的地选项 · int
    IPPROTO_IPV6 IPV6_RECVHOPLIMIT · · 接收单播跳限 · int
    IPPROTO_IPV6 IPV6_RECVHOPOPTS · · 接收步跳选项 · int
    IPPROTO_IPV6 IPV6_RECVPATHMTU · · 接收路径MTU · int
    IPPROTO_IPV6 IPV6_RECVPKTINFO · · 接收分组信息 · int
    IPPROTO_IPV6 IPV6_RECVRTHDR · · 接收源路径 · int
    IPPROTO_IPV6 IPV6_RECVTCLASS · · 接收流通类别 · int
    IPPROTO_IPV6 IPV6_UNICAST_HOPS · · 默认单播跳限 int
    IPPROTO_IPV6 IPV6_USE_MIN_MTU · · 使用最小MTU · int
    IPPROTO_IPV6 IPV6_V6ONLY · · 禁止v4兼容 · int
    IPPROTO_IPV6 IPV6_XXX · · 黏附性辅助数据
    IPPROTO_IPV6 IPV6_MULTICAST_IF · · 指定外出接口 u_int
    IPPROTO_IPV6 IPV6_MULTICAST_HOPS · · 指定外出跳限 int
    IPPROTO_IPV6 IPV6_MULTICAST_LOOP · · 指定是否环回 · u_int
    IPPROTO_IPV6 IPV6_JOIN_GROUP · 加入多播组 ipv6_mreq{}
    IPPROTO_IPV6 IPV6_LEAVE_GROUP · 离开多播组 ipv6_mreq{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_JOIN_GROUP · 加入多播组 group_req{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_LEAVE_GROUP · 离开多播组 group_source_req{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_BLOCK_GROUP · 阻塞多播组 group_source_req{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_UNBLOCK_GROUP · 开通多播组 group_source_req{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_JOIN_SOURCE_GROUP · 加入源特定多播组 group_source_req{}
    IPPROTO_IP或
    IPPROTO_IPV6
    MCAST_LEAVE_SOURCE_GROUP · 离开源特定多播组 group_source_req{}
    IPPROTO_TCP TCP_MAXSEG · · TCP最大分节大小 int
    IPPROTO_TCP TCP_NODELAY · · 禁止Nagle算法 · int
    IPPROTO_SCTP SCTP_ADAPTION_LAYER · · 适配层指示 stcp_setadaption{}
    IPPROTO_SCTP SCTP_ASSOCINFO · 检查并设置关联信息 stcp_assocparams{}
    IPPROTO_SCTP SCTP_AUTOCLOSE · · 自动关闭操作 int
    IPPROTO_SCTP SCTP_DEFAULT_SEND_PARAM · · 默认发送参数 stcp_sndrcvinfo{}
    IPPROTO_SCTP SCTP_DISABLE_FRAGMENTS · · SCTP分片 · int
    IPPROTO_SCTP SCTP_EVENTS · · 感兴趣事件的通知 sctp_event_subscribe{}
    IPPROTO_SCTP SCTP_GET_PEER_ADDR_INFO 获取对端地址状态 sctp_paddrinfo{}
    IPPROTO_SCTP SCTP_I_WANT_MAPPED_V4_ADDR · · 映射的v4地址 · int
    IPPROTO_SCTP SCTP_INTTMSG · · 默认的INIT参数 sctp_initmsg{}
    IPPROTO_SCTP SCTP_MAXBURST · · 最大猝发大小 int
    IPPROTO_SCTP SCTP_MAXSEG · · 最大分片大小 int
    IPPROTO_SCTP SCTP_NODELAY · · 禁止Nagle算法 · int
    IPPROTO_SCTP SCTP_PEER_ADDR_PARAMS · 对端地址参数 sctp_paddrparams{}
    IPPROTO_SCTP SCTP_PRIMARY_ADDR · 主目的地址 sctp_setprim{}
    IPPROTO_SCTP SCTP_RTOINFO · RTO信息 sctp_rtoinfo{}
    IPPROTO_SCTP SCTP_SET_PEER_PRIMARY_ADDR · 对端的主目的地址 sctp_setpeerprim{}
    IPPROTO_SCTP SCTP_STATUS · 获取关联状态 sctp_status{}

    说明:

    1. 数据类型形如linger{}, 表示struct linger;
    2. 标有"标志"的列指出一个选项是否为标志选项. 当给这些标志选项调用getsockopt函数时, *optval是一个整数. *optval中返回的值为0, 表示相应的选项被禁止, 不为0表示相应的选项被启用;
    3. setsockopt函数需要一个不为0 的optval值来启用选项, 为0 的optval来禁止选项;
    4. 如果"标志"列不含"·", 那么相应选项用于在用户进程与系统之间传递所指定数据类型的值;

    3. 检查选项是否受支持并获取默认值

    思路: 利用getsockopt 获取每个level对应的选项名, 如果没有出错, 则证明支持该选项; 如果出错, 则说明不支持. 如果读取出来的值大小, 跟选项汇总表不一致, 则表明可能有哪里出问题, 打印出来.
    一个sockopt的判断伪代码

    sockfd = socket();
    
    if (getsockopt(fd, opt_level, opt_name, &val, &len) < 0) {
        // getsockopt error
        ...
    }
    else {
    // no getsockopt error
    // print opt_val_str
    }
    

    如果IPV6_DONTFRAG, SCTP_AUTOCLOSE等宏定义无法识别, 请确认安装好libsctp-dev, lksctp-tools库后, 然后#include <netinet/sctp.h>
    安装命令, 详见 Unix下基于SCTP socket的通信:一对一场景 | 简书

    $ sudo apt-get install libsctp-dev lksctp-tools
    

    源代码:

    #include <sys/types.h> 
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
    #include <netinet/sctp.h>
    
    /* UNP官方代码 */
    #include "ourhdr.h" 
    
    union val {
        int i_val;
        long l_val;
        struct linger linger_val;
        struct timeval timeval_val;
    } val;
    
    int Socket(int domain, int type, int protocol);
    static char *sock_str_flag(union val *, int);
    static char *sock_str_int(union val *, int);
    static char *sock_str_linger(union val *, int);
    static char *sock_str_timeval(union val *, int);
    
    /**
    * 自定义结构sock_opts, 包含了获得或输出套接字选项的所有信息
    */
    struct sock_opts {
        const char *opt_str; /// 字符名称
        int opt_level; /// 级别
        int opt_name;  /// 名称
        char *(*opt_val_str)(union val *, int); /// 函数指针, 用于输出
    } sock_opts[] = {
        {"SO_BROADCAST",  SOL_SOCKET,  SO_BROADCAST,     sock_str_flag},
        {"SO_DEBUG",      SOL_SOCKET,  SO_DEBUG,         sock_str_flag},
        {"SO_DONTROUTE",  SOL_SOCKET,  SO_DONTROUTE,     sock_str_flag},
        {"SO_ERROR",      SOL_SOCKET,  SO_ERROR,         sock_str_int },
        {"SO_KEEPALIVE",  SOL_SOCKET,  SO_KEEPALIVE,     sock_str_flag},
        {"SO_LINGER",     SOL_SOCKET,  SO_LINGER,        sock_str_linger},
    	{"SO_OOBINLINE",  SOL_SOCKET,  SO_OOBINLINE,     sock_str_flag},
    	{"SO_RCVBUF",     SOL_SOCKET,  SO_RCVBUF,        sock_str_int },
    	{"SO_SNDBUF",     SOL_SOCKET,  SO_SNDBUF,        sock_str_int },
    	{"SO_RCVLOWAT",   SOL_SOCKET,  SO_RCVLOWAT,      sock_str_int },
    	{"SO_SNDLOWAT",   SOL_SOCKET,  SO_SNDLOWAT,      sock_str_int },
    	{"SO_RCVTIMEO",   SOL_SOCKET,  SO_RCVTIMEO,      sock_str_timeval },
    	{"SO_SNDTIMEO",   SOL_SOCKET,  SO_SNDTIMEO,      sock_str_timeval },
    
    	{"SO_REUSEADDR",  SOL_SOCKET,  SO_REUSEADDR,     sock_str_flag },
    #ifdef SO_REUSEPORT
    	{"SO_REUSEPORT",  SOL_SOCKET,  SO_REUSEPORT,     sock_str_flag},
    #else
    	{"SO_REUSEPORT",  0,  		   0,     			 NULL},
    #endif
    
    	{"SO_TYPE",  	  SOL_SOCKET,  SO_TYPE,       	 sock_str_int },
    #ifdef SO_USELOOPBACK
    	{"SO_USELOOPBACK",SOL_SOCKET,  SO_USELOOPBACK,   sock_str_flag},
    #else
    	{"SO_USELOOPBACK",  0,  		   0,     			 NULL},
    #endif
    
    	{"IP_TOS",  	  IPPROTO_IP,  IP_TOS,     	     sock_str_int },
    	{"IP_TTL",  	  IPPROTO_IP,  IP_TTL,  	     sock_str_int },
    
    	{"IPV6_DONTFRAG", 		IPPROTO_IPV6, IPV6_DONTFRAG,    	 sock_str_flag},
    	{"IPV6_UNICAST_HOPS",  	IPPROTO_IPV6, IPV6_UNICAST_HOPS,     sock_str_int },
    	{"IPV6_V6ONLY",  		IPPROTO_IPV6, IPV6_V6ONLY,     		 sock_str_flag},
    	
    	{"TCP_MAXSEG",   IPPROTO_TCP,  TCP_MAXSEG,        sock_str_int },
    	{"TCP_NODELAY",  IPPROTO_TCP,  TCP_NODELAY,      sock_str_flag},
    
    	{"SCTP_AUTOCLOSE",  IPPROTO_SCTP,  SCTP_AUTOCLOSE,     sock_str_int },
    #ifdef SCTP_MAXBURST
    	{"SCTP_MAXBURST",   IPPROTO_SCTP,  SCTP_MAXBURST,      sock_str_int },
    #else
    	{"SCTP_MAXBURST",  0,  		   0,     			 NULL},
    #endif
    
    	{"SCTP_MAXSEG",  	IPPROTO_SCTP,  SCTP_MAXSEG,    	   sock_str_int },
    	{"SCTP_NODELAY",    IPPROTO_SCTP,  SCTP_NODELAY,       sock_str_flag},
    
    	{NULL, 				0, 			   0, 				   NULL}
    };
    
    int main()
    {
        int fd;
        socklen_t len;
        struct sock_opts *ptr;
    
        for (ptr = sock_opts; ptr->opt_str != NULL; ++ptr) {
            printf("%s: ", ptr->opt_str);
            if (ptr->opt_val_str == NULL) {
                printf("(undefined)
    ");
            }
            else {
                switch(ptr->opt_level) {
                case SOL_SOCKET:
                case IPPROTO_IP:
                case IPPROTO_TCP:
                    fd = Socket(AF_INET, SOCK_STREAM, 0);
                    break;
    
    #ifdef IPV6
                case IPPROTO_IPV6:
                    fd = Socket(AF_INET6, SOCK_STREAM, 0);
                    break;
    #endif
    #ifdef IPPROTO_SCTP
                case IPPROTO_SCTP:
                    fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
                    break;
    #endif
                default:
                    // err_quit("Can't create fd for level %d
    ", ptr->opt_level);
    				fprintf(stderr, "Can't create fd for level %d
    ", ptr->opt_level);
                }
    
                len = sizeof(val);
    			
    			// if (fd < 0) continue;
    
                if (getsockopt(fd, ptr->opt_level, ptr->opt_name, &val, &len) == -1) {
                    err_ret("getsockopt error");
                }
                else {
                    printf("default = %s
    ", (*ptr->opt_val_str)(&val, len));
                }
    
                close(fd);
            }
        }
        
        return 0;
    }
    
    int Socket(int domain, int type, int protocol)
    {
        int fd = socket(domain, type, protocol);
    
        if (fd < 0) {
            perror("socket error");
            return -1;
        }
    
        return fd;
    }
    
    
    static char strres[128];
    static char *sock_str_flag(union val *ptr, int len) 
    {
        if (len != sizeof(int)) 
            snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
        else 
            snprintf(strres, sizeof(strres), "%s", (ptr->i_val == 0) ? "off": "on");
    
        return strres;
    }
    
    static char *sock_str_int(union val *ptr, int len)
    {
        if (len != sizeof(int)) 
            snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
        else 
            snprintf(strres, sizeof(strres), "%d	", ptr->i_val);
    
        return strres;
    }
    
    static char *sock_str_linger(union val *ptr, int len)
    {
        if (len != sizeof(struct linger)) 
            snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct linger)", len);
        else 
            snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d	", ptr->linger_val.l_onoff, ptr->linger_val.l_linger);
    
        return strres;
    }
    
    static char *sock_str_timeval(union val *ptr, int len)
    {
        if (len != sizeof(struct timeval)) 
            snprintf(strres, sizeof(strres), "size (%d) not sizeof(struct timeval)", len);
        else 
            snprintf(strres, sizeof(strres), "%ld sec, %ldusec	", ptr->timeval_val.tv_sec, ptr->timeval_val.tv_usec);
    
        return strres;
    }
    

    运行结果:

    SO_BROADCAST: default = off
    SO_DEBUG: default = off
    SO_DONTROUTE: default = off
    SO_ERROR: default = 0	
    SO_KEEPALIVE: default = off
    SO_LINGER: default = l_onoff = 0, l_linger = 0	
    SO_OOBINLINE: default = off
    SO_RCVBUF: default = 131072	
    SO_SNDBUF: default = 16384	
    SO_RCVLOWAT: default = 1	
    SO_SNDLOWAT: default = 1	
    SO_RCVTIMEO: default = 0 sec, 0usec	
    SO_SNDTIMEO: default = 0 sec, 0usec	
    SO_REUSEADDR: default = off
    SO_REUSEPORT: default = off
    SO_TYPE: default = 1	
    SO_USELOOPBACK: (undefined)
    IP_TOS: default = 0	
    IP_TTL: default = 64	
    Can't create fd for level 41
    IPV6_DONTFRAG: getsockopt error: Bad file descriptor
    Can't create fd for level 41
    IPV6_UNICAST_HOPS: getsockopt error: Bad file descriptor
    Can't create fd for level 41
    IPV6_V6ONLY: getsockopt error: Bad file descriptor
    TCP_MAXSEG: default = 536	
    TCP_NODELAY: default = off
    SCTP_AUTOCLOSE: default = 0	
    SCTP_MAXBURST: (undefined)
    SCTP_MAXSEG: default = size (8) not sizeof(int)
    SCTP_NODELAY: default = off
    

    4. 常用套接字选项

    4.1 SO_REUSEADDR 选项

    可通过设置SO_REUSEADDR 选项,强制使用被处于TIME_WAIT状态的连接占用的socket地址。即使sock处于TIME_WAIT状态,与之绑定的socket地址,也可以立即被重用。也可以通过修改内核参数/proc/sys/net/ipv4/tcp_tw_recycle,来快速回收被关闭的socket,从而使得TCP连接不进入TIME_WAIT状态,进而允许应用程序立即重用本地socket地址。
    常见场景:bind地址失败,还能继续对该sock fd进行bind。因为bind会改变套接字内部状态,如果不加上SO_REUSEADDR 选项,无法立即用于重新bind。

    重用本地址方法:

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    assert(sock >= 0);
    int reuse = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);
    int ret = bind(sock, (struct sockaddr*)&address, sizeof(address)); /* SO_REUSEADDR选项可以让bind地址失败后,还能继续bind */
    
    

    4.2 SO_RCVBUF, SO_SNDBUF 选项

    SO_RCVBUF表示TCP接收缓冲区大小;
    SO_SNDBUF表示TCP发送缓冲区大小;

    用setsockopt设置TCP接收缓冲区和发送缓冲区大小时,系统会将其值加倍,并且不得小于某个最小值。
    TCP接收缓冲区最小值256byte, 发送缓冲区最小值2048byte(对于不同系统,值可能不一样)。
    这样做的目的在于:确保一个TCP连接有足够的空闲缓冲区来处理拥塞(i.g. 快速重传算法就期望TCP接收缓冲区至少容纳4个大小为SMSS的TCP报文段)。
    可以通过内核参数/proc/sys/net/ipv4/tcp_rmem/proc/sys/net/ipv4/tcp_wmem,来修改TCP接收缓冲区、发送缓冲区的大小没有限制。

    修改TCP发送缓冲区的客户端程序,关键代码:

    typedef struct sockaddr SA;
    
    int sendbuf = atoi(arg[3]); 
    int len = sizeof(sendbuf);
    /* 先设置TCP发送缓冲区大小,然后立即读取 */
    setsockopt(sock, SOL_SOCKET, &sendbuf, sizeof(sendbuf)); /* 单位byte */
    getsockopt(sock, SOL_SOCKET, &sendbuf, (socklen_t *)&len);
    
    printf("TCP send buffer size after setting is %d
    ", sendbuf);
    
    if (connect(sock, (SA *)&servaddr, sizeof(servaddr)) != -1) { /* tcp connect server */
    ...
     
    }
    ...
    

    修改TCP接收缓冲区的服务器程序,关键代码:

    int recv = atoi(argv[3]);
    int len = sizeof(len);
    /*  先设置接收缓冲区大小,然后立即读取 */
    setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, sizeof(recvbuf)); /* 单位byte */
    getsockopt(sock, SOL_SOCKET, SO_RCVBUF, &recvbuf, (socklen_t *)&len);
    
    printf("TCP receive buffer size after setting is %d
    ", recvbuf);
    
    bind(sock, (SA *)&servaddr, sizeof(servaddr));
    ...
    

    4.3 SO_RCVLOWAT, SO_SNDLOWAT 选项

    SO_RCVLOWAT 表示TCP接收缓冲区的低水平位标记,默认值1byte
    SO_SNDLOWAT 表示TCP发送缓冲区的低水平位标记,默认值1byte

    这2个选项,用于判断socket是否可读,或可写:
    当TCP接收缓冲区中,可读数据的总数 > 低水平位标记时,IO系统将通知应用程序可从对应socket读取数据;
    当TCP发送缓冲区中,空闲空间 > 低水平位标记时, IO系统将通知应用程序可写数据到socket;


    4.4 SO_LINGER 选项

    SO_LINGER 选项用于控制close关闭TCP连接时的行为。默认情况,调用close关闭socket时,会立即返回,TCP模块负责把socket对应的发送缓冲区残留数据发送给对方。

    setsockopt/getsockopt 设置/获取SO_LINGER选项值时,要传递给一个linger类型的结构体,其定义如下:

    #include <sys/socket.h>
    struct linger {
        int l_onoff;  /* 开启(非0),关闭(0)该选项 */
        int l_linger; /* 滞留时间 */
    };
    

    1)l_onoff值为0,此时SO_LINGER选项不起作用,close用默认行为来关闭socket;
    2)l_onoff值非0,linger_为0,此时调用close立即返回,TCP模块将丢弃被关闭socket发送缓冲区中的数据,同时给对端发送一个RST分节。---- 终止异常连接。
    3)l_onoff值非0,l_linger > 0,此时close行为取决于2个条件:
    1.被关闭的socket对应发送缓冲区,是否还有残留数据;
    2.该socket是阻塞,还是非阻塞的。
    对于阻塞socket,close将等待一段长为l_linger的时间,直到TCP模块发送完所有残留数据,并得到对端确认,那么close将返回-1,且errno被设置为EWOULDBLOCK。 如果socket是非阻塞的,close将立即返回,此时需要根据其返回值和errno来判断残留数据是否已经发送完毕。


    5. fcntl函数和ioctl

    fcntl函数可执行各种描述符控制操作.

    fcntl, ioctl和路由套接字操作汇总表:

    操作 fcntl ioctl 路由套接字 POSIX
    设置套接字为非阻塞式I/O型 F_SETFL, O_NONBLOCK FIONBIO fcntl
    设置套接字为信号驱动式I/O型 F_SETFL, O_ASYNC FIOASYNC fcntl
    设置套接字属主 F_SETDOWN SIOCSPGRP或
    FIOSETOWN
    fcntl
    获取套接字属主 F_GETDOWN SIOCSPGRP或
    FIOSETOWN
    fcntl
    获取套接字接收缓冲区的字节数 FIONREAD fcntl
    测试套接字是否处于带外标志 SIOCATMARK sockatmark
    获取接口列表 SIOCGIFCONF sysctl
    接口操作 SIOC[GS]IFxxx
    ARP高速缓存操作 SIOCxARP RTM_xxx
    路由表操作 SIOCxxxxRT RTM_xxx

    说明:
    1)前6个操作可由任何进程应用于套接字, 接着2个接口操作较少见, 最后2个操作(ARP和路由表操作)由ifconfig, route之类管理程序执行;
    2)POSIX一列表明是POSIX规定的方法, 推荐使用;
    3)sockatmark作为测试是否处于带外标志的首选方法;

    5.1 fcntl与socket网络编程

    fcntl提供了与socket编程相关的特性:

    • 非阻塞式I/O
      以F_SETFL方式设置选项O_NONBLOCK文件状态标志

    • 信号驱动式I/O
      以F_SETFL方式设置选项O_ASYNC, 套接字一旦状态变化, 内核就产生一个SIGIO信号

    • 以F_SETDOWN指定用于接收SIGIO和SIGURG信号的套接字属主(pid或pgid).
      F_GETDOWN返回套接字当前属主

    5.2 fcntl函数

    • 原型
    #include <unistd.h>
    #include <fcntl.h>
    
    int fcntl(int fd, int cmd, ... /* arg */ );
    
    • 参数
      命令cmd有多种用途, 其中跟socket编程相关的:
      1)获取文件标志: F_GETFL命令获取由F_SETFL命令设置的文件标志;
      2)影响套接字描述符: O_NONBLOCK(非阻塞式IO), O_ASYNC(信号驱动式IO);

    • 返回值
      成功时, 返回值为0, 参数输出取决于cmd命令; 失败时, 返回-1, errno被设置.

    5.3 fcntl使用示例

    fcntl开启非阻塞IO的典型代码:

    int flags;
    
    if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
        err_sys("F_GETFL error");
    }
    
    flags |= O_NONBLOCK;
    
    if (fcntl(fd, F_SETFL, flags) < 0) {
        err_sys("F_SETFL error");
    }
    

    关闭非阻塞IO标志的典型代码:

    int flags;
    
    if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
        err_sys("F_GETFL error");
    }
    
    flags &= ~O_NONBLOCK;
    
    if (fcntl(fd, F_SETFL, flags) < 0) {
        err_sys("F_SETFL error");
    }
    

    参考

    《UNP》, 《Linux高性能服务器编程》

  • 相关阅读:
    mysql事物中行锁与表锁
    https的实现原理
    基于http的断点续传和多线程下载
    Cookie与Session
    centos 7 安装python3
    为CentOS下的Docker安装配置python3【转】
    Jmeter如何提取响应头部的JSESSIONID【转】
    centOS7 安装nginx
    centos 7.X关闭防火墙和selinux
    (四)从输入URL到页面加载发生了什么
  • 原文地址:https://www.cnblogs.com/fortunely/p/14856765.html
Copyright © 2020-2023  润新知