sockopt套接字选项操作

参考:《UNIX 网络编程 · 卷1 : 套接字联网API》

获取和设置套接字选项的方法:

  1. getsockopt 和 setsockfopt函数
  2. fcntl 函数
  3. ioctl 函数

getsockopt & setsockopt函数

这两个函数仅用于套接字

#include 

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:选项所在的协层,例如SOL_SOCKET、IPPROTO_TCP等。

optname:需要访问的选项名。

optval:指向某个变量的指针,setsockopt从该变量中取得选项待设置的新值,getsockopt则把已获取的选项值存到该指针所指空间中。

optlen:指定参数optval的大小,对于setsockopt是一个值参数,在getsockopt是值-结果参数。

返回值:

成功执行时,返回0。失败返回-1,errno被设为以下的某个值:

  • EBADF:socket 不是有效的文件描述词
  • EFAULT:optval 指向的内存并非有效的进程空间
  • EINVAL:在调用 setsockopt() 时,optlen 无效
  • ENOPROTOOPT:指定的协议层不能识别选项
  • ENOTSOCK:socket 描述的不是套接字

可用选项如下,大概可以分为两类:标志选项(启用或禁止某个特性的二元选项),值选项(取得并返回我们可以设置或检查的特定值的选项)。其中 optval 带一对花括号的记法表示一个结构:

其中标志列说明了该选项是否为标志选项。当给这些标志选项调用 getsockopt 函数时,*optval 是一个整数,值为 0 表示选项被禁用,不为 0 表示被启用。

非标志选项用于在用户进程和系统之间传递所指数据类型的值。

套接字状态

某些套接字选项,针对套接字的状态,什么时候设置或获取选项有时序上的考虑。

比如下面的套接字选项时从 TCP 已连接套接字从监听套接字继承来的:SO_DEBUG、SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWA、SO_SNDBUF、SO_SNDLOWAT、TCP_MAXSEG、TCP_NODELAY。

这对 TCP 很重要,因为 accept 一直要到 TCP 层完成三路握手后才会给服务器返回已连接套接字。如果想要在三路握手完成时确保这些套接字选项中的某一个是给已连接套接字设置的,必须先给监听套接字设置该选项。

通用套接字选项

所谓通用套接字选项就是与协议无关的,不过有些选项只能应用到某些特定类型的套接字中。比如:尽管 SO_BROADCASE 套接字选项是“通用”的,但它却只能用于数据报套接字。

SO_BROADCASE

用于开启或禁止进程发送广播消息的能力。前提是:必须是数据报套接字,并且在支持广播消息的网络上。例如,以太网。

应用进程在发送广播数据报之前必须设置本套接字选项。一个 UDP 程序获取目的IP地址可以在内核中进行测试:如果目的地址是一个广播地址并且本套接字选项没有设置,就返回 EACCES 错误。

SO_DEBUG

本选项仅支持 TCP。当给一个 TCP 套接字开启本选项时,内核将位 TCP 在该套接字发送和接收所有分组保留详细跟踪信息。

这些信息保存在内核的某个环形缓冲区中,并可以通过 trpt 程序进行检查。

SO_DONTROUTE

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

例如,在 IPv4 下外出分组将被定向到适当的本地接口,也就是其目的地的网络和子网络部分确定的本地接口。如果这样的本地接口无法由目的地址确定,那么返回 ENETUNREACH 错误。

给函数 send、sendto、sendmsg 使用 MSG_DOWNROUTE 标志也能在个别的数据报上取得与本选项相同的效果。

路由守护进程经常使用本选项来绕过路由表(在路由表不正常的情况下),以强制将分组从特定接口送出。

SO_ERROR

当一个套接字上发生错误时,内核会将该套接字的 so_error 的变量设为标准 Unix Exxx 值中的一个,称为该套接字的待处理错误。内核通过下面两种方式之一立即通知进程这个错误。

(1)如果进程阻塞在对该套接字调用的 select 上,那么无论无何是检查可读条件还是可写事件,select 均返回并设置其中一个或所有两个条件。

(2)如果进程使用信号驱动 IO,那就给进程或进程组产生一个 SIGIO 信号。进场然后可以通过访问 SO_ERROR 套接字选项获取 so_error 的值。由 getsockopt 函数返回的整数值就是该套接字的待处理错误。so_error 随后由内核复位为 0。

当进程调用 read 且没有数据返回时,如果 so_error 为非 0 值,那么 read 返回 -1 并且 errno 被设置为 so_error 的值。so_error 随后被复位为 0。如果该套接字上有数据在排队等待读取,那么 read 返回那些数据而不是返回错误条件。

如果进程在调用 write 时 so_error 为非 0 值,那么 write 返回 -1 且 errno 被设置为 so_error 的值,并随后复位为 0。

注:这是一个可以获取,但是不能设置的套接字选项。

SO_KEEPALIVE

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

  1. 对端以期望的 ACK 响应。应用进程得不到通知(因为一些正常)。在又经过仍无动静的 2 小时后,TCP 将发出另一个探测分节。
  2. 对端以 RST 响应,告诉本端 TCP:对端已经崩溃并且已经重新启动。该套接字的待处理错误被设置为 ECONNRESET,套接字本身则被关闭。
  3. 对端对于本端发送的分节没有任何响应。TCP 将另外发送 8 个探测分节,每个间隔 75 秒(共 11 分 15 秒),视图得到一个响应。如果最后一个分节还没有响应则放弃(不能肯定对端主机崩溃,路由器崩溃 15 分钟有可能)。

如果根本没有对 TCP 的探测分节响应,该套接字的待处理错误就被置为 ETIMEOUT,套接字本身则被关闭。

如果该套接字收到一个 ICMP 错误作为某个探测分节的响应,那就返回响应的错误,套接字本身也被关闭。这种情况下常见的 ICMP 错误是 “host unreachable”(主机就不可达),说明对端主机可能没有崩溃,只是不可达,这种情况下待处理错误被设置 EHOSTUNREACH,主要是因为网络故障或者对端主机崩溃,最后一跳路由器也已经检测到它的崩溃。

该选项一般由服务器使用,不过客户端也可以使用。是因为有些服务器等待客户都端的请求。然而客户端主机连接掉线、电源掉电或系统崩溃,服务器进程永远不会知道,并将继续等待,这种情况称为半开连接。有了保持存活选项就可以检测出这些半开连接并终止它们。

大多数服务器会在应用程设计超时机制。通常是清理不可达客户端的半连接的较好办法,因为应用层实现的超时具备完全的控制能力。我们称之为心跳机制(heartbeat)。

SO_LINGER

该选项指定close函数对面向连接的协议(例如 TCP)如何操作。默认操作时 close 立即返回,但是如果有数据残留在缓冲区中,系统将试着把这些数据发送给对端。

情形 对端进程崩溃 对端主机崩溃 对端主机不可达
本端TCP正主动发送数据 对端TCP发送一个FIN,通过使用select判断可读条件可检测。如果本端TCP发送另外一个分节,对端TCP就以RST响应。如果在本端TCP收到RST之后应用进程仍试图写套接字,就会给该进程发送一个SIGPIPE信号 本段TCP将超时,并且套接字的待处理错误被设置为ETIMEDOUT 本端TCP将超时,并且套接字的待处理错误赚设置为EHOSTUNREACH
本端TCP正主动接收数据 对端TCP发送一个FIN,我们把它作为一个EOF读入 我们将停止接受数据 我们将停止接受数据
连接空闲,保持存活选项已设置 对端TCP发送一个FIN,通过使用select判断可读条件可检测。 在毫无动静2小时后,发送9个保持存活的探测分节,然后套接字待处理错误被设置为ETIMEDOUT 在毫无动静2小时后,发送9个保持存活的探测分节,然后套接字待处理错误被设置为EHOSTUNREACH
连接空闲,保持存活选项未设置 对端TCP发送一个FIN,通过使用select判断可读条件可检测。

上表是检测各种 TCP 条件的方法。

SO_LINGER 选项使得我们可以改变这个默认设置。要求用户进程与内核间传递如下结构:

#include 
struct linger
{
    int l_onoff;	//0=off, 非0=on
    int l_linger;	//时间,seconds
}

对 setsockopt 的调用将根据其中两个结构成员的值形成下列三种情况:

(1)如果 l_onoff 为 0,那么关闭本选项,l_linger 的值忽略。默认设置生效,即 close 立即返回。

(2)如果 l_onoff 为非 0 且 l_linger 为 0,那么当 close 某个连接时 TCP 将中止该连接。即 TCP 将丢弃保留在套接字发送缓冲区中的任何数据,并发送一个 RST 给对端,而没有通常的四分组连接终止序列。这样避免了 TIME_WAIT 状态,但是存在以下可能性:在 2MSL 秒内创建该连接的另一个化身,导致来自刚被终止连接上的旧的重复分节被不正确地递送到新的化身上。

(3)如果 l_onoff 为非 0 值且 l_linger 也为非 0 值,那么当套接字关闭时将拖延一段时间。这就是说如果在套接字发送缓冲区中仍有残留的数据,那么进程将投入睡眠,直到所有数据都已经发送完且被对方确认或延滞时间到。如果该套接字被设置为非阻塞型,那么它将不等待 close 完成,即使延滞时间为非 0 也是如此。当使用 SO_LINGER 选项的这个特性时,应用进程检查 close 的返回值是非常重要的,因为如果在数据发送完成并被确认前延滞时间到的话,close 将返回 EWOULDBLOCK 错误,且套接字发送缓冲区中的任何残留数据都被丢弃。

如下表汇总了 shutdown 的两种可能调用和对 close 的三种可能调用,以及它们对 TCP 套接字的影响。

函数 说明
shutdown,SHUT_RD 在套接字上不能再发出接收请求;进程仍可往套接字发送数据;套接字接收缓冲区中所有数据被丢弃;再接收到的任何数据由TCP丢弃;对套接字发送缓冲区没有任何影响。
shutdown,SHUT_WR 在套接字上不能再发出发送请求;进程仍可从套接字接收数据;套接字发送缓冲区中的内容被发送到对端,后跟正常的TCP连接终止序列(即发送FIN);对套接字接收缓冲区无任何影响。
close,l_onoff = 0(默认) 在套接字上不能再发出发送或接收请求;套接字发送缓冲区中的内容被发送到对端。如果描述符引用计数变为0;在发送完发送缓冲区中的数据后,跟以正常的TCP连接终止序列(即发送FIN);套接字接收缓冲区中内容被丢弃。
close,l_onoff = 1,l_linger = 0 在套接字上不能再发出发送或接收请求。如果描述符引用计数变为0;RST被发送到对端;连接的状态被置为CLOSED(没有TIME_WAIT状态);套接字发送缓冲区和套接字接收缓冲区中的数据被丢弃。
close,l_onoff = 1,l_linger != 0 在套接字上不能再发出发送或接收请求;套接字发送缓冲区中的数据被发送到对端。如果描述符引用计数变为0;在发送完发送缓冲区中的数据后,跟以正常的TCP连接终止序列(即发送FIN);套接字接收缓冲区中数据被丢弃;如果在连接变为CLOSED状态前延滞时间到,那么close返回EMNOULDBLOCK错误。

SO_OOBINLINE

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

SO_RECVBUF & SO_SNDBUF

每个套接字都有一个发送缓冲区和一个接收缓冲区。接收缓冲区被 TCP、UDP 用来保存接收到的数据,知道由应用进程来读取。对于 TCP 来说,套接字接收缓冲区中可用空间大小限定了 TCP 通告对端的窗口大小。

TCP 套接字接收缓冲区不可能溢出,因为不允许对端发送超过本端所通告窗口大小的数据。这就是 TCP 的流量控制,如果对端无视窗口大小而发出了超过本端所通告窗口大小的数据,本端 TCP 将丢弃它们。

对于 UDP 来说是没有流量控制的,当接收到的数据包装不进套接字接收缓冲区时,该数据报就被丢弃。较快的发送端可以红容易地淹没较慢的接收端,导致接收端的 UDP 丢弃数据报。

这两个套接字选项允许我们改变这两个缓冲区的默认大小。对于不同的实现, 默认值大小可以有很大的差别。较早的实现大小均默认为 4096 字节,而新的系统可以是 8192~61440 字节间的任何值。

如果主机支持 NFS,那么 UDP 发送缓冲区的大小经常默认为 9000 字节左右的一个值,而 UDP 接收缓冲区大小则经常默认为 40000 左右的一个值。

当设置 TCP 套接字接收缓冲区大小时,函数表用顺序很重要。原因是:TCP 的窗口大小规模是在建立连接是用 SYN 分节与对端交换得到的。对于客户端,SO_RECVBUF 选项必须在调用 connect 之前设置。对于服务器,必须在调用 listen 之前设置。给已连接套接字设置该选项没有任何作用。原因是:套接字缓冲区大小总是由新创建的已连接套接字从监听套接字继承而来。

TCP 套接字接收缓冲区的大小至少是相应连接的 MSS 值的四倍。典型的缓冲区大小默认值是 8192 字节或更大,典型的 MSS 值为 512 或者 1460,这些要求一般总能被满足。为了避免潜在的缓冲区空间浪费,TCP 套接字缓冲区大小还必须是相应连接的 MSS 值的偶数倍。

SO_RCVLOWAT & SO_SNDLOWAT

每个套接字都有一个接收低水位和一个发送低水位标记。由 IO 复用函数 select、poll、epoll 函数使用。这两个函数允许我们修改这两个低水位标记。

接收低水位标记是让 select 返回“可读”时套接字接收缓冲区中所需的数据量。对于 TCP 和 UDP 来说,其默认值为 1。

发送低水位标记是让 select 返回“可写”时套接字发送缓冲区中所需的可用空间。对于 TCP 而言,通常是 2048。UDP 也使用发送低水位标记,然而由于 UDP 套接字发送缓冲区中可用空间的字节数不改变,只要 UDP 套接字的发送缓冲区大小大于该套接字的低水位标记,该 UDP 套接字就总是可写。

SO_RECVTIMEO & SO_SNDTIMEO

这两个选项允许我们给套接字的接收和发送设置一个超时值。访问它们的 getsockopt 和 setsockopt 函数的参数是指向 timeval 结构体的指针,与 select 所用的参数相同。我们可以通过设置其时间为 0 来禁止超时。

接收超时影响 5 个输入函数:read、readv、recv、recvfrom和recvmsg。

发送超时影响 5 个输出函数:write、writev、send、sendto、sendmsg。

SO_REUSEADDR & SO_REUSEPORT

SO_REUSEADDR 套接字选项起到以下 4 个功用:
(1)SO_REUSEADDR 允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将该端口用作它们的本地端口的连接仍存在。

这个条件通常是这样碰到的:a) 启动一个监听服务器;b) 连接请求到达,派生一个子进程来处理这个客户;c) 监听服务器终止,但子进程继续为现有连接上的客户提供服务;d) 重启监听服务器。

默认情况下,当监听服务器在步骤 d 通过调用 socket、bind 和 listen 重新启动时,由于它试图捆绑一个现有连接上的端口,从而 bind 调用会失败。但是如果该服务器在 socket 和 bind 两个调用之间设置了 SO_REUSEADDR 套接字选项,那么 bind 将成功。所有 TCP 服务器都应该指定本套接字选项,以允许服务器在这种情形下被重新启动。

(2)SO_REUSEADDR 允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地 IP 地址即可。对于 TCP 而言,我们绝不可能启动捆绑相同 IP 地址和相同端口号的多个服务器,这完全是重复的捆绑。

(3)SO_REUSEADDR 允许单个进程捆绑同一端口到多个套接字上,只要每次捆绑指定的不同本地 IP 即可。

(4)SO_REUSEADDR 允许完全重复的捆绑:当一个 IP 地址和端口已经捆绑到某个套接字上时,如果传输协议支持,同样的 IP 和端口还可以捆绑到另一个套接字上。一般来说本特性仅支持 UDP 套接字。

本特性用于多播时,允许在同一个主机上同时运行同一个应用程序的多个副本。当一个 UDP 数据报需由这些重复捆绑套接字中的一个接收时,所用规则为:如果该数据报的目的地址是一个广播地址或多播地址,那就给每个匹配的套接字递送一个该数据报的副本;但是如果该数据报的目的地址是一个单播地址,那么它只递送给单个套接字。在单播数据报情况下,如果有多个套接字匹配该数据报,那么该选择由哪个套接字接收它取决于实现。

随着多播的支持,添加引入了 SO_REUSEPORT 这个套接字选项。它并未在 SO_REUSEAADDR 上重载所需多播语义(即允许完全重复的捆绑),而是给 SO_REUSEPORT 引入了以下语义:

(1)本选项允许完全重复的捆绑,不过只有在想要捆绑同一 IP 地址和端口的每个套接字都指定了本套接字选项才可以。

(2)如果被捆绑的IP地址是一个多播地址,那么 SO_REUSEADDR 和 SO_REUSEPORT 被认为是等效的。

本套接字选项并非所有的系统都支持。在不支持本选项但支持多播的系统上,可以改用 SO_REUSEADDR 以允许合理的完全重复的捆绑。

SO_TYPE

该选项返回套接字的类型,返回的整数值是一个诸如 SOCK_STREAM 或 SOCK_DGRAM 之类值。本选项通常由启动时继承了套接字的进程使用。

SO_USELOOPBACK

本选项仅用于路由域(AF_ROUTE)的套接字。对于这些套接字,默认总是为打开。当本选项开启时,相应套接字将接收在其上发送的任何数据报的一个副本。

注:这是唯一一个默认值为打开而不是关闭的 SO_XXX 二元套接字选项。

IPv4 套接字选项

这些套接字由 IPv4 处理,它们的级别为 IPPROTO_IP,也就是 getsockopt 和 setsockopt 函数的第二个参数。

IP_HDRINCL

如果该选项是给一个原始 IP 套接字设置的,那么必须为所有在该原始套接字上发送的数据报构造自己的 IP 首部。一般情况下,在原始套接字上发送的数据报其 IP 首部是由内核构造的,不过有些应用程序需要构造自己的 IP 首部以取代 IP 置于该首部中的某些字段。

本选项开启时,我们构造完整的 IP 首部,但下列情况例外:

  1. IP 总是计算并存储 IP 首部校验和。
  2. 如果我们将 IP 标识字段置为 0,内核将设置该字段。
  3. 如果源 IP 地址是 INADDR_ANY,IP 将把它设置为外出接口的主 IP 地址。
  4. 如果设置 IP 选项取决于实现。有些实现取出我们预先使用 IP_OPTIONS 套接字选项设置的任何 IP 选项,把它们添加到我们的构造的首部中,而其他实现则要求我们亲自在首部指定任何期望的IP选项。
  5. IP 首部中有些字段必须以主机字节序填写,有些字节序必须以网络字节序填写,具体取决于实现。

IP_OPTIONS

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

IP_RECVDSTADDR

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

IP_RECVIF

该选项导致所收到的 UDP 数据报的接收接口索引由 recvmsg 函数作为辅助数据返回。

IP_TOS

本套接字选项允许我们为 TCP、UDP 套接字设置 IP 首部中的服务类型字段。如果我们给本选项调用 getsockopt,那么用于放入外出 IP 数据报首部的 DSCP 和 ECN 字段中的 TOS 当前值将返回(默认为 0)。我们没有办法从接收到的 IP 数据报中得到该值。

IP_TTL

我们可以使用本选项设置或获取系统用在某个给定套接字发送的单播分组上的默认 TTL 值。TCP 和 UDO 套接字使用的默认值都是 64,对原始套接字使用的默认值则是 255。和 TOS 字段一样,调用 getsockopt 返回的是系统用于外出数据报的字段的默认值。我们没有办法从接收到的IP数据报中得到该值。

IPv6 套接字选项

这些套接字由 IPV6 处理,它们的级别为 IPPROTO_IPV6。

IPV6_CHECKSUM

该选项指定用户数据中校验和所处位置的字节偏移。

如果该值为负值,那么内核将:1. 给所有外出分组计算并存储校验和。2. 验证外来分组的校验和,丢弃所有校验和无效分组。本选项影响除 ICMPv6 原始套接字之外的所有 IPv6 原始套接字。

如果指定本选项的值为 -1,那么内核不会在相应的原始套接字上计算并存储外出分组的校验和,也不会验证外来分组的校验和。

IPV6_DONTFRAG

本选项将禁止为 UDP 套接字或原始套接字自动插入分片首部,外出分组中大小超过发送接口 MTU 的那些分组将被丢弃。发送分组的系统调用不会为此返回错误,因此已发送出去仍在途中的分组也可能因为超过路径 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 数据报的以下两条信息将由 recvmsg 作为辅助数据返回:目的 IPv6 地址和到达接口索引。

IPV6_RECVRTHDR

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

IPV6_RECVTCLASS

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

IPV6_UNICAST_HOPS

本 IPv6 选项类似于IPv4的 IP_TTL 套接字选项。设置本选项会给在相应套接字上发送的外出数据报指定默认跳限,获取本选项会返回内核用于相应套接字的跳限值。来自接收到的 IPv6 数据报中跳限字段的实际值通过使用 IPV6_RECVHOPLIMIT 套接字选项取得。

IPV6_USE_MIN_MTU

把本选项设置为1表明,路径 MTU 发现功能不必执行,为避免分片,分组就使用 IPv6 的最小 MTU 发送。把本选项设置为 0 表明,路径 MTU 发现功能对于所有目的地都得执行。把本选项设置为 -1 表明,路径 MTU 发现功能仅对单播目的地执行,对于多播目的地就使用最小 MTU。本选项默认值为 -1。

TPV6_V6ONLY

在一个 AF_INET6 套接字上开启本选项将限制它只执行 IPv6 通信。本选项默认为关闭,不过有些系统存在默认开启本选项的手段。

IPV6_XXX

大多数用于修改协议首部的 IPv6 选项假设:就 UDP 套接字而言,信息由 recvmsg 和 sendmsg 作为辅助数据在内核和应用进程之间传递;就 TCP 套接字而言,同样的信息改用 getsockopt 和 setsockopt 获取和设置。套接字选项和辅助数据的类型一致,并且访问套接字选项的缓冲区所含的信息和辅助数据中存放的信息也一致。

TCP 套接字选项

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

TCP_MAXSEG

本选项允许我们获取或设置 TCP 连接的最大分节大小(MSS)。返回值是我们的 TCP 可以发送给对端的最大数据量,它通常是由对端使用 SYN 分节通告的 MSS,除非我们的 TCP 选择使用一个比对端通告的 MSS 小些的值。如果该值在相应套接字的连接建立之前取得,那么返回值是未从对端收到 MSS 选项的情况下所用的默认值。还得注意的是,如果用上譬如说时间戳选项的话,那么实际用于连接中的最大分节大小可能小于本套接字选项的返回值,因为时间戳选项在每个分节中要占用 12 字节的 TCP 选项容量。
如果 TCP 支持路径 MTU 发现功能,那么它将发送的每个分节的最大数据量还可能在连接存活期内改变。如果到对端的路径发生变动,该值就会有所调整。

TCP_NODELAY

开启本选项将禁止 TCP 的 Nagle 算法。默认情况下该算法是启动的。Nagle 算法的目的在于减少广域网(WAN)上小分组的数目。该算法指出:如果某个给定连接上有待确认数据,那么原本应该作为用户写操作之响应的在该连接上立即发送相应小分组的行为就不会发生,直到现有数据被确认为止。这里“小”分组的定义就是小于 MSS 的任何分组。TCP 总是尽可能地发送最大大小的分组,Nagle 算法的目的在于防止一个连接在任何时刻有多个小分组待确认。
Rlogin 和 Telnet 的客户端是两个常见的小分组产生进程,它们通常把每次击键作为单个分组发送。在快速的局域网(LAN)上,我们通常不会注意到 Nagle 算法对这些客户进程的影响,因为小分组所需的确认时间一般也就几毫秒,远远小于我们相继键入两个字符的间隔时间。然而在广域网上,小分组所需的确认时间可能长达一秒,我们就会注意到字符回显的延迟,而且该延迟往往被 Nagle 算法进一步放大。

fcntl 函数

表示 file control(文件控制),fcntl 函数可以执行各种描述符控制操作。

如下汇总了 fcntl、ioctl、和路由套接字执行的不同操作。

sockopt套接字选项操作_第1张图片

前六个可由任何进程应用于套接字,接着两个接口操作比较少见,但也是通用的,后面两个操作由诸如 ifconfig 和 route 之类管理程序执行。

最后一列指出 POSIX 规定的首选方法。可以看到前四种都推荐 fcntl 函数。POSIX 还提供了 sockatmark 函数作为测试是否处于带外标志的首选方法。

fcntl 函数提供了与网络编程相关得特性:

  1. 非阻塞式 IO。通过使用 F_SETFL 命令设置 O_NONBLOCK 文件状态标志,可以将一个套接字设置为非阻塞型。
  2. 信号驱动式 IO。通过使用 F_SETFL 命令设置 O_ASYNC 文件状态标志,可以将一个套接字设置成一旦其状态发生变化,内核就产生一个 SIGIO 信号。
  3. F_SETOWN 命令允许我们指定用于接收 SIGIO 和 SIGURG 信号的套接字属主(套接字的进程组 ID,因为存放该ID的变量是 socket 结构的 so_pgid 成员)。SIGURG 信号是在新的带外数据到达套接字时产生的。F_GETOWN 命令返回套接字当前属主。

fcntl函数定义

#include 
int fcntl(int fd, int cmd, ...);

每种描述符都有一种由 F_GETFL 命令获取文件标志,由 F_SETFL 命令设置文件标志。影响套接字描述符的两个标志是:

O_NONBLOCK:非阻塞式 IO

O_ASYNC:信号驱动式 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");

如果直接使用如下代码:

if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0)
    err_sys("F_SETFL error");

虽然设置了非阻塞标志,但同时也清除了其他文件状态标志。

正确的方式为:先取得描述符当前的标志,与新的标志逻辑或后再设置标志。

如果要关闭非阻塞标志,可以使用如下方式:

flags &= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0)
    err_sys("F_SETFL error");

信号 SIGIO 和 SIGURG 与其他信号的不同之处在于,这两个信号仅在已使用 F_SETOWN 命令给相关套接字指派了属性后才会产生。

F_SETOWN 命令的整数类型 arg 参数既可以是一个正整数,指出接收信号的进程 ID,也可以是一个负整数,其绝对值指出接收信号的进程组 ID。

F_GETOWN 命令把套接字属主作为 fcntl 函数的返回值返回,它既可以是进程 ID,也可以是进程组 ID(一个除 -1 以外的负数)。

指定接收信号的套接字属主为一个进程或一个进程组的差别在于:前者尽导致单个进程接收信号,后者导致整个进程组中的所有进程接收信号。

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

你可能感兴趣的:(Linux环境编程)