Unix网络编程补充:套接字选项

1. getsockoptsetsockop函数

#include <sys/socket.h>

// 成功返回0,出错返回-1
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

// 成功返回0,出错返回-1
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

        这两个函数仅适用于套接字。$sockfd$ 指向一个打开的套接字描述符,$level$ 指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码。$optval$ 是一个指向某个变量的指针,$setsockopt$ 从 $optval$ 中获取设置,$getoptval$ 向 $optval$ 中写入设置。$optval$ 的大小由 $optlen$ 指定。套接字选项粗分为两大基本类型:一是启用或禁用某个特性的二元选项 ( 标志选项 ),二是取得并返回我们可以设置或检查的特定值的选项 ( 值选项 )。对于标志选项,$optval$ 是一个整数,$1$ 标识启用,$0$ 表示禁用。

$level$ $optname$ $get$ $set$ 说明 标志选项 数据类型
$SOL_-SOCKET$ $SO_-BROADCAST$ $\checkmark$ $\checkmark$ 允许发送广播数据报 $\checkmark$ $int$
$SO_-DEBUG$ $\checkmark$ $\checkmark$ 开启调试跟踪 $\checkmark$ $int$
$SO_-DONTROUTE$ $\checkmark$ $\checkmark$ 绕过外出路由表查询 $\checkmark$ $int$
$SO_-ERROR$ $\checkmark$ 获取待处理错误并清除 $int$
$SO_-KEEPALIVE$ $\checkmark$ $\checkmark$ 周期性测试连接是否仍存活 $\checkmark$ $int$
$SO_-LINGER$ $\checkmark$ $\checkmark$ 若有数据待发送则延迟关闭 $linger\{\}$
$SO_-OOBINLINE$ $\checkmark$ $\checkmark$ 让接收到的带外数据在线存留 $\checkmark$ $int$
$SO_-RCVBUF$ $\checkmark$ $\checkmark$ 接收缓冲区大小 $int$
$SO_-SNDBUF$ $\checkmark$ $\checkmark$ 发送缓冲区大小 $int$
$SO_-RCVLOWAT$ $\checkmark$ $\checkmark$ 接收缓冲区低水位标记 $int$
$SO_-SNDLOWAT$ $\checkmark$ $\checkmark$ 发送缓冲区低水位标记 $int$
$SO_-RCVTIMEO$ $\checkmark$ $\checkmark$ 接收超时 $timeval\{\}$
$SO_-SNDTIMEO$ $\checkmark$ $\checkmark$ 发送超时 $timeval\{\}$
$SO_-REUSEADDR$ $\checkmark$ $\checkmark$ 允许重用本地地址 $\checkmark$ $int$
$SO_-REUSEPORT$ $\checkmark$ $\checkmark$ 允许重用本地端口 $\checkmark$ $int$
$SO_-TYPE$ $\checkmark$ 取得套接字类型 $int$
$SO_-USELOOPBACK$ $\checkmark$ $\checkmark$ 路由套接字取得所发送数据的副本 $\checkmark$ $int$
$IPPROTO_-IP$ $IP_-HDRINCL$ $\checkmark$ $\checkmark$ 随数据包含的IP首部 $\checkmark$ $int$
$IP_-OPTIONS$ $\checkmark$ $\checkmark$ IP首部选项 见后续
$IP_-RECVDSTADDR$ $\checkmark$ $\checkmark$ 返回目的IP地址 $\checkmark$ $int$
$IP_-RCVIF$ $\checkmark$ $\checkmark$ 返回接收接口索引 $\checkmark$ $int$
$IP_-TOS$ $\checkmark$ $\checkmark$ 服务类型和优先权 $int$
$IP_-TTL$ $\checkmark$ $\checkmark$ 存活时间 $int$
$IP_-MULTICAST_-IF$ $\checkmark$ $\checkmark$ 指定外出接口 $in_-addr\{\}$
$IP_-MULTICAST_-TTL$ $\checkmark$ $\checkmark$ 指定外出 $TTL$ $u_-char$
$IP_-MULTICAST_-LOOP$ $\checkmark$ $\checkmark$ 指定是否环回 $u_-char$
$IP_-ADD_-MEMBERSHIP$ $\checkmark$ 加入多播组 $ip_-mreq\{\}$
$IP_-DROP_-MEMBERSHIP$ $\checkmark$ 离开多播组 $ip_-mreq\{\}$
$IP_-BLOCK_-SOURCE$ $\checkmark$ 阻塞多播源 $ip_-mreq_-source\{\}$
$IP_-UNBLOCK_-SOURCE$ $\checkmark$ 开通多播源 $ip_-mreq_-source\{\}$
$IP_-ADD_-SOURCE_-MEMBERSHIP$ $\checkmark$ 加入源特定多播组 $ip_-mreq_-source\{\}$
$IP_-DROP_-SOURCE_-MEMBERSHIP$ $\checkmark$ 离开源特定多播组 $ip_-mreq_-source\{\}$
$IPPROTO_-ICMPV6$ $ICMP6_-FILTER$ $\checkmark$ $\checkmark$ 指定待传递的ICMPv6消息类型 $icmp6_-filter\{\}$
$IPPROTO_-IPV6$ $IPV6_-CHECKSUM$ $\checkmark$ $\checkmark$ 用于原始套接字的校验和字段偏移 $int$
$IPV6_-CHECKSUM$ $\checkmark$ $\checkmark$ 用于原始套接字的校验和字段偏移 $int$
$IPV6_-DONTFRAG$ $\checkmark$ $\checkmark$ 丢弃大的分组而将其分片 $\checkmark$ $int$
$IPV6_-NEXTHOP$ $\checkmark$ $\checkmark$ 指定下一跳地址 $sockaddr_-in6\{\}$
$IPV6_-PATHMTU$ $\checkmark$ 获取当前路径 $MTU$ $ip6_-mtuinfo\{\}$
$IPV6_-RECVDSTOPTS$ $\checkmark$ $\checkmark$ 接收目的地选项 $\checkmark$ $int$
$IPV6_-RECVHOPLIMIT$ $\checkmark$ $\checkmark$ 接收单播跳限 $\checkmark$ $int$
$IPV6_-RECVHOPOPTS$ $\checkmark$ $\checkmark$ 接收步跳选项 $\checkmark$ $int$
$IPV6_-RECVPATHMTU$ $\checkmark$ $\checkmark$ 接收路径 $MTU$ $\checkmark$ $int$
$IPV6_-RECVPKTINFO$ $\checkmark$ $\checkmark$ 接收分组信息 $\checkmark$ $int$
$IPV6_-RECVRTHDR$ $\checkmark$ $\checkmark$ 接收源路径 $\checkmark$ $int$
$IPV6_-RECVTCLASS$ $\checkmark$ $\checkmark$ 接收流通类型 $\checkmark$ $int$
$IPV6_-UNICAST_-HOPS$ $\checkmark$ $\checkmark$ 默认单播跳限 $int$
$IPV6_-USE_-MIN_-MTU$ $\checkmark$ $\checkmark$ 使用最小 $MTU$ $\checkmark$ $int$
$IPV6_-V6ONLY$ $\checkmark$ $\checkmark$ 禁止v4兼容 $\checkmark$ $int$
$IPV6_-XXX$ $\checkmark$ $\checkmark$ 黏附性辅助数据 见后续
$IPV6_-MULTICAST_-IF$ $\checkmark$ $\checkmark$ 指定外出接口 $u_-int$
$IPV6_-MULTICAST_-HOPS$ $\checkmark$ $\checkmark$ 指定外出跳限 $int$
$IPV6_-MULTICAST_-LOOP$ $\checkmark$ $\checkmark$ 指定是否环回 $\checkmark$ $u_-int$
$IPV6_-JOIN_-GROUP$ $\checkmark$ 加入多播组 $ipv6_-mreq\{\}$
$IPV6_-LEAVE_-GROUP$ $\checkmark$ 离开多播组 $ipv6_-mreq\{\}$
$IPPROTO_-IP$ 或 $IPPROTO_-IPV6$ $MCAST_-JOIN_-GROUP$ $\checkmark$ 加入多播组 $group_-req\{\}$
$MCAST_-LEAVE_-GROUP$ $\checkmark$ 离开多播组 $group_-source_-req\{\}$
$MCAST_-BLOCK_-SOURCE$ $\checkmark$ 阻塞多播源 $group_-source_-req\{\}$
$MCAST_-UNBLOCK_-SOURCE$ $\checkmark$ 开通多播源 $group_-source_-req\{\}$
$MCAST_-JOIN_-SOURCE_-GROUP$ $\checkmark$ 加入源特定多播组 $group_-source_-req\{\}$
$MCAST_-LEAVE_-SOURCE_-GROUP$ $\checkmark$ 离开源特定多播组 $group_-source_-req\{\}$
$IPPROTO_-TCP$ $TCP_-MAXSEG$ $\checkmark$ $\checkmark$ TCP最大分节大小 $int$
$TCP_-NODELAY$ $\checkmark$ $\checkmark$ 禁止 $Nagle$ 算法 $\checkmark$ $int$

2. 通用套接字选项

2.1 SO_DEBUG

        本选项仅由TCP支持。当给一个TCP套接字开启本选项时,内核将为TCP在该套接字发送和接收的所有分组保留详细跟踪信息。这些信息保存在内核的某个环形缓冲区中,并可使用 $trpt$ 程序进行检查。

2.2 SO_DONTROUTE

        本选项规定外出的分组将绕过底层协议的正常路由机制。例如,IPv4情况下外出分组将被定向到合适的本地接口,也就是由目的地址的网络和子网部分确定的本地接口。如果这样的本地接口无法由目的地址确定,会返回 $ENETUNREACH$ 错误。路由守护进程 ( $routed$ 和 $gated$ ) 常使用本选项来绕过路由表 ( 路由表不正确的情况下 ),用于强制将分组从特定接口发出。

2.3 SO_ERROR

        当一个套接字上发生错误时,源自 $Berkeley$ 的内核中的协议模块将该套接字的 $so_-error$ 变量设置为错误码中的一个,称为待处理错误 ( $pending$ $error$ )。内核能够以两种方式通知进程这个错误:

  1. 如果进程阻塞在对该套接字的 $select$ 错误上,那么无论是检查可读条件还是可写条件,$select$ 均返回并设置其中一个或所有两个条件;
  2. 如果进程使用信号驱动式I/O,那么会产生一个 $SIGIO$ 信号。

        进程可以通过访问 $SO_-ERROR$ 选项来获取 $so_-error$ 的值,随后 $so_-error$ 会被复位为 $0$ 。当进程调用 $read$ 且没有数据返回时,如果 $so_-error$ 为非 $0$ 值,那么 $read$ 返回 $-1$ 且 $errno$ 被设置为 $so_-error$ 的值,随后 $so_-error$ 被设置为 $0$ 。同样的,如果在进程调用 $write$ 时 $so_-error$ 非 $0$ ,也会进行上述操作。

2.4 SO_KEEPALIVE

        给一个TCP套接字设置该选项后,如果 $2$ 小时内在该套接字的任一方向上都没有数据交换,TCP就会自动给对端发送一个保持存活探测分节 ( $keep-alive$ $probe$ )。这是一个对端必须相应的TCP分节,会导致三种情况之一:

  1. 对端响应ACK,应用进程不会收到通知;
  2. 对端响应RST,本端TCP收到通知后得知对端已经崩溃且重新启动,设置 $ECONNRESET$ 错误并关闭套接字;
  3. 对端没有响应,源自 $Berkeley$ 的TCP实现将会重新发送 $8$ 个探测分节,两两相隔 $75$ 秒。如果还是没有响应则放弃,设置 $ETIMEOUT$ 错误并关闭套接字;如果收到一个ICMP错误,则会返回相应的错误。

2.5 SO_LINGER

        本选项指定 $close$ 函数如何处理面向连接的协议 ( TCPSCTP等 )。默认操作是 $close$ 立即返回,如果有数据残留,会尝试将这些数据发送给对端。

#include <sys/socket.h>

struct linger {
  int l_onoff;  // 0=off, nonzero=on
  int l_linger;  // linger time, POSIX specifies units as seconds
};

2.6 SO_REUSEADDRSO_REUSEPORT

        $SO_-REUSEADDR$ 选项有以下作用:

  1. 允许重启一个监听服务器并绑定众所周知的端口,即使以前建立的使用这个端口的进程仍然存在。一般情况下,在同一个端口重复绑定会失败,但是如果该服务器在绑定前指定了该选项,绑定将会成功。所有TCP服务器都应该指定这个选项,从而允许服务器在这种情形下被重新启动;
  2. 允许在同一个端口上启动同一服务器的多个实例,只要每个实例绑定不同的IP地址;
  3. 允许单个进程绑定同一端口到多个套接字上,只要每次绑定指定不同的本地IP地址即可;
  4. 允许完全重复的绑定:当一个IP地址和端口已绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以捆绑到另一个套接字上。一般来说,本特性仅支持UDP套接字;

        随着多播的加入,$SO_-REUSEPORT$ 也被引入:

  1. 允许完全重复的绑定,不过只有在绑定了同一IP地址和端口的套接字都指定了该选项时才可以;
  2. 如果被绑定的IP地址是多播地址,那么与 $SO_-REUSEADDR$ 等效。

3.IPv4套接字选项

3.1 IP_OPTIONS

        本选项允许我们在IPv4首部中设置IP选项,要求我们熟悉IP首部中的IP选项格式。

3.2 IP_RECVDSTADDR

        本选项导致收到UDP数据报的目的IP地址由 $recvmsg$ 函数作为辅助数据返回。

3.3 IP_RECVIF

        本选项导致收到UDP数据报的接收接口索引由 $recvmsg$ 韩硕作为辅助数据返回。

4. IPv6套接字选项

4.1 IPV6_RECVDSTOPTS

        本选项会导致任何接收到的IPv6目的地选项都将由 $recvmsg$ 作为辅助数据返回。

4.2 IPV6_RECVHOPLIMIT

        本选项会导致任何接收到的跳限字段都将由 $recvmsg$ 作为辅助数据返回。

4.3 IPV6_RECVHOPOPTS

        本选项会导致任何接收到的IPv6步跳选项都将由 $recvmsg$ 作为辅助数据返回。

4.4 IPV6_RECVPATHMTU

        本选项会导致某跳路径 $MTU$ 在发生变化时由 $recvmsg$ 作为辅助数据返回。

4.5 IPV6_RECVPKTINFO

        本选项会导致接收到的IPv6数据报的目的IPv6地址和到达接口索引由 $recvmsg$ 作为辅助数据返回。

4.6 IPV6_RECVRTHDR

        本选项会导致接收到的IPv6路由首部将由 $recvmsg$ 作为辅助数据返回。

4.7 IPV6_RECVTCLASS

        本选项会导致接收到的流通类别将由 $recvmsg$ 作为辅助数据返回。

4.8 IPV6_USE_MIN_MTU

        设置为 $1$ 会关闭路径 $MTU$ 发现功能,使用IPv6的最小 $MTU$ 发送;设置为 $0$ 会对所有目的地执行路径 $MTU$ 发现;设置为 $-1$ 则路径 $MTU$ 发现仅对单播目的地执行,多播目的地使用最小 $MTU$ 。本选项默认值为 $-1$ 。

5. fcntl函数

#include <fcntl.h>

// 成功则返回cmd命令的结果,出错返回-1
int fcntl(int fd, int cmd, ... /* int arg */);

        $fcntl$ 函数可以执行各种描述符控制操作。如下表所示:

操作 $fcntl$ $ioctl$ 路由套接字 POSIX
设置套接字为非阻塞式I/O $F_-SETFL$ ,$O_-NONBLOCK$ $FIONBIO$ $fcntl$
设置套接字为信号驱动式I/O $F_-SETFL$ ,$O_-ASYNC$ $FIOASYNC$ $fcntl$
设置套接字属主 $F_-SETOWN$ $SIOCSPGRP$ / $FIOSETOWN$ $fcntl$
获取套接字属主 $F_-GETOWN$ $SIOCGPGRP$ / $FIOGETOWN$ $fcntl$
获取套接字接收缓冲区中的字节数 $FIONREAD$
测试套接字是否处于带外标志 $SIOCATMARK$ $sockatmark$
获取接口列表 $SIOCGIFCONF$ $sysctl$
接口操作 $SIOCGIFxxx$ / $SIOSIFxxx$
ARP高速缓存操作 $SIOCxARP$ $RTM_-xxx$
路由表操作 $SIOCxxxRT$ $RTM_-xxx$

        POSIX一栏指出POSIX规定的首选操作。$F_-SETOWN$ 命令的整数类型参数 $arg$ 既可以是一个正整数,指出接收信号的进程ID,也可以是一个负整数,其绝对值指出接收信号的进程组ID。同样的,$F_-GETOWN$ 命令既可以返回代表进程ID的正值,也可以返回代表进程组ID的负值。使用 $socket$ 函数新创建的套接字没有属主,然而如果一个新的套接字是从监听套接字创建而来的,那么这个新的套接字属主将从监听套接字继承而来。

Unix网络编程补充:套接字选项