您的位置:首页 > 其它

套接字选项SO_LINGER, SO_KEEPALIVE等

2015-04-21 17:23 197 查看
int getsockopt(int sockfd, int level, intoptname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, intoptname, const void *optval, socklen_t optlen);
//获取与设置套接字选项的函数


通用套接字选项:

(一)SO_RCVBUF和SO_SNDBUF

每个套接字都有一个发送缓冲区和一个接收缓冲区。对于TCP来说,套接字接收缓冲区中的可用空间的大小限制了TCP通告对端的窗口大小。TCP接收缓冲区不可能溢出,因为如果对端无视窗口限制大小发送超过限制大小的数据,本端TCP将直接丢弃。对于UDP来说,当接收的数据报装不进接收缓冲区时候,该数据就被丢弃:较快的发送端可以很容易的淹没较慢的接收端,导致UDP接收端丢弃数据报(甚至较快的发送端可以淹没本机的网络接口,导致数据报被本机丢弃)

当设置TCP套接字接收缓冲区的大小时,函数调用顺序重要。因为TCP的窗口规模选项是在建立连接时用SYN分节与对端互换得到的。因此, 对于客户,函数必须在connect之前调用; 对于服务器,函数必须在listen之前给监听套接字设置(为什么是设置监听套接字? 因为客户套接字是在三路握手完成后由accept取出,此时设置已再无影响。然而,新创建的套接字属性可以从监听套接字那里继承得到)

当我们用setsockopt设置了TCP的接收缓冲区和发送缓冲区的大小时,系统都会将其值加倍,并且不得小于某个最小值

(二)SO_REUSEADDR和SO_REUSEPORT

SO_REUSEADDR选项能起到以下4个作用:

1、它允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作他们的本地端口的连接仍存在(由以下种情况导致)

a)启动一个监听服务器

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

c)监听服务终止,但子进程仍然为客户服务(此刻服务器进程处于TIME_WAIT状态,因为其主动关闭连接)

d)重启监听服务器

默认情况下,服务器通过socket、bind和listen重新启动时,由于它试图捆绑一个现有连接(即正由早先派生的那个子进程处理着连接)的端口,bind会失败。但是如果在socket和bind之间调用SO_REUSEADDR选项,那么bind会成功

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

比如本地主机的主IP地址(这是啥)为A,不过它有两个别名(这又是啥)B和C,在其上启动三个服务器。第一个HTTP服务器指定为 INADDR_ANY 和端口号80, 第二个HTTP调用SO_REUSEADDR指定为 B 和端口号80, 第三个HTTP也通过SO_REUSEADDR 指定为C和80。目的为B端口为80的外来TCP请求将被发送至第二个服务器,目的为C端口为80的将发给第三个服务器,其余都将发送给第一个服务器。(当我使用tcp服务器进行尝试时, 却发生各种问题. 网上说这个适用于UDP,
而非tcp)

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

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

以下为使用SO_REUSEADDR建议:

1、在所有TCP服务器中,在调用bind之前设置SO_REUSEADDR选项

2、当编写一个可在同一时刻在同一主机上运行多次的多播应用程序时,设置SO_REUSEADDR选项,并将所参加多播组的地址作为本地IP地址捆绑


(三)SO_LINGER

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

但是,SO_LINGER选项可以帮助我们改变这个默认设置。本选项要求在用户进程与内核间传递如下结构:

struct linger{

int l_onoff; /* 0=off , nonzero=on 选项开启/关闭 */

int l_linger; /* linger time, POSIX specifies units as seconds 延滞时间*/

};

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

(1)、如果l_onoff为0,那么关闭选项。l_linger被忽略,之前的 默认设置生效

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

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

现在我们来看看close到底是什么时候返回的:

下图是默认情况下,即close立即返回,但是如果有数据残留在套接字发送缓冲区中,系统将尝试把数据发送给对端



这里我们假设在数据到达时,服务器正处于忙状态,即新到的数据会被存入接受缓存区,类似的,下一个分节即客户发来的FIN也被放入接收缓存区(这里不管对方是如何记录已接收到FIN的)。按默认情况,客户端close立即返回,如图所示,客户的close可能在服务器读套接字接收缓存区中的数据之前就返回。这样一来,假如服务器在读取缓存区数据之前就崩溃了,那么客户端就永远不会知道了。

现在我们调用SO_LINGER选项,且设定一个正的延滞时间(不能太小),这样客户端close就会在收到数据和FIN被确认的信息后才返回,如下图:



尽管如此,我们仍然有默认处理中出现过的问题存在: 在服务器应用进程读缓存区中排队的数据和FIN前,服务器崩溃但是客户端永远不知道

更糟糕的是延滞时间如果太小,会发生如下情况:



在数据发送完并被确认前延滞时间到的话,close将返回 EWOULDBLOCK错误,且套接字发送缓冲区中的任何残留数据都被丢弃

这里有一个基本原则:设置SO_LINGER选项后,close的成功返回只是告诉我们先前发送的数据和FIN已由对端TCP确认,而不能告诉我们对端应用程序是否已成功接收数据。但是如果我们不设置这个选项,我们连TCP是否确认了数据都不知道。

让客户知道服务器应用已经读取数据的一个办法是调用shutdown函数,而不是用close,并等待对端的close,如下:



以下我们对几种close返回做一个总结:

1、 close立即返回,根本不等待

2、 close拖延到接收到对端对FIN的ACK才返回

3、 后跟一个read调用的shutdown一直等到接收对端的FIN才返回

另一种可以让客户知道服务器应用已经接受数据的方法是应用级ACK。

客户端在发送完数据后调用read读取一个字节的数据:

char ack;
write();     //客户端写数据给服务器
read();       //准备接收一个结束数据


而服务器在接受完数据后发送一个字节的应用级ACK

nbytes = read();         //接收完客户端的数据
write();                         //向客户端写一个应用级ACK


具体过程如下:



最后给这个套接字选项做一个总结:



(四)SO_KEEPALIVE

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

1、对端响应ACK,应用进程也不会得到任何通知(因为一切正常),接下来2小时若仍无动静,TCP将再次发送分节

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

3、对端没有任何响应,TCP将另外发送8个探测分节,两两相隔75秒,在发出第一个探测分节后11分15秒内没有得到任何响应则放弃,待处理错误为ETIMEOUT,套接字本身被关闭

然而如果收到一个ICMP错误作为某个探测分节的响应,那就返回相应错误,套接字被关闭。(常见的ICMP错误为“主机不可达”)

本选项的公用是检测对端主机是否崩溃或变得不可达。如果对端进程崩溃,它的TCP将跨连接发送一个FIN,这可以通过select很容易检测到。同时也要认识到,即使对任何探测无响应,我们也不能认定主机崩溃,因而TCP可能会终结一个有效的连接。某个中间路由崩溃一段时间也是有可能的

本选项一般由服务器使用。(客户端也可)



(五)SO_ERROR

当一个套接字上发生错误时,伯克利的内核中的协议模块将该套接字的名为so_error的变量设置为标准的Unix EXXX值中的一个,我们称其为该套接字的待处理错误(pending error)。内核能通过下面两种方式之一立即通知进程这个错误:

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

2、如果进程使用信号驱动I/O模型,那么就会给进程或是进程组产生一个SIGIO信号。

进程然后就可以通过访问SO_ERROR套接字选项获取 so_error值。由getsockopt返回的整数值就是该套接字的待处理错误。so_error随后由内核复位为0.

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

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

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

fcntl函数:(file control)

设置套接字为非阻塞式I/O F_SETFL, O_NONBLOCK

设置套接字为信号驱动式I/O F_SETFL, O_ASYNC

设置套接字属主 F_SETOWN

获取套接字属主 F_GETOWN

fcntl提供的网络编程特性:

1、将套接字设置为非阻塞式I/O

2、将套接字设置为信号驱动I/O

3、F_SETOWN可允许我们指定用于接收SIGIO,SIGURG信号的套接字属主(进程ID或进程组ID)

SIGIO是套接字被设置为信号驱动后产生的

SIGURG是在新的带外数据到达套接字时候产生的

一般使用fcntl的方式:

int flags;

if((flags=fcntl(fd,F_GETFL,0)) < 0)
errquit(...);
flags |= O_NONBLOCK;
if(fcntl(fd,F_SETFL,flags) < 0)
errquit(...);
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: