# 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

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

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

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