网站建设预期达到的效果线上广告推广平台
W...Y的主页 😊
代码仓库管理💕
前言:之前我们已经学习了TCP传输协议,而无论是TCP还是UDP都是使用socket套接字进行网络传输的,而TCP的socket是比UDP复杂的,当时我们学习TCPsocket编程时使用listen函数进行监听,今天我们就来说说listen函数第二个参数的含义,并且打通socket与内核之间的关系!!
目录
理解 listen 的第二个参数
理解全连接队列
在内核中理解全连接队列与套接字
理解 listen 的第二个参数
基于刚才封装的 TcpSocket 实现以下测试代码。对于服务器, listen 的第二个参数设置为 1, 并且不调用 accept。不调用accept就无法建立连接,导致这些连接全部会在全连接队列中(全连接队列是什么我们会在后面讲述)。
test_server.cc
#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_server [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Bind(argv[1], atoi(argv[2]));
if (!ret) {
return 1;
}
ret = sock.Listen(2);
if (!ret) {
return 1;
}
// 客户端不进行 accept
while (1) {
sleep(1);
}
return 0;
}
test_client.cc
#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_client [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Connect(argv[1], atoi(argv[2]));
if (ret) {
printf("connect ok\n");
} else {
printf("connect failed\n");
}
while (1) {
sleep(1);
}
return 0;
}
此时启动 3 个客户端同时连接服务器, 用 netstat 查看服务器状态, 一切正常,但是启动第四个客户端时, 发现服务器对于第四个连接的状态存在问题了。
tcp 3 0 0.0.0.0:9090 0.0.0.0:*
LISTEN 9084/./test_server
tcp 0 0 127.0.0.1:9090 127.0.0.1:48178
SYN_RECV -
tcp 0 0 127.0.0.1:9090 127.0.0.1:48176
ESTABLISHED -
tcp 0 0 127.0.0.1:48178 127.0.0.1:9090
ESTABLISHED 9140/./test_client
tcp 0 0 127.0.0.1:48174 127.0.0.1:9090
ESTABLISHED 9087/./test_client
tcp 0 0 127.0.0.1:48176 127.0.0.1:9090
ESTABLISHED 9088/./test_client
tcp 0 0 127.0.0.1:48172 127.0.0.1:9090
ESTABLISHED 9086/./test_client
tcp 0 0 127.0.0.1:9090 127.0.0.1:48174
ESTABLISHED -
tcp 0 0 127.0.0.1:9090 127.0.0.1:48172
ESTABLISHED -
前三个连接状态都是established,而第四个连接的客户端的状态是syn_sent(链接同步发送状态)是没有连接成功的,三次握手都没有成功。往后所以的客户端想要连接都会是syn_sent状态。
所以我们得出结论:在服务器来不及accept时,底层的TCP listen socket允许用户进行三次握手建立成功连接,但是不能建立太多,最多能建立backlog + 1个连接。 而这个backlog就是listen函数的第二个参数。
我们将这些建立好连接的链接会放在一个队列来维护,这个队列就是全连接队列。
客户端状态正常, 但是服务器端出现了 SYN_RECV 状态, 而不是 ESTABLISHED 状态这是因为, Linux 内核协议栈为一个 tcp 连接管理使用两个队列:
1. 半链接队列(用来保存处于 SYN_SENT 和 SYN_RECV 状态的请求)
2. 全连接队列(accpetd 队列)(用来保存处于 established 状态,但是应用层没有调用 accept 取走的请求)
而全连接队列的长度会受到 listen 第二个参数的影响.
全连接队列满了的时候, 就无法继续让当前连接的状态进入 established 状态了.
这个队列的长度通过上述实验可知, 是 listen 的第二个参数 + 1.
理解全连接队列
连接本质是内核中的一种数据结构,而我们使用socket套接字中的accept就是为了将已经建立好的连接拿上来进行使用。accpet函数在建立连接的同时,还能获取客户端的地址和端口信息。
我们不能将backlog中参数和服务器只能处理backlog+1个连接所混淆,服务器可以处理多个请求,这些连接就会被拿到上层不再全连接队列中,而全连接队列中的连接是已经建立好三次握手连接的却来不及接收的连接!!!
而我们不能不要这个全连接队列,因为这个全连接队列就相当于一个缓冲区,这个模式就是生产消费者模型,如果服务端突然空闲那就可以连接全连接队列中的连接,提高了整个服务端的吞吐量和用户的体验。如果没有全连接队列就会增加服务端的闲置率!
我们也不能设置太长,如果设置太长那么晚来的客户端就在队列的尾部也会等很长时间,许多用户等不住就会关掉连接,但是还没有达到最终目的浪费了用户时间并且浪费服务器维护队列空间。
在内核中理解全连接队列与套接字
在Linux内核中,task_struct结构体是描述进程的核心数据结构,它包含了控制和管理进程所必需的全部信息。 而struct file_struct结构体是用啦维护文件描述符的一个数据结构,他记录了一个进程打开的所有文件消息,file_struct中有一个fd数组用来记录文件描述符的。
在Linux内核中,struct file
结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file
实例。
所以使用listen_socket打开一个文件,就有一个文件描述符指向打开的struck file中,这时我们所理解的。
而我们使用socket创建套接字时,内核就为我们创建了一个struct socket结构体,struct file 与struct socket用*private_data指针关联,上图就是我们结构体的内容。struct socket结构体就是我们网络的入口。
但真实的情况是创建一个TCP连接,而真正的应该是一个tcp_sock的结构体。而tcp是面向连接的,里面有一个字段也是一个结构体是struct inet_connection_sock,他说tcp_sock中第一个字段,这个结构体包含了tcp链接的相关消息(超时时间……),里面有个字段是struct request_sock_queue icsk_accept_queue,这个就是全连接队列。而struct inet_connection_sock中第一个字段又是一个结构体struct inet_sock(里面有源端口、目的端口、源IP、目的IP……)而struct inet_sock里面第一个字段又是一个结构体struct sock
而我们的struct socket结构体中有一个字段struct sock* sk指针,这个指针就可以指向struct tcp_sock结构体并且使用强转就可以对tcp_sock里面的所有字段进行访问。这就是C风格的多态。
而我们的UDP与TCP大相径庭,只不过UDP是无连接的,所以在udp_sock中没有struct inet_connection_sock字段,剩下都是一样的。
我们的struct socket中有一个字段struct proto_ops结构体指针,里面存放了UDP与TCP的函数方法集。所以我们的struct socket是基类,tcp_sock与udp_sock都是子类。我们把struct socket叫做BSD socket——通用socket接口,把子类叫做inet_sock
上述是我们讲述的创建一个listen套接字,而如何获取一个连接呢(accept)?
当我们进行三次握手成功后就会建立tcp_sock结构体,这个结构体会被连入全连接队列中,后面我们进行accept时就会创建struct file 然后再创建struct socket,之后将struct file 与struct socket用*private_data指针关联起来并在文件描述符表中申请一个fd(这些操作都是由一个函数(映射)完成的(sock_map_fd)),然后就会再全连接队列中获取tcp_sock结构体进行关联,直接用struct socket中的sock指针指向tcp_sock结构体,这样我们的一个普通套接字也有上述对应的结构了。
这就是在内核中socket、UDP、TCP、全连接队列、文件所对应的关系,下面我们用一张非常简单的图来表示对应关系:
详细可以参考源码,此处不展示!!!