在socket的基础API中,有一个函数是listen,有一个函数是accept
#include <sys/socket.h>
int listen(int fd, int backlog);
参数backlog表示内核监听队列的最大长度
int accept(int fd, struct sockaddr *addr, socklen_t *addrlen);
这里,调用该函数,系统为该socket会创建一个监听队列,监听队列的长度由输入的backlog控制。也就是说,如果此时有客户端需要连接进来,那么服务器将会生成一个对应的socket放在监听队列里面,然后等待服务器使用accept提取监听队列中的处于establish状态的socket出来。
这里就有几个疑问点,
- backlog是否完全对应着监听队列的长度?超过了这个长度,多余的socket,内核怎么处理?是丢弃?还是别的?
- backlog范围内的socket的状态,肯定是establish吗?如果这个时候客户端直接挂掉,服务器还会保持establish吗?
- 服务器使用accept连接的,一定是处于establish状态的socket吗?这个和第二个问题类似。
下面先讨论第一个问题:
static bool stop = false;
static void signal_deal(int sig)
{
stop = true;
}
int main(int argc, char* argv[])
{
signal(SIGTERM, signal_deal);
int fd = ::socket(AF_INET, SOCK_STREAM, 0);
if (fd == -1)
exit(0);
struct sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(5050);
if (1 != inet_pton(AF_INET, "192.168.196.130", &addr.sin_addr))
exit(0);
if (0 != ::bind(fd, (const sockaddr*)&addr, sizeof(addr)))
exit(0);
if (0 != listen(fd, 5))
exit(0);
while (!stop)
{
sleep(1);
}
close(fd);
return 0;
}
我在虚拟机上,执行这个程序。然后在window上使用telnet来连接这个服务器。然后使用netstat -nt来查看这个连接状况。
先说结论:在window上,telnet的次数多的超过了6次后,后面的socket处于SYN_SEND状态。在虚拟机上,telnet超过6次后,后面的socket不做任何处理。也就是说,超过backlog+1个数的socket都不会进行三次握手的完整过程。他们都在等待服务器调用accept从监听队列中提取socket出来后,等待补上。
配合TCP状态转换图,可以看到,后面的socket都处于SYN_SEND状态,正等待着三次握手的进一步完成达到ESTABLISHED状态
下面考虑第二个问题?我先telnet一次,然后关闭,然后查看虚拟机服务器上的状态.
当第一个telnet 192.168.196.130 5050发出后,调用netstat可以查看到有一个socket在监听队列中,处于ESTABLISHED状态。接下来直接强制关闭掉这个客户端。查看结果。
这个监听队列,会一直保持着socket的当前状态,即使处于CLOSE_WAIT,掉线了,也不会将它移除出去。
那么就很好解释第三个问题了,使用accept是有可能从listen监听队列中提取到属于close_wait状态的,不一定是ETABLISHED的。
了解基础api的时候,应该配合TCP基本原理进行学习,这样才能深入了解这些基础函数做了什么,内核配合这些函数干了什么事?出现了问题的时候才好排查。