Unix网络编程补充:套接字选项
1. getsockopt
和setsockop
函数
#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$ )。内核能够以两种方式通知进程这个错误:
- 如果进程阻塞在对该套接字的 $select$ 错误上,那么无论是检查可读条件还是可写条件,$select$ 均返回并设置其中一个或所有两个条件;
- 如果进程使用信号驱动式
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
分节,会导致三种情况之一:
- 对端响应
ACK
,应用进程不会收到通知; - 对端响应
RST
,本端TCP
收到通知后得知对端已经崩溃且重新启动,设置 $ECONNRESET$ 错误并关闭套接字; - 对端没有响应,源自 $Berkeley$ 的
TCP
实现将会重新发送 $8$ 个探测分节,两两相隔 $75$ 秒。如果还是没有响应则放弃,设置 $ETIMEOUT$ 错误并关闭套接字;如果收到一个ICMP
错误,则会返回相应的错误。
2.5 SO_LINGER
本选项指定 $close$ 函数如何处理面向连接的协议 ( TCP
和SCTP
等 )。默认操作是 $close$ 立即返回,如果有数据残留,会尝试将这些数据发送给对端。
#include <sys/socket.h>
struct linger {
int l_onoff; // 0=off, nonzero=on
int l_linger; // linger time, POSIX specifies units as seconds
};
- 如果 $l_-onoff$ 为 $0$ ,$l_-linger$ 的值将被忽略,默认配置将生效;
- 如果 $l_-onoff$ 非 $0$ 且 $l_-linger$ 为 $0$ ,那么 $close$ 某个连接时
TCP
将终止连接,丢弃所有留存在发送缓冲区中的数据,并给对端发送RST
。这样一来就避免了 $TIME_-WAIT$ 阶段,但是如果在 $2MSL$ 时间内创建了该连接的分身,会导致来自刚被终止的连接上的旧的重复分节被不正确地传递到新的分身上; - 如果 $l_-onff$ 非 $0$ 且 $l_-linger$ 也非 $0$ ,那么当套接字关闭时内核将维持一段时间的连接。如果此时发送缓冲区中仍有数据,进程将休眠,直到所有数据发送完成且被确认或者到达时间上限。如果到达时间上限返回,会设置 $EWOULDBLOCK$ 错误,且丢弃发送缓冲区中的数据。如果套接字是非阻塞的,它将不等待 $close$ 完成。
2.6 SO_REUSEADDR
和SO_REUSEPORT
$SO_-REUSEADDR$ 选项有以下作用:
- 允许重启一个监听服务器并绑定众所周知的端口,即使以前建立的使用这个端口的进程仍然存在。一般情况下,在同一个端口重复绑定会失败,但是如果该服务器在绑定前指定了该选项,绑定将会成功。所有
TCP
服务器都应该指定这个选项,从而允许服务器在这种情形下被重新启动; - 允许在同一个端口上启动同一服务器的多个实例,只要每个实例绑定不同的
IP
地址; - 允许单个进程绑定同一端口到多个套接字上,只要每次绑定指定不同的本地
IP
地址即可; - 允许完全重复的绑定:当一个
IP
地址和端口已绑定到某个套接字上时,如果传输协议支持,同样的IP
地址和端口还可以捆绑到另一个套接字上。一般来说,本特性仅支持UDP
套接字;
随着多播的加入,$SO_-REUSEPORT$ 也被引入:
- 允许完全重复的绑定,不过只有在绑定了同一
IP
地址和端口的套接字都指定了该选项时才可以; - 如果被绑定的
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$ 函数新创建的套接字没有属主,然而如果一个新的套接字是从监听套接字创建而来的,那么这个新的套接字属主将从监听套接字继承而来。