# Linux accept 原理
当我们对Server FD 初始化 、绑定端口、开启监听后,客户端便可以通过 connect 函数连接到服务端,经历过TCP 三次握手后,将会把客户端 socket 放入 accept 接收队列,本节将详细介绍 accept 接收客户端连接的整个过程。
int serverFD = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr; // 端口绑定信息
addr.sin_family = AF_INET; // 指定协议簇
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(8080); // 将端口信息转为网络字节序(大端序)
bind(serverFD, (sockaddr*)&addr, sizeof(addr)); // 开始绑定
listen(serverFD,100); // 绑定后开启监听,同时指定接收队列长度为 100
struct sockaddr_in clientSocket; // 客户端信息结构
int sin_size = sizeof(struct sockaddr_in); // 指定 客户端信息结构大小
accept(serverFD, (struct sockaddr*)&clientSocket,(socklen_t*)&sin_size)) // 从 backlog 队列(accept队列)中获取客户端连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sys_accept 函数
当我们使用server socket fd时,我们很容易看到:server fd 将会把存在于 backlog 队列中的客户端信息取出,生成一个新的 socket 结构 用于表示该连接信息,这就表示 server fd 本身就是一个生成者,每调用一次 accept 函数,便生成一个 客户端 socket fd。
asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user *upeer_addrlen)
{
struct socket *sock, *newsock;
int err, len;
char address[MAX_SOCK_ADDR];
sock = sockfd_lookup(fd, &err); // 通过 fd 获取到 socket 结构
if (!sock)
goto out;
err = -EMFILE;
if (!(newsock = sock_alloc())) // 分配一个新的socket 结构,用于表示客户端信息
goto out_put;
// 客户端 socket 的类型和操作与服务端 socket 保持一致
newsock->type = sock->type;
newsock->ops = sock->ops;
...
err = sock->ops->accept(sock, newsock, sock->file->f_flags); // 从 accept 队列中获取客户端连接,信息保存在 newsock 中
if (err < 0)
goto out_release;
if (upeer_sockaddr) { // 将客户端信息复制到用户空间的 upeer_sockaddr 结构中(看这里的复制操作,应该明白为什么需要传递结构的大小)
if(newsock->ops->getname(newsock, (struct sockaddr *)address, &len, 2)<0) {
err = -ECONNABORTED;
goto out_release;
}
err = move_addr_to_user(address, len, upeer_sockaddr, upeer_addrlen);
if (err < 0)
goto out_release;
}
if ((err = sock_map_fd(newsock)) < 0) // 同样,将客户端连接接入VFS,此时需要将其映射为 file 和 下标 fd
goto out_release;
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
inet_accept 函数
该函数用于接收客户端连接,在创建socket时指定。可以看到这里实际操作的对象为Linux 底层 sock 结构,直接调用 sock 的 accept 函数接收连接 sk2,然后在 sock_graft 函数中,将 struct socket *newsock 与 struct sock *sk2 结构互相关联(再次强调一便:socket 为 BSD socket,sock 为Linux底层网络socket)。
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk1 = sock->sk;
int err = -EINVAL;
struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err); // 接收连接生成客户端 sock
if (!sk2)
goto do_err;
lock_sock(sk2);
BUG_TRAP((1 << sk2->sk_state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)); // TCP 状态必须为者三种状态之一(表示曾经建立过连接)
sock_graft(sk2, newsock); // socket 与 sock 互相关联
newsock->state = SS_CONNECTED; // socket 状态修改为建立连接状态
err = 0;
release_sock(sk2);
do_err:
return err;
}
static inline void sock_graft(struct sock *sk, struct socket *parent)
{
write_lock_bh(&sk->sk_callback_lock);
sk->sk_sleep = &parent->wait;
parent->sk = sk;
sk->sk_socket = parent;
write_unlock_bh(&sk->sk_callback_lock);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
tcp_accept 函数
该函数用于从 accept_queue 中获取open_request结构,该结构保存了 sk 信息,若该队列为空,那么根据server fd是否处于非阻塞模式,来选择是否等待,然后从 open_request 中取出 sk 返回。
struct sock *tcp_accept(struct sock *sk, int flags, int *err)
{
struct tcp_opt *tp = tcp_sk(sk); // 获取 tcp 信息结构
struct open_request *req;
struct sock *newsk;
int error;
lock_sock(sk);
error = -EINVAL;
if (sk->sk_state != TCP_LISTEN) // server socket 状态 必须为 监听 状态
goto out;
if (!tp->accept_queue) { // accept 队列为空,表示没有客户端连接
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); // 根据 O_NONBLOCK 标志位获取等待时间(noblock ? 0 : sk->sk_rcvtimeo)
error = -EAGAIN;
if (!timeo) // 若指定非阻塞,那么直接返回
goto out;
error = wait_for_connect(sk, timeo); // 否则等待客户端连接,时间将由 timeo 指定(sk->sk_rcvtimeo = MAX_SCHEDULE_TIMEOUT ; #define MAX_SCHEDULE_TIMEOUT LONG_MAX 表示无限期等待)
if (error)
goto out;
}
// 存在客户端连接
req = tp->accept_queue; // 获取第一个请求
if ((tp->accept_queue = req->dl_next) == NULL) // 队列中只有一个请求,那么将尾指针置空
tp->accept_queue_tail = NULL;
newsk = req->sk; // 取出客户端sk
tcp_acceptq_removed(sk); // sk->sk_ack_backlog-- 减少队列大小
tcp_openreq_fastfree(req); // 释放struct open_request *req 内存,因为其中的sk已经取出
BUG_TRAP(newsk->sk_state != TCP_SYN_RECV); // sock 的状态不能为 SYN 状态,此状态表示:接收到了客户端的连接请求,并且服务端发送了 ack ,等待 客户端 确认后,完成 三次握手,也即:此时处于两次握手截断
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57