基本TCP客户端/服务器程序的套接字函数
listen函数仅由TCP服务调用,它做两件事
1.当socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将调用connect连接的客户套接字。listen函数把一个未连接的套接字转换成一个被动套接字,指示内核应该接受指向该套接字的连接请求。如下图所示,调用listen导致套接字从CLOSED状态转换到LISTEN状态。
2.listen函数的第二个参数规定了内核应为相应套接字排队的最大连接个数。
#include<sys/socket.h>
int listen(int sockfd, int backlog);
本函数通常应该在调用socket和bind之后,并在调用accept之前被调用。
为了理解其中的backlog参数,我们必须认识到内核为任何一个给定的监听套接字维护两个队列:
(1)未完成连接队列,每个这样的SYN分节对应其中的一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RCVD状态,如上图。
(2)已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
下图描绘了监听套接字的这两个队列
每当在未完成连接队列中创建一项时,来自监听套接字的参数就复制到即将建立的连接中。连接的创建机制是完全自动的,无需服务器进程插手。下图展示了两个队列建立连接时所交换的分组:
当来自客户的SYN到达时,TCP在未完成连接队列中创建一个新项,然后响应以三路握手的第二个分节:服务器的SYN响应,其中捎带对客户SYN的ACK。这一项一直保留在未完成连接队列中,直到三路握手的第三个节点到达或者该项超时为止。如果三次握手正常完成,该项就从未完成连接队列移到已完成连接队列的队尾。当进程调用accept时,已完成连接队列中的对头将返回给进程,或者如果队列为空,那么进程将被投入睡眠,直到TCP在该队列中放入一项才唤醒它。
关于这两个队列的处理,以下几点需要考虑:
- listen函数的backlog参数曾被规定为这两个队列总和的最大值
- 源自Berkeley的实现给backlog增设了一个模糊因子:把它乘以1.5得到的未处理队列最大长度。举例来说,通常指定为5的backlog值实际上允许最多有8项在排队。
- 不要把backlog设为0,因为不同的实现对此有不同的解释(下方图4-10)。如果你不想让任何客户连接到你的监听套接字上,那就关掉该坚挺套接字。
- 在三路握手正常完成的前提下,未完成连接队列中的任何一项在其中的存留时间就是一个RTT,而RTT的值取决于特定的客户与服务器。
- 历来沿用的样例代码总是给出值为5的backlog,因为这是BSD4.2支持的最大值。这个值在20世纪八十年代是足够的,当时繁忙的服务器一天也就处理几百个连接。然而随着万维网的发展,繁忙的服务器一天要处理几百万个连接,这个偏小的值就根本不够了。繁忙的HTTP服务器必须指定一个较大的backlog值,而且较新的内核必须支持较大的backlog值
当前许多系统允许管理员修改backlog的最大值
- 问题是既然backlog的值为5不合适,那么该指定多少呢?这个问题不好回答。当今的HTTP服务器指定了一个较大值,但是如果这个指定值在源码中是一个常量,那么增长其大小就要重新编译服务器程序。另一个方法就是设定一个默认值,允许通过命令行选项或环境变量覆写该默认值。指定一个比内核能够支持的值更大的backlog也是可以接受的,因为内核应该悄然把所指定的偏大值截成自身支持的最大值,而不返回错误。我们通过修改listen的包裹函数就能提供解决问题的一个简单办法,如下所示:我们允许环境变量LISTENQ覆写有调用者指定的值
int Listen(int sockfd, int backlog){
char *ptr;
if( (ptr = getenv("LISTENQ")) != NULL)
backlog=atoi(ptr);
if(listen(fd, backlog)<0)
err_sys("listen error");
}
atoi可看这篇文章。
下图是7种操作系统对backlog的意义的解释的不同