首页 > 代码库 > apue和unp的学习之旅09——套接字选项

apue和unp的学习之旅09——套接字选项

//-----------------------------------1.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);

 

其中sockfd必须是打开的套接字描述符,level(级别)指定系统中解释选项的代码或为通用套接字代码,或为某个特定于协议的代码(例如ipv4,ipv6,tcp或sctp)。

optval是指向某个变量(*optval)的指针,setsockopt从*optval中取得选项待设置的新值,getsockopt则把已经获得的选项当前值保存放到optval中,*optval的大小由最后一个参数指定,它对于setsockopt是一个值参数,对于getsockopt是一个值结果参数。

套接字选项粗粗地分为两大基本类型,一是启用或禁止某个特性的二元选项(称为标志选项),二是取得并返回我们可以设置或检查的特定值的选项(称为值选项)。

当为标志选项时,*optval是一个整数,*optval中返回的值为0表示相应选项被启用,不为0表示相应选项被启用,类似地,setsockopt函数需要一个不为0的*optval值来启用选项,一个为0的*optval值来禁止选项。

当为值选项时,那么该选项是用于在用户进程与系统之间传递所指定数据类型的值。

 

//------------------------------------2.套接TCP状态-------------------------------------------

针对套接字的状态,什么时候设置或获取选项有时序上的考虑,,例如,下面的套接字选项是由已连接套接字从监听字继承来的,SO_DEBUG,SO_DONTROUT,SO_KEEPALIVE,SO_LINGER,SO_OOBINLINE,SO_RCVBUF,SO_RCVLOWAT,SO_SNDBUF,SO_SNDLOWAT,TCP_MAXSEG和TCP_NODELAY.这对TCP是很重要的,因为accpet一直要到TCP层完成三路握手后才会给服务器返回已连接套接字。如果我们想在三路握手完成时确保这些套接字选项中的某一个是给已连接套接字设置的,那么我们必须先给监听套接字设置该选项。

 

//-----------------------------------3.一些通用套接字选项-------------------------------------

因为套接字选项有很多,不可能每个都记得下,所以只能选取一些常用且重要的来记住,其他的就当字典似的需要时来查。

SO_DONTROUTE套接字选项:

本选项规定外出的分组将绕过底层协议的正常路由机制,举例子来说,在ipv4情况下外出分组将被定向到适当的本地接口,也就是由其目的地址的网络和子网部分确定的本地接口。如果这样的本地接口无法由目的地址确定(譬如说目的地主机不在一个点对点链路的另一端,也不在一个共享的网络上),那么返回ENETUNREACH错误。路由守护进程(routed和gated)经常使用本选项来绕过路由表(路由表不正确的情况下),以强制将分组从特定接口送出。

 

SO_KEEPALIVE套接字选项:

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

1).对端以期望的ACK响应,应用进程得不到通知,因为一切正常,在又经过仍无动静的2小时后,TCP将发出另外也给探测分节。

2).对端以RST响应,它告知本端TCP:对端已崩溃且已经重新启动,该套接字的待处理错误被置ECONNRESET,套接字本身则被关闭。

3).对端对保持存活探测分节没有任何响应,源自Berkeley的TCP将另外发送8个探测分节,两两相隔75秒,试图得到一个响应,TCP在发出第一个探测分节后11分15秒内若没有得到任何响应则放弃。该套接字的待处理错误就被置为ETIMEOUT,套接字本身则被关闭。然而如果该套接字收到一个ICMP错误作为某个探测分节的响应,那就返回响应的错误,套接字本身也呗关闭,这种情形下待处理的错误即是EHOSTUNREACH,说明对端主机可能并没有崩溃,只是不可达,原因可能是网络发生故障,或者是对端主机已经崩溃,而最后一跳的路由器也已经检测到它的崩溃。

下图对一个TCP连接的另一端发生某些事件时我们可以采用的各种检测办法做了汇总:

 

SO_LINGER套接字选项:

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

SO_LINGER套接字选项使得我们可以改变这个默认设置,本选项要求在用户进程与内核间传递如下结构,它在头文件<sys/socket.h>中定义:

struct  linger {

     int  l_onoff;

     int  l_linger;

};

对setsockopt的调用将根据其中2个结构成员的值形成下列3种情形之一:

1).如果l_onoff为0,那么关闭本选项,l_linger的值也被忽略,先前讨论的TCP默认设置生效,级close立即返回。

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

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


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



SO_RCVBUF和SO_SNDBUF选项:

    每个套接字都有一个发送缓冲区和一个接收缓冲区,对于TCP来说,套接字接收缓冲区中可用空间的大小限定了TCP通告对端的窗口大小。TCP套接字接收缓冲区不可能溢出,因为不允许对端发出超过本端所通告窗口大小的数据,这就是TCP的流量控制,如果对端无视窗口大小而发出了超过本端所通告窗口大小的数据,本端TCP将丢弃它们。然而对于UDP来说,当接收到的数据报装不进套接字接收缓冲区时,该数据报就被丢弃。

    套接字缓冲区的大小总是由新创建的已连接套接字从监听套接字继承而来,意味着,对于客户,SO_RECVBUF选项必须在调用connect之前设置;对于服务器,必须在调用listen之前给监听套接字设置。

    TCP套接字缓冲区的大小应该至少是相应连接的MSS值的4倍,这一点的依据是TCP快速恢复算法的工作机制,TCP发送端使用3个重复的确认来检测某个分节是否丢失,发现某个分节丢失后,接收端将给新收到的每个分节发送一个重复的确认,如果窗口大小不足以存放4个这样的分节,那就不可能连发三个重复的确认,从而无法激活快速恢复算法。

    为避免潜在缓冲区空间的浪费,TCP套接字缓冲区大小还必须是相应连接的MSS值的偶数倍,有些实现题应用进程处理这个细节问题,在连接建立之后,向上舍入套接字缓冲区大小,使用默认的4.4BSD大小8192举例来说,假设以太网的MSS值为1460,在连接建立时收发两个套接字接收缓冲区的大小将被向上舍入成8760(6*1460),这个要求并非必须,只不过套接字缓冲区中的MSS整数倍大小以外的空间不会被使用

 

SO_RCVLOWAT和SO_SNDLOWAT选项:

    每个套接字还有一个接收低水位标记和一个发送低水位标记,它们由select函数使用,这两个套接字选项允许我们修改这2个低水位标记。

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


SO_RCVTIMEO和SO_SNDTIMEO选项:

    这2个选项允许我们给套接字的接收和发送设置一个超时值,注意访问它们的getsockopt和setsockopt函数的参数是指向timeval结构的指针,与select所用参数相同,与select所用参数相同。我们可通过设置其0s和0us来禁止超时,默认都是静禁止的。


SO_REUSEADDR和SO_REUSEPORT选项:

1).SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知端口,即使以前建立的将该端口用作它们的本地端口的连接仍然存在

a)启动一个监听服务器

b)连接请求到达,派生一个子进程来处理这个客户

c)监听服务器终止,但是子进程继续为现有连接上的客户提供服务

d)重启监听服务器

如果服务器在socket 和 bind两个调用之间设置了SO_REUSEADDR套接字选项,那么bind将成功。


2).SO_REUSEADDR允许在同一个端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的IP地址即可。


//------------------------------------4.TCP的两个套接字选项----------------------------------------

TCP_MAXSEG套接字选项:

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

    如果TCP支持路径MTU发现功能,那么它将发送的每个分节的最大数据量还可能在连接存活期内改变,如果到对端的路径发生变动,该值就会有所调准。


TCP_NODELAY套接字选项:

    开启本选项将禁止TCP的Nagle算法,默认情况下该算法是启动的。   

    Nagle算法的目的在于减少广域网(wlan)上小分组的数目,该算法指出:如果某个给定连接上有待确认的数据(outstanding data),那么原本应该作为用户写操作之响应的在该连接上立即发送相应小分组的行为就不会发生,直到现有数据被确认为止。这里小分组的定义就是小于MSS的任何分组,TCP总是尽可能地发送最大大小的分组,Nagle算法的目的在于防止一个连接在任何时刻有多个小分组待确认。

    假设Nagle算法是禁止的,我们将得到如下图的12个分组。

    

但是如果Nagle算法是开启的(默认情形),将得到如下的8个分组

           

(假设用户每250ms输入一个字符,上图忽略了客户对服务器的ACK)第一个字符独自作为一个分组,然而下2个字符没有立即发送,因为该连接上有一个小分组待确认。在时刻600ms处收到对端对第一个分组的ACK后(该ACK由第一个字符的回显捎带),这2个字符才发送。在该分组在时刻1200ms处被确认之前,没有其他小分组被发送,Nagle始终保持一个连接在任何时刻有多个有多个分组待确认。(待确认数据是指未决数据,也就是已发送,但还在等待对端确认的数据)。


Nagle算法常常与另外一个TCP算法联合使用:ACK延滞算法,该算法使得TCP在接收到数据后不立即发送一个ACK,而是等待一小段时间(典型值为50~200ms),然后才发送一个ACK。TCP期待在这一小段时间内自身有数据发送回对端,被延滞的ACK就可以由这些数据捎带,从而省下一个TCP分节。这种情形对于Rlogin和Telnet客户来说通常可行,因为它们的服务器一般都回显客户发送来的每个字符,这样对客户端字符的ACK完全可以在服务器对该字符回显中捎带返回。然而对于其服务器不在相反方向产生数据以便携带ACK的客户来说,ACK延滞算法存在问题,客户会感觉到明显的延迟,因为客户TCP要等到服务器的ACK延滞定时器超时才继续给服务器发送数据。这些客户需要一种禁止Nagle算法的方法,TCP_NODELY选项就能起到这个作用。


//----------------------------------------5.fcntl函数------------------------------------------

#include <fcntl.h>

int fcntl(int fd, int cmd, .../* int arg */);


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

1).非阻塞式I/O:通过使用F_SETFL命令设置O_NONBLOCK文件状态标志,我们可以把一个套接字设置为非阻塞型。

2).信号驱动式I/O:通过使用F_SETFL命令设置O_ASYNC文件状态标志,我们可以把一个套接字设置成一旦其状态发生变化,内核就产生一个SIGIO信号。

3).F_SETOWN命令允许我们指定用于接收SIGIO和SIGURG信号的套接字属主(进程id或进程组id),其中SIGIO信号是套接字被设置为信号驱动式I/O型产生的,SIGURG信号是在新的带外数据到达套接字时候产生的,F_GETOWN命令返回套接字的当前属主。不得不提的是,信号SIGIO和信号SIGURG与其他信号的不同之处在于:这2个信号仅仅在已使用F_SETOWN命令的整数类型arg参数既可以是一个整数,指出接收信号的进程id,也可以是也给负整数,其绝对值指出接收信号的进程组id。F_GETOWN命令把套接字属主作为fcntl函数的返回值返回,它既可以是进程ID(一个正的返回值)也可以是进程组ID(一个除-1外的负值),指定接收信号的套接字属主为一个进程或一个进程组的差别在于:前者仅仅导致单个进程接收信号,而后者则导致整个进程组中的所有进程接收。使用socket函数新创建的套接字并没有属主,然而如果一个套接字是从一个监听套接字创建来的,那么该套接字属主讲由已连接套接字从监听套接字继承而来。