UNP_7_套接字选项

Posted on Mar 7, 2020

7.1 概述

  有很多方法来获取和设置影响套接字的选项

  • getsockopt() 和 setsockopt() 函数
  • fcntl() 函数
  • ioctl() 函数

  fcntl 函数是把套接字设置为非阻塞式 I/O 型或信号驱动式 I/O 型以及设置套接字的方法

7.2 getsockopt() 和 setsockopt() 函数

这两个函数仅用于套接字

#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);
// 均返回:成功为 0,出错为 -1

参数解析

  • sockfd:必须指向一个打开的套接字描述符
  • level:指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(IPv4,IPv6,TCP,SCTP)
  • optval:指向某个变量的指针,setsockopt 从 optval 中取得选项带设置的新值,getsockopt 则把以获取的选项放到 optval 中,optval 大小由 optlen 决定

套接字选项大致分为两类:

  • 启用或者禁止某个特性的二元选项
  • 取得并返回我们可以设置或检查的特定值的选项

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

  书中编写了一个用来检查机器是否支持大多数套接字的程序,如果支持该套接字则输出它的默认值。这里不做展示,见 UNP-P152

7.4 套接字状态

  对于一些套接字选项,针对套接字的状态,什么时候设置或获取选项有时序上的考虑。
  对于 TCP 是很重重要的,因为 accept 在三次握手成功后才会给服务器返回已连接套接字。如果想在三路握手完成时确保从监听套接字继承来的套接字已经被设置,我们必须提前设置监听套接字的这些特殊套接字

7.5 通用套接字选项

  这些套接字选项式协议无关的,不过其中有些选项只能应用到某些特定类型的套接字中。举例来说,尽管 SO_BROADCAST 套接字选项是通用的,但却只能应用于数据报套接字。

SO_BROADCAST 套接字选项

  该选项设置进程是否可以发送广播消息。只有数据报套接字(UDP 等)支持广播,并且还必须是支持广播消息的网路上。

SO_DEBUG 套接字选项

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

SO_DONTROUTE 套接字选项

  本选项规定外出的分组将绕过底层协议的正常路由机制。

SO_ERROR 套接字选项

  当一个套接字上发生错误时,伯克利内核会将 so_error 变量设置为 Unix Exxx,则该套接字变为错误待处理状态。内核可以通过下面两种方式进行通知

  • 进程阻塞在 select 的调用上,无论检查的时可读还是可写条件,select 均返回并设置其中一个或所有条件。
  • 如果进程使用信号式驱动 I/O 模型,那就给进程或者进程组产生一个 SIGIO 信号。 进程可以通过访问 SO_ERROR 套接字选项获取 so_error 的值。由 getsockopt 返回的整数值就是该套接字待处理的错误。so_error 随后由内核复位为 0. 如果进程调用 read 时没有数据返回,so_error 非 0,那么 read 返回 -1,且 error 被置为 so_error 的值。so_error 随后复位。如果该套接字上有数据在排队等待读取。那么 read 返回那些数据而不是错误条件。如果在进程调用 write 时 so_error 为非 0 值,那么 write 返回 -1 且 error 被置为 so-error 的错误

SO_KEEPALIVE 套接字选项

  给一个 TCP 套接字设置保持 SO_KEEPALIVE 后,如果 2 小时以内在该套接字的任一方向上都么有数据交换,TCP 就自动给对端发送一个保持存活探测分节。这是一个对端必须响应的分节,它会导致以下三种情况之一:

  • 对端回复期望的 ACK 响应。应用进程得不到通知,因为连接正常,且 keepalive 并不在程序中进行处理。之后以 2 小时的频率发送探测分节
  • 对端用 RST 进行响应,它告知本端 TCP:对端已经崩溃并重启。该套接字的待处理错误变成 ECONNRESET,套接字本身则被关闭
  • 对端没有任何响应,某些系统会另外发送 8 个探测分节,每两个相隔 75s,试图获取响应,所有均没有响应则关闭套接字,对应的错误如果是无响应则设置为 ETIMEOUT,如果是 ICMP 的错误,直接返回即可

※ 注意:对于 SO_KEEPALIVE 的时间设置,并不是对套接字直接进行设置,而是在系统内核中进行控制,所以一旦设置,对所有本机开启本选项的套接字均会受影响。
※ 本选项一般由服务器进行使用,服务器使用是因为他们花费大量的时间阻塞在等待 TCP 连接的输入上,也就是等待客户的请求上,如果客户关闭电源等不发送 FIN 的行为,会造成服务器的资源浪费

SO_LINGER 套接字选项

  本选项指定 close 函数对面向连接的协议(TCP,SCTP)如何操作。默认操作是 close 立即返回,但是如果有数据残留在套接字发送缓冲区中,系统试着把这些数据发送给对端。
  SO_LINGER 套接字选项使得我们可以改变这个默认设置。本选项要求用户进程与内核间传递如下结构,它在头文件<sys/socket.h>中定义

struct linger{
        int l_onoff;
        int l_linger;
}

对 setsockopt 的调用将根据其中两个结构成员的值分为三种情形之一:

  1. l_nonff 为 0,关闭本选项,忽略 l_linger,使用默认设置。
  2. 如果 l_onoff != 0 && l_linger == 0,调用 close 时,TCP 将中止连接,并删除所有缓冲区中的数据,并发送一个 RST 给对端,并不使用普通的四次挥手,这样的话,避免了 TCP 的 TIME_WAIT 状态,但是存在以下的问题,在 2 MSL 内创建了该连接的另一个相同连接,导致刚才被终止的连接上的旧的重复分节被不正确的传递到新的连接(也就是 TIME_WAIT 所解决的问题)
  3. 如果 l_onoff != 0 && l_linger != 0,当套接字关闭会拖延一段时间。也就是说,套接字的发送缓冲区中还有数据时会进入睡眠。直到所有数据都受到 ACK,或者拖延时间到达。如果套接字是非阻塞的,不会等待 close 完成,就是拖延时间不为 0,这时我们要通过判断 close 的返回值来确定是什么情况。

SO_OOBINLINE 套接字选项

  本选项开启时,带外数据被留在正常输入队列中,这种情况下接收函数的 MSG_OOB 标志不能用来读带外数据

SO_RCVBUF 和 SO_SNDBUF 套接字选项

  这两个套接字允许我们改变接收缓冲区和发送缓冲区的大小

SO_RCVLOWAT 和 SO_SNDLOWAT 套接字选项

  这两个套接字选项允许修改接收和发送的低水位标记。默认可读标记大小为 1.可写标记大小为 2048。

SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项

  这两个选项允许我们给套接字的接收和发送设置超时值。

SO_REUSEADDR 和 SO_REUSEPORT 套接字选项

  有如下 4 个作用:

  1. SO_REUSEADDR 可以创建一个监听服务器并绑定众所周知的端口,即使这个端口已经被使用,共用本端口,这个条件一般这样会碰到:
  • 启动一个监听服务器
  • 连接请求到达,派生一个子进程来处理这个客户
  • 监听服务器终止,但子进程继续提供服务
  • 重启监听服务器(通过调用 socket,bind,listen 重新启动时,试图绑定一个现有的连接,bind 失败,但是打开 SO_REUSEADDR 选项,就会成功,建议 TCP 服务器启动该套接字选项,支持这种情况)
  1. SO_REUSEADDR 允许在同一个端口启动同一个服务器的多个实例,只要 IP 地址不同即可(使用 IP 别名技术等待)但是即使设置了 SO_REUSEADDR 也不可以启动多个相同 IP 和 port 的 TCP 连接。
  2. SO_REUSEADDR 允许一个进程捆绑相同端口到多个套接字上,每次指定的 IP 不同即可
  3. SO_REUSEADDR 允许完全重复的捆绑(把相同 IP 和 port 绑定到多个套接字上,一般是 UDP 套接字)

SO_TYPE 套接字选项

  返回套接字类型,本选项一般由继承了套接字的进程进行使用

SO_USELOOPBACK 选项

  本选项仅用于 AF_ROUTE 的套接字。当本套接字开启时,相应套接字将接收在其上发送的任何数据报的一个副本

7.6 ipv4 套接字选项

  这些套接字选项由 IPv4 处理,级别(getsockopt,setsockopt 第二个参数)为 IPPROTO_IP,组播套接字暂时不解析

IP_HDRINCL 套接字选项

  如果本选项是给一个原始 IP 套接字设置的,我们必须为所有在该原始套接字上发送的数据构造自己的 IP 首部。一般情况下,由内核进行构造,但是有些程序需要自己进行构造

IP_OPTIONS 套接字选项

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

IP_RECVDSTADDR 套接字选项

  本套接字选项导致所收到的 UDP 数据报的目的地址的 IP 地址由 recvmsg 函数作为辅助数据进行返回

IP_RECVIF 套接字选项

  本套接字选项导致所收到的 UDP 数据报的接收接口索引由 recvmsg 函数作为辅助数据进行返回

IP_TOS 套接字选项

  本套接字选项允许我们为 TCP,UDP,SCTP 套接字设置 IP 首部的服务类型字段

IP_TTL 套接字选项

  可以使用本选项设置或获取系统用在从某个给定套接字发送的单播分组上的默认 TTL 值。对 TCP,UDP 默认是 64,原始套接字默认 255

7.7 icmpv6 套接字选项

  这个唯一的套接字由 ICMPv6 处理,它的级别为 IPPROTO_ICMPV6

ICMP6_FILTER 选项

  本选项允许获取或设置一个 icmp6_filter 结构,该结构指出 256 个可能的 ICMPv6 消息类型中那些将经由某个原始套接字传递给所在进程

7.8 ipv6 套接字选项

  这些套接字由 IPv6 处理,它的级别为 IPPROTO_IPV6,组播的套接字选项暂时不做考虑。这些选项很多使用了辅助数据。

IPV6_CHECKSUM 套接字选项

  本选项指定用户数据中校验和所处位置的字节偏移。如果该值为负,内核将:

  1. 给出所有外出分组计算并存储校验和
  2. 验证外来分组的校验和,丢弃所有校验和无效的分组

※ 本选项并不影响 ICMPV6 原始套接字,因为内核总是为 ICMPv6 原始套接字计算并存储校验和。 ※ 本选项指定 -1,内核不会在套接字上计算并存储校验和,也不会验证接收分组的校验和

IPV6_DONTFRAG 套接字选项

  开启本选项将禁止为 UDP 套接字或原始套接字自动插入分片首部,外出分组中大小超过发送接口 MTU 的那些分组将被丢弃。发送分组的系统调用不会为此返回错误。应用进程应开启 IPV6_RECVPATHMTU 选项已获悉路径 MTU 的变动

IPV6_NEXTHOP 套接字选项

  本选项将外出数据报的下一跳地址指定为一个套接字地址结构

IPV6_PATHMTU 套接字选项

  本选项不能设置,只能获取。获取本选项时,返回值为路径 MTU 发现功能确定当前的 MTU

IPV6_RECVDSTOPTS 套接字选项

  开启本选项表示,任何接收到的 IPv6 目的地址都将由 recvmsg 作为辅助数据返回。默认关闭

IPV6_RECVHOPLIMIT 套接字选项

  开启本选项表明,任何接收到的跳限字段都将由 recvmsg 作为辅助数据返回。默认关闭

IPV6_RECVHOPOPTS 套接字选项

  开启本选项表明,任何接收到的 IPv6 步跳选项都将由 recvmsg 作为辅助数据返回。默认关闭

IPV6_RECVPATHMTU 套接字选项

  开启本选项表明,某条路径 MTU 的改变都将由 recvmsg 作为辅助数据返回

IPV6_RECVPKTINFO 套接字选项

  开启本选项表示,任何接收到的 IPv6 数据报的(目的 IPv6 地址和到达接口索引)都将由 recvmsg 作为辅助数据返回。

IPV6_RECVRTHDR 套接字选项

  开启本选项表示,任何接收到的 IPv6 路由首部都将由 recvmsg 作为辅助数据返回。默认关闭

IPV6_RECVTCLASS 套接字选项

  开启本选项表示,任何接收到的流通类别(包含 DSCP 和 ECN 字段)都将由 recvmsg 作为辅助数据返回。默认关闭

IPV6_UNICAST_HOPS 套接字选项

  设置相应套接字发送数据报的默认跳限,获取的是内核的默认跳限

IPV6_USE_MIN_MTU 套接字选项

  • 设置为 1:不执行路径发现 MTU,分组使用 IPv6 的最小 MTU 发送
  • 设置为 0:路径 MTU 发现功能对所有目的地都执行
  • 设置为 -1:路径 MTU 发现功能只对单播目的地执行,对多播使用最小 MTU。
    ※ 默认值 -1

IPV6_V6ONLY 套接字选项

  在一个 AF_INET6 套接字上开启本选项将限制它执行 IPv6 通信。本选项默认关闭

7.9 tcp 套接字选项

  TCP 有两个套接字选项,它的级别为 IPPROTO_TCP

TCP_MAXSEG 套接字选项

  本选项允许我们获取或设置 TCP 连接的最大分节大小(MSS),返回值是我们的 TCP 能够发给对端的最大数据量。

TCP_NODELAY 套接字选项

  本选项禁止 TCP 的 Nagle 算法,默认情况下是启动的。具体 Naggle 算法参考《TCP/IP》

7.10 sctp 套接字选项

  SCTP 的套接字选项数目较多,SCTP 提供了较细粒度的控制能力。它的级别为 IPPROTO_SCTP

SCTP_ADAPTION_LAYER套接字选项

  在关联初始化期间,任何一个断点都可能指定一个适配层指示(adaption layer indication)。这个指示是一个32位无符号整数,可由两端的应用进程用来协调任何本地应用适配层。

SCTP_ASSOCINFO套接字选项

  本套接字选项可用于以下三个目的:(a)获取关于某个现有关联的信息,(b)改变某个已有关联的参数,(c)为未来的关联设置默认信息。

SCTP_AUTOCLOSE套接字选项

  本选项允许我们获得或设置一个SCTP端点的自动关闭时间。自动关闭时间是一个SCTP关联在空闲时保持打开的秒数。默认是禁止的。

SCTP_DEFAULT_SEND_PARAM套接字选项

  SCTP有许多可选的发送参数,它们通常作为辅助数据传递,或者由sctp_sendmsg函数使用。

SCTP_DISABLE_FRAGMENTS套接字选项

  SCTP通常把太大而不适合置于单个SCTP分组中的用户消息分割成多个DATA块。

SCTP_EVENTS套接字选项

  本套接字选项允许调用者获取、开启或禁用各种SCTP通知。

SCTP_GET_PEER_ADDR_INFO套接字选项

  本选项仅用于获取某个给定对端地址的相关信息,包括拥赛窗口、平滑化后的RTT和MTU等。

SCTP_I_WANT_MAPPED_V4_ADDR套接字选项

  这个标志套接字选项用于为AF_INET6类型套接字开启或禁止IPv4映射地址,其默认状态是开启的。

SCTP_INITMSG套接字选项

  本套接字选项用于获取或设置某个SCTP套接字在发送INIT消息时所用的默认初始参数。

SCTP_MAXBURST套接字选项

  本套接字选项允许应用进程获取或设置用于分组发送的最大猝发大小(maximum burst size)。

SCTP_MAXSEG套接字选项

  本套接字选项允许应用进程获取或设置用于SCTP的最大片端大小(maximum fragment size)。

SCTP_NODEELAY套接字选项

  开启本选项将禁止SCTP的Nagle算法。默认是关闭的。

SCTP_PEER_ADDR_PARAMS套接字选项

  本套接字选项运行应用进程获取或设置关于某个关联的对端地址的各种参数。

SCTP_PRIMARY_ADDR套接字选项

  本套接字选项用于获取或设置本地断点所用的主目的地址。

SCTP_RTOINFO套接字选项

  本套接字选项用于获取或设置各种RTO信息,它们既可以是关于某个给定关联的设置,也可以是用于本地断点的默认设置。

SCTP_SET_PEER_PRIMARY_ADDR套接字选项

  设置本套接字选项导致发送一个消息:请求对端把所指定的本地地址作为它的主目的地址。

SCTP_STATUS套接字选项

  本套接字选项用于获取某个SCTP关联的状态。

7.11 fcntl 函数

fcntl 函数可以执行各种描述符控制操作,fcntl 函数提供了与网络编程相关的如下特性:

  • 非阻塞式 I/O。通过使用 F_SETFL 命令设置 O_NONBLOCK 文件状态标志,我们可以把一个套接字设置为非阻塞型
  • 信号驱动式 I/O。通过使用 F_SETFL 命令设置 O_ASYNC 文件状态标志,我们可以把一个套接字设置为一旦状态发生变化,就产生一个 SIGIO 信号
  • F_SETOWN 命令允许我们指定用于接收 SIGIO 和 SIGURG 信号的套接字属主(进程 ID 或进程组 ID)
#include<fcntl.h>
int fcntl(int fd, int cmd, ...);
// 返回:若成功取决于 cmd,出错则为 -1

  使用 socket 函数新创建的套接字并没有属主。然而如果新的套接字是从一个监听套接字创建来的,套接字属主将由已连接套接字从监听套接字继承过来。

小结

  套接字选项多种多样,不同的套接字应用位置和应用场景不同。
  许多 TCP 连接设置 SO_KEEPALIVE 套接字选项以自动关闭一个半开连接,由 TCP 层处理,不需要程序层设置定时器等操作,缺点是不能确定另一端是掉线还是暂时连通不了。
  SO_LINGER 套接字选项是我们更好的控制 close 函数返回的时机,并允许我们强制发送 RST 来终止连接,但是不进入 TIME_WAIT 状态也会给我们带来一些问题。
  每个 TCP 和 SCTP 都由发送缓冲区和接收缓冲区,每个 UDP 只有接受缓冲区,有些套接字选项可以用来设置这些缓冲区。