• Linux:TCP三握四挥简析


    1. 前言

    限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

    2. 背景

    本文基于 linux-4.14.132 内核代码进行分析。

    3. TCP连接的建立和断开

    3.1 TCP协议状态机

    在这里插入图片描述

    3.2 TCP的三握四挥

    在这里插入图片描述
    在后面的分析中,我们将始终参考 3.1,3.2 两小节中的状态图

    3.2.1 TCP 连接建立的三次握手过程分析

    3.2.1.1 服务端和客户端套接字的创建

    通过 socket() 系统调用创建套接字后,套接字初始状态为 CLOSED 状态(即 TCP_CLOSE)。来看代码实现细节:

    /* 应用层 通过系统调用 sys_socket() 创建套接字 */
    server_fd = socket(AF_INET, SOCK_STREAM, 0); // 服务端 
    
    remote_fd = socket(AF_INET, SOCK_STREAM, 0); // 客户端
    
    /* 内核空间:初始创建时,套接字为 CLOSED 状态(即 TCP_CLOSE) */
    sys_socket(AF_INET, SOCK_STREAM, 0) // net/socket.c
    	sock_create(family, type, protocol, &sock)
    		__sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0);
    			sock = sock_alloc();
    			sock->type = type; // sock->type = SOCK_STREAM;
    			...
    			pf = rcu_dereference(net_families[family]); // 获取协议簇接口
    			...
    			// 进入协议簇(family)的套接字创建过程
    			pf->create(net, sock, protocol, kern) = inet_create() // net/ipv4/af_inet.c
    				struct sock *sk;
    				
    				sock->state = SS_UNCONNECTED; /* socket 初始为[未连接状态 (SS_UNCONNECTED)] */
    				...
    				list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
    					if (protocol == answer->protocol) { /* 显式指定了 protocol */
    						...
    					}  else { /* 非显式指定 protocol */
    						/* Check for the two wild cases. */
    						if (IPPROTO_IP == protocol) { /* protocol == 0 意味着创建各 @type 下缺省协议的套接字 */
    							protocol = answer->protocol;
    							break;
    						}
       						...
    					}
    					...
    				}
    				...
    				/* 设定套接字对应协议接口 */
    				sock->ops = answer->ops; /* 设定套接字对应协议接口: &inet_stream_ops */
    				answer_prot = answer->prot; /* &tcp_prot */
    				...
    				/* 创建套接字[网络层管理数据]对象 */
    				sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
    					struct sock *sk;
    					
    					sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
    					if (sk) {
    						sk->sk_family = family; // sk->sk_family = PF_INET;
    						sk->sk_prot = sk->sk_prot_creator = prot; /* &tcp_prot */
    						...
    					}
    					
    					return sk;
    				...
    				/* 初始化套接字[网络层管理数据]: 如 type, 状态等 (TCP_CLOSE) */
    				sock_init_data(sock, sk);
    					...
    					//sk->sk_rcvbuf  = sysctl_rmem_default;
    					//sk->sk_sndbuf  = sysctl_wmem_default;
    					//sk->sk_state  = TCP_CLOSE; /* 设定套接字初始状态为 CLOSE */
    					sk_set_socket(sk, sock); /* 绑定网络层管理数据到 socket */
    						sk_tx_queue_clear(sk);
    						sk->sk_socket = sock;
    						...
    					if (sock) {
    						sk->sk_type = sock->type; // sk->sk_type = SOCK_STREAM;
    						sk->sk_wq = sock->wq;
      						sock->sk = sk; /* 设定套接字的网络层管理数据对象 */
      						...
    					}
    					...
    					sk->sk_state_change = sock_def_wakeup;
    					sk->sk_data_ready = sock_def_readable;
    					//sk->sk_write_space = sock_def_write_space;
    					...
    				sk->sk_destruct    = inet_sock_destruct;
    				sk->sk_protocol    = protocol; // IPPROTO_TCP
    				sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
    				...
    				/*
    				 * 前面完成的是 IPv4 协议簇套接字公共初始化。
    				 * 这里是 IPv4 协议簇下的子类型 @type 和 子协议 @protocol 套接字的特定初始化。
    				 * 套接字的初始化是一个个层层递进的过程,有点类似于 C++ 子类对象的构建过程:
    				 * 先调用父类的构造函数,然后在逐级调用子类的构造函数。
    				 *
    				 * 这里是 TCP 类型(IPPROTO_TCP) 套接字 初始化。
    				 */
    				if (sk->sk_prot->init) {
    					err = sk->sk_prot->init(sk); /* tcp_v4_init_sock() */
    					...
    				}
    				...
    
    /* TCP 类型套接字初始化 */
    tcp_v4_init_sock() // net/ipv4/tcp_ipv4.c
    	struct inet_connection_sock *icsk = inet_csk(sk);
    	
    	/* TCP 套接字初始化 */
    	tcp_init_sock(sk);
    		...
    		sk->sk_state = TCP_CLOSE; /* TCP 套接字创建时初始状态为 TCP_CLOSE */
    
    		sk->sk_write_space = sk_stream_write_space;
    		...
    		sk->sk_sndbuf = sysctl_tcp_wmem[1];
    		sk->sk_rcvbuf = sysctl_tcp_rmem[1];
    		...
    
    	/* 设定 IPv4 TCP 套接字操作接口 */
    	icsk->icsk_af_ops = &ipv4_specific;
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    3.2.1.2 服务端进入 LISTEN 状态

    调用 listen() 后,服务端监听套接字 由 CLOSED 状态进入 LISTEN :CLOSED => LISTEN

    // 用户空间
    
    struct sockaddr_in server_addr;
    int backlog = 8;
    
    memset(&server_addr, 0, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8888); // 服务端 端口号
    server_addr.sin_addr.s_addr = inet_addr("192.168.1.123"); // 服务端 IP 地址
    bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    listen(server_fd, backlog); // 服务端: CLOSED => LISTEN
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    // 内核空间
    
    sys_listen(fd, backlog) // net/socket.c
    	struct socket *sock;
    	
    	sock = sockfd_lookup_light(fd, &err, &fput_needed);
    	if (sock) {
    		...
    		err = sock->ops->listen(sock, backlog); /* inet_listen() */
    		...
    	}
    
    inet_listen() // net/ipv4/af_inet.c
    	struct sock *sk = sock->sk;
    	unsigned char old_state;
    
    	err = -EINVAL;
    	// 处于未连接状态的、 SOCK_STREAM 类型套接字 才能监听
    	if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
    		goto out;
    	
    	old_state = sk->sk_state;
    	/* 只有对 TCP_CLOSE 或 TCP_LISTEN 态套接字 listen 才是合法的 */
    	if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
    		goto out;
    	
    	if (old_state != TCP_LISTEN) {
    		...
    		/*
    		 * . accept 队列初始化
    		 * . backlog 初始化
    		 * . 套接字由 TCP_CLOSE 转为 TCP_LISTEN 态 (CLOSED => LISTEN)
    		 */
    		err = inet_csk_listen_start(sk, backlog);
    		if (err)
    			goto out;
    	}
    	sk->sk_max_ack_backlog = backlog;
    	err = 0;
    	
    out:
    	return err;
    
    inet_csk_listen_start() // net/ipv4/inet_connection_sock.c
    	struct inet_connection_sock *icsk = inet_csk(sk);
    
    	reqsk_queue_alloc(&icsk->icsk_accept_queue); // 创建和初始化 accept 队列(全连接队列)
    	
    	sk->sk_max_ack_backlog = backlog;
    	sk->sk_ack_backlog = 0;
    	inet_csk_delack_init(sk);
    
    	sk_state_store(sk, TCP_LISTEN); // 由 TCP_CLOSE 转为 TCP_LISTEN 态 (CLOSED => LISTEN)
    		smp_store_release(&sk->sk_state, newstate);
    	...
    • 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
    3.2.1.3 服务端 应用程序 用 accept() 取出 已建立好 的 客户端连接

    服务端 应用程序accept() 取出 位于 服务端监听套接字 accept 队列(全连接队列) 中、已建立好(已完成三次握手) 的 客户端连接套接字信息。如果服务端监听套接字 accept 队列(全连接队列)当前没有已建立好的客户端连接,则陷入睡眠等待,直到有新建立好(已完成三次握手)的客户端连接到来后被唤醒。

    client_fd = accept(server_fd, NULL, NULL);
    
    sys_accept(server_fd, NULL, NULL) // net/socket.c
    	sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
    		struct socket *sock, *newsock;
    		
    		sock = sockfd_lookup_light(fd, &err, &fput_needed);
    		
    		/* 为可能连接的客户端准备一个 sock 对象 */
    		newsock = sock_alloc(); /* (1) */
    		...
    
    		newsock->type = sock->type; // SOCK_STREAM
     		newsock->ops = sock->ops; // &inet_stream_ops
    
    		...
    		
    		/*
    		 * 为客户端 sock 分配一个 fd 。
    		 * 
    		 * !!!
    		 * 注意:
    		 * @newfd 和 服务端监听套接字 @server_fd 不是同一个,@newfd 是 用来和 新的
    		 * 客户端连接 通信 的 服务端 套接字,也即前面代码中 accept() 返回的 @client_fd 。
    		 */
    		newfd = get_unused_fd_flags(flags);
    		/* 为客户端 sock 分配一个文件对象 (struct file) */
    		newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
    		...
    
    		// 睡眠等待 完成握手过程、添加到 服务端监听套接字 @sock 的 accept 队列(全连接队列) 的 客户端连接
    		err = sock->ops->accept(sock, newsock, sock->file->f_flags, false); /* inet_accept() */
    
    		/* 将指代 已连接客户端 的 套接字 @newsock 的 fd 放入进程的 fd 表 */
    		fd_install(newfd, newfile);
     		err = newfd; /* 返回 服务端 和 客户端通信的套接字 句柄 到用户空间 */
    
    		...
    	out:
     		return err;
    
    inet_accept(newsock, flags, sock->sk->sk_prot_creator->name) // net/ipv4/af_inet.c
    	struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err, kern); /* inet_csk_accept() */
    		inet_csk_accept(sk1, flags, &err, kern) // net/ipv4/inet_connection_sock.c
    			struct inet_connection_sock *icsk = inet_csk(sk);
    			struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    			struct request_sock *req;
     			struct sock *newsk;
    
    			/* Find already established connection */
    			if (reqsk_queue_empty(queue)) { /* accept 队列(全连接队列) 为空,即 还没有 建立好的 客户端 连接 */
    				long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); 
    				
    				// 这里只讨论阻塞方式,不关心非阻塞方式的逻辑
    				
    				/* If this is a non blocking socket don't sleep */
    				error = -EAGAIN;
    				if (!timeo)
    					goto out_err;
    				
    				// 等待 直到有 建立好的 客户端连接
    				error = inet_csk_wait_for_connect(sk, timeo);
    				if (error)
    					goto out_err;
    			}
    			req = reqsk_queue_remove(queue, sk); // 从 accpet 队列(全连接队列) 中取出/移除一个建立好的连接
     			newsk = req->sk; // 返回 新建立的、 和 客户端通信的 套接字
     			...
     	...
     	newsock->state = SS_CONNECTED; // 等待到客户端连接后,套接字标记为已连接状态 SS_CONNECTED
    
    // 睡眠等待 新建立好的客户端连接 (已完成三次握手)
    inet_csk_wait_for_connect(sk, timeo) // net/ipv4/inet_connection_sock.c
    	struct inet_connection_sock *icsk = inet_csk(sk);
    	DEFINE_WAIT(wait);
    	int err;
    
    	for (;;) {
    		prepare_to_wait_exclusive(sk_sleep(sk), &wait,
    					TASK_INTERRUPTIBLE);
    		...
    		if (reqsk_queue_empty(&icsk->icsk_accept_queue)) // 还没有 建立好的 客户端连接,陷入睡眠等待
    			timeo = schedule_timeout(timeo);
    		...
    		if (!reqsk_queue_empty(&icsk->icsk_accept_queue)) // 有 建立好的 客户端连接了,结束等待
    			break;
    		...
    		if (signal_pending(current)) // 被信号中断
    			break;
    		err = -EAGAIN;
    		if (!timeo)
    			break;
    	}
    	finish_wait(sk_sleep(sk), &wait);
    	return err;
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    3.2.1.4 客户端 向 服务端 发送 SYN 连接请求,进入 SYN-SENT 态

    客户端 通过 connect()服务端 (监听套接字) 发起了 SYN 连接请求,然后自身进入 SYN-SENT 态睡眠等待服务端的 SYN + ACK。来看这一个过程的代码实现细节:

    // 客户端
    int remote_fd;
    struct sockaddr_in server_addr;
    
    remote_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SERVER_PORT); // 服务端端口号
    server_addr.sin_addr.s_addr = inet_addr("192.168.1.188"); // 假设服务端 IP 为 192.168.1.188
    connect(remote_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))); // 连接服务端
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    /*
     * c1. 客户端 通过 connect() 向 服务端监听套接字 发送 SYN 连接请求。
     */
    sys_connect(remote_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr))) // net/socket.c
    	struct socket *sock;
    
    	sock = sockfd_lookup_light(fd, &err, &fput_needed);
    	...
    	/* @uservaddr: 客户端 想连接的 目标地址 */
    	err = move_addr_to_kernel(uservaddr, addrlen, &address);
    	...
    	err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags); // inet_stream_connect()
    		inet_stream_connect() // net/ipv4/af_inet.c
    			__inet_stream_connect(sock, uaddr, addr_len, flags, 0);
    				switch (sock->state) {
    				...
    				// 发送 SYN 请求包到 服务端
    				case SS_UNCONNECTED:
    					...
    					err = sk->sk_prot->connect(sk, uaddr, addr_len); /* tcp_v4_connect() */
    					sock->state = SS_CONNECTING; /* 套接字标记为 正在连接状态 */
    					...
    					break;
    				}
    
    tcp_v4_connect(sk, uaddr, addr_len) // net/ipv4/tcp_ipv4.c
    	// 一些路由等相关的其它处理
    	...
    	tcp_set_state(sk, TCP_SYN_SENT); /* 客户端套接字状态由 CLOSED 转为 SYN-SENT: CLOSED => SYN-SENT */
    	...
    	err = tcp_connect(sk); /* 发送 SYN 包 */
    		struct tcp_sock *tp = tcp_sk(sk);
    		struct sk_buff *buff;
    
    		...
    		/* 为 SYN 包分配空间 */
    		buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
    		...
    		tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN); /* 构建 SYN 包 */
    		...
    		/* 发送 SYN 包 */
    		err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
    				tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
    	...
    • 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
    /*
     * c2. 客户端 套接字 向 服务端监听套接字 发送 SYN 请求,在 SYN-SENT 态睡眠等待 服务端回复 SYN+ACK 。
     */
    
    // 接前面的 inet_stream_connect() 调用 tcp_v4_connect() 之后的流程
    inet_stream_connect() // net/ipv4/af_inet.c
    	...
    	switch (sock->state) {
    	// 发送 SYN 请求包到服务端
    	case SS_UNCONNECTED:
    		...
    		err = sk->sk_prot->connect(sk, uaddr, addr_len); /* tcp_v4_connect() */
    		sock->state = SS_CONNECTING; /* 套接字标记为 已连接状态 */
    		...
    		break;
    	}
    
    	...
    	if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    		...
    		/*
    		 * 已向服务端发送 SYN 包,陷入睡眠等待服务端回复 SYN + ACK. 
    		 * 在收到服务端的 SYN + ACK 后, 内核再回复 ACK 给服务端,
    		 * 然后唤醒等待在此处的进程.
    		 */
    		if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))
    			goto out;
    		...
    	}
    	...
    
    	sock->state = SS_CONNECTED; /* 到此, socket 进入已连接状态 (SS_CONNECTED) */
    
    // 睡眠等待服务端回送 SYN + ACK
    inet_wait_for_connect() // net/ipv4/af_inet.c
    	DEFINE_WAIT_FUNC(wait, woken_wake_function);
    
    	// sk_sleep() 
    	//		return &rcu_dereference_raw(sk->sk_wq)->wait;
    	add_wait_queue(sk_sleep(sk), &wait);
    	...
    	while ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
    		...
    		timeo = wait_woken(&wait, TASK_INTERRUPTIBLE, timeo); // 等待服务端回复: 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
    3.2.1.5 服务端 收取 客户端 SYN,然后回复 客户端 SYN + ACK,进入 SYN-RECEIVED 态

    上一小节说到 客户端套接字服务端监听套接字 发送 SYN 连接请求后,在 SYN-SENT 态睡眠等待 服务端监听套接字 回复 SYN + ACK 。接下来 服务端监听套接字 收取 客户端套接字 SYN 连接请求,为 SYN 连接请求 新建一个 记录客户端连接信息、初始为 SYN-RECEIVED 态 的 轻量套接字 struct request_sock(见后面代码注释 /* (2) */ 处),然后将其插入到 服务端监听套接字SYN 队列(半连接队列),最后回复 客户端套接字 SYN + ACK 的细节。注意,服务端监听套接字 仍维持在 LISTEN 态,进入 SYN-RECEIVED 态的是为新连接建立的轻量套接字 struct request_sock

    /*
     * s1. 服务端监听套接字 收取 客户端 SYN,为 SYN 连接请求 建立轻量套接字,
     *     并将 轻量套接字 插入 服务端监听套接字 的 SYN 队列(半连接队列),
     *     最后 服务端监听套接字 回复 客户端 SYN + ACK.
     */
    
    xxx_nic_interrput() // 从网卡数据接收中断入口开始
    	napi_gro_receive()
    		napi_skb_finish()
    			netif_receive_skb_internal()
    				__netif_receive_skb()
    					__netif_receive_skb_core()
    						pt_prev->func() = ip_rcv()
    							ip_rcv_finish()
    								dst_input()
    									ip_local_deliver()
    										ip_local_deliver_finish()
    											// 见后续分析
    											ipprot->handler() = tcp_v4_rcv()
    	
    // 接上面的分析
    tcp_v4_rcv()
    	...
    	/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字: 服务端监听套接字 */
    	sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    				th->dest, sdif, &refcounted);
    	...
    	if (sk->sk_state == TCP_LISTEN) { /* 服务端监听套接字 处于 LISTEN 态 */
    		ret = tcp_v4_do_rcv(sk, skb); // 见后续分析
    		goto put_and_return;
    	}
    	...
    
    // 接上面的分析
    tcp_v4_do_rcv(sk, skb)
    	...
    	if (tcp_rcv_state_process(sk, skb)) { // 见后续分析
    		// 出错处理
    		...
    	}
    	return 0;
    	...
    
    // 接上面的分析
    tcp_rcv_state_process(sk, skb)
    	...
    	switch (sk->sk_state) {
    	...
    	case TCP_LISTEN:
    		if (th->syn) { /* @skb 为 SYN 报文 */
    			...
    			// 见后续分析
    			acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0; // tcp_v4_conn_request()
    			...
    			if (!acceptable) /* @sk 不接收 @skb, 回发给源头 RESET */
    				return 1;
    			/* @sk 正常接收 SYN @skb */
    			consume_skb(skb);
    			return 0;
    		}
    	...
    	}
    	
    // 接上面的分析
    acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0; // tcp_v4_conn_request()
    	tcp_v4_conn_request(sk, skb) // net/ipv4/tcp_ipv4.c
    		// 见后续分析
    		return tcp_conn_request(&tcp_request_sock_ops,
    					&tcp_request_sock_ipv4_ops, sk, skb);
    
    // 接上面的分析
    tcp_conn_request(&tcp_request_sock_ops, // net/ipv4/tcp_input.c
    		&tcp_request_sock_ipv4_ops, sk, skb);
    	...
    	struct request_sock *req;
    	...
    
    	if (sk_acceptq_is_full(sk)) { // 套接字的 @sk accept 队列(全连接队列) 已满
    		NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
    		goto drop;
    	}
    	
    	...
    	
    	/*
    	 * 为连接请求, 分配 轻量套接字 struct request_sock (指代 server_fd 套接字), 
    	 * 将其先放到 服务端监听套机字 @sk 的 SYN 队列(半连接队列),然后在收到 客户端 
    	 * 对 服务端 SYN (SYN+ACK) 的 ACK 回复后,再从 服务端监听套机字 @sk 的 SYN 队列(半连接队列)
    	 * 移动到 accept 队列(全连接队列)。
    	 */
    	req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie); /* (2) */
    		struct request_sock *req = reqsk_alloc(ops, sk_listener, attach_listener);
    			req = kmem_cache_alloc(ops->slab, GFP_ATOMIC | __GFP_NOWARN);
    				if (attach_listener) {
    					...
    					/* 
    					 * 设定 记录 客户端连接信息 的 轻量套接字 所属的 服务端监听套接字, 
    					 * 即 客户端 的 SYN 请求是向 服务端监听套接字 @sk_listener 发起的。
    					 */
    					req->rsk_listener = sk_listener;
    				}
    				req->rsk_ops = ops; /* req->rsk_ops = &tcp_request_sock_ops */
    				req_to_sk(req)->sk_prot = sk_listener->sk_prot;
    				...
    		if (req) {
    			struct inet_request_sock *ireq = inet_rsk(req);
    			...
    			// 标记 服务端 记录客户端连接信息的、轻量级套接字(struct request_sock) 为 SYN-RECEIVED 态:
    			// 即 已经收到 客户端的 SYN 连接请求包。
       			// 注:TCP_NEW_SYN_RECV 是 高内核版本新引入的套接字状态,TCP_SYN_RECV 状态被 TFO 特性使用。
       			ireq->ireq_state = TCP_NEW_SYN_RECV;
       			...
       			ireq->ireq_family = sk_listener->sk_family;
    		}
    	tcp_rsk(req)->af_specific = af_ops; /* tcp_rsk(req)->af_specific = &tcp_request_sock_ipv4_ops */
    	
    	...
    
    	af_ops->init_req(req, sk, skb); /* 设置轻量级套接字的 源、目的 IP: 同 @sk 的 源、目的 IP */
    		tcp_v4_init_req()
    	
    	...
    
    	if (fastopen_sk) { /* TCP Fast Open(TFO) 特性 */
    		...
    	}  else {
    		tcp_rsk(req)->tfo_listener = false;
    		if (!want_cookie)
    			/* 添加 记录客户端连接信息的 轻量套接字 @req 到 服务端监听套接字 @sk 的 SYN 队列(半连接队列) */
    			inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));
    				reqsk_queue_hash_req(req, timeout); // 将 轻量套接字 插入到 SYN 队列(半连接队列)
    				inet_csk_reqsk_queue_added(sk);
    					reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
    						atomic_inc(&queue->young);
    						atomic_inc(&queue->qlen); // SYN 队列(半连接队列) 长度 加 1
    		/* 服务端监听套接字 收到 客户端 SYN 后,回送 SYN + ACK */
    		af_ops->send_synack(sk, dst, &fl, req, &foc, // tcp_v4_send_synack()
               			!want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE);
    			...
    			struct sk_buff *skb;
    
    			skb = tcp_make_synack(sk, dst, req, foc, synack_type);
    			if (skb) {
    				...
    				err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,
    							ireq->ir_rmt_addr,
    							rcu_dereference(ireq->ireq_opt));
    				...
    			}
    	}
    	reqsk_put(req);
    	return 0;
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    3.2.1.6 客户端 收取 服务端 SYN + ACK,回复 服务端 ACK,进入 ESTABLISHED 态

    上一小节中,服务端监听套接字 完成了收取 客户端 SYN ,建立插入连接请求轻量套接字,并回复 客户端套接字SYN + ACK 的过程。接下来 客户端套接字 将收取 服务端SYN + ACK,并回复 服务端 的 SYNACK,进入 ESTABLISHED 态,于是完成了 客户端 => 服务端 单向连接 的建立。来看这一过程代码实现细节:

    // 从网卡数据接收中断入口开始
    xxx_nic_interrput()
    	napi_gro_receive()
    		napi_skb_finish()
    			netif_receive_skb_internal()
    				__netif_receive_skb()
    					__netif_receive_skb_core()
    						pt_prev->func() = ip_rcv()
    							ip_rcv_finish()
    								dst_input()
    									ip_local_deliver()
    										ip_local_deliver_finish()
    											ipprot->handler() = tcp_v4_rcv()
    
    tcp_v4_rcv() // net/ipv4/tcp_ipv4.c
    	struct sock *sk;
    
    	...
    lookup:
    	/* @sk: 客户端套接字 */
    	sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    			       th->dest, sdif, &refcounted); 
    	...
    	ret = 0;
    	if (!sock_owned_by_user(sk)) {
    		ret = tcp_v4_do_rcv(sk, skb);
    	}  else if (tcp_add_backlog(sk, skb)) {
    		...
    	}
    	...
    
    tcp_v4_do_rcv(sk, skb)
    	...
    	if (tcp_rcv_state_process(sk, skb)) {
    		...
    	}
    
    tcp_rcv_state_process(sk, skb)
    	...
    	switch (sk->sk_state) {
    	...
    	case TCP_SYN_SENT: /* 已往服务端发送了 SYN 的客户端处于 TCP_SYN_SENT (connect()  调用) */
    		...
    		queued = tcp_rcv_synsent_state_process(sk, skb, th); /* 收取服务端发送的 SYN + ACK */
    		...
    		return 0;
    	...
    	}
    
    tcp_rcv_synsent_state_process(sk, skb, th)
    	...
    	if (th->ack) { // ACK 包标记
    		...
    		if (!th->syn) // 不是 (SYN + ACK)
    			goto discard_and_undo;
    		...
    		tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
    		tcp_ack(sk, skb, FLAG_SLOWPATH); // 处理服务端回复包中的 ACK
    		// 包序列号的一些处理
    		tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
    		tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;
    		...
    		// MTU, MSS 处理
    		tcp_mtup_init(sk);
    		tcp_sync_mss(sk, icsk->icsk_pmtu_cookie);
    		tcp_initialize_rcv_mss(sk);
    		...
    		tcp_finish_connect(sk, skb); // 连接建立完成:套接字 @sk 由 SYN-SENT 转为 ESTABLISHED
    			tcp_set_state(sk, TCP_ESTABLISHED);
    				...
    				sk_state_store(sk, state); // sk->state = TCP_ESTABLISHED
    			...
    		...
    		if (!sock_flag(sk, SOCK_DEAD)) {
    			/* 唤醒等待 server 端 SYN + ACK 的 connect() */
    			sk->sk_state_change(sk) = sock_def_wakeup(sk)
    				struct socket_wq *wq;
    				...
    				wq = rcu_dereference(sk->sk_wq);
    				if (skwq_has_sleeper(wq))
    					wake_up_interruptible_all(&wq->wait); // 唤醒 connect() 调用链中在 inet_wait_for_connect() 中等待连接建立完成的进程
    				...
    			sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);
    		}
    		...
    		if (sk->sk_write_pending ||
    		    icsk->icsk_accept_queue.rskq_defer_accept ||
    		    icsk->icsk_ack.pingpong) {
    		    ...
    		} else {
    			tcp_send_ack(sk); /* 客户端 构建 + 发送 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
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    3.2.1.7 服务端 收取 客户端 ACK,进入 ESTABLISHED 态,完成 三次握手

    上一小节中,客户端 => 服务端 的单向连接 已经建立完成。接下来 服务端3.2.1.6 小节中代码注释 /* (2) */ 处、为 客户端 SYN 连接请求 建立的 轻量套接字 struct request_sock,将收取 客户端套接字服务端 SYN 连接请求 回复的 ACK,然后将 位于 服务端监听套接字 SYN 队列(半连接队列)连接请求轻量套接字 struct request_sock 取出,用 轻量套接字 的数据信息、在 服务端 为该 轻量套接字 所指代的 客户端连接 建立新的、初始状态为 SYN-RECEIVED 的、完整的 TCP 套接字对象(struct sock),然后添加该 TCP 套接字对象服务端监听套接字 的 accept 队列(全连接队列),同时将 TCP 套接字对象(struct sock) 的 状态更新为 ESTABLISHED,此时如果发现有在 accept() 中 睡眠等待 新连接就绪的 进程,则唤醒它们获取代表新连接的 TCP 套接字对象。进程调用 accept() 进入睡眠的过程参考 3.2.1.3 小节的分析。同时,来看这一过程代码实现细节:

    /*
     * s2. 服务端 收取 客户端 ACK,将位于 服务端监听套接字 SYN 队列(半连接队列)、代表 客户端连接 的
     *     轻量套接字,用 轻量套接字 的数据信息,为 客户端连接 建立完成 TCP 套接字 struct sock,并
     *     添加到 服务端监听套接字 的 accept 队列(全连接队列)。此时如果发现有在 accept() 中 睡眠等待 
     *     新连接就绪的 进程,则唤醒它们获取 代表新连接 TCP 套接字对象。
     */
    
    xxx_nic_interrput() // 从网卡数据接收中断入口开始
    	...
    	tcp_v4_rcv()
    		...
    	lookup:
    		// 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字。
    		// @sk: 3.2.1.6 小节中代码注释 /* (2) */ 处 建立的 轻量套接字 struct request_sock
    		sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    						th->dest, sdif, &refcounted);
    		...
    	process:
    		...
    		/*
    		 * 已经收到了客户端 SYN 并回应了 SYN + ACK 的 服务端 客户端连接轻量级套接字: 
    	 	 * . 服务端 客户端连接 轻量套接字 @sk / @req 处于 TCP_NEW_SYN_RECV 状态
    	 	 * . 服务端 客户端连接 轻量套接字 @sk / @req 关联 的 服务端监听套接字 
    	 	 *   @req->rsk_listener 处于 TCP_LISTEN 状态,即 @req 指代的客户端连接
    	 	 *   是 向 服务端监听套接字 @req->rsk_listener 发起。
    	 	 */
    		if (sk->sk_state == TCP_NEW_SYN_RECV) {
    			struct request_sock *req = inet_reqsk(sk); // 服务端轻量级"代理"套接字
    			struct sock *nsk;
    
    			sk = req->rsk_listener;
    			...
    			if (unlikely(sk->sk_state != TCP_LISTEN)) {
    				inet_csk_reqsk_queue_drop_and_put(sk, req);
    				goto lookup;
    			}
    			...
    			if (!tcp_filter(sk, skb)) { /* 如果数据包没有被 eBPF 过滤掉 */
    				...
    				/*
    				 * 通过 轻量套接字 @req 的信息,为 客户端新连接 建立 服务端的完整套接字: 
    				 * 建立 TCP 的套接字数据 (struct sock *nsk),并将  添加 到 服务端监听套接字 
    				 * @req->rsk_listener 的 accept 队列 (全连接队列)。
    				 */
    				nsk = tcp_check_req(sk, skb, req, false); // 见后续 s2.1 分析
    			}
    			...
    			if (nsk == sk) {
    				...
    			}
    			// 新连接初始化(MTU、缓冲等),然后唤醒在 accept() 调用中阻塞等待新连接的进程
    			// 见后续 s2.2 分析
    			else if (tcp_child_process(sk, nsk, skb)) {
    				...
    			}  else {
    				sock_put(sk);
    				return 0;
    			}
    			...
    		}
    
    // s2.1 
    // 为 客户端新连接 建立完整的 TCP 套接字(struct sock),并将其添加到 accept 队列(全连接队列)。
    nsk = tcp_check_req(sk, skb, req, false); // net/ipv4/tcp_minisocks.c
    	child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL, req, &own_req);
    		tcp_v4_syn_recv_sock() // net/ipv4/tcp_ipv4.c
    			struct sock *newsk;
    			...
    			newsk = tcp_create_openreq_child(sk, req, skb);
    				struct sock *newsk = inet_csk_clone_lock(sk, req, GFP_ATOMIC);
    					struct sock *newsk = sk_clone_lock(sk, priority);
    					if (newsk) {
    						...
    						newsk->sk_state = TCP_SYN_RECV;
    						...
    					}
    				...
    			...
    	...
    	/* 
    	 * . 将 客户端新连接 轻量套接字 从 服务端监听套接字 的 SYN 队列(半连接队列) 移除,
    	 * . 将 新建的 客户端新连接 TCP 套接字(struct sock) 添加到 服务端监听套接字 的 accept 队列(全连接队列) 
    	 */
    	return inet_csk_complete_hashdance(sk, child, req, own_req);
    		if (own_req) {
    			// 将 客户端新连接 轻量套接字 从 服务端监听套接字 的 SYN 队列(半连接队列) 移除
    			inet_csk_reqsk_queue_drop(sk, req); // 从 ehash 表删除
    			reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req);
    				if (req->num_timeout == 0)
    					atomic_dec(&queue->young);
    				atomic_dec(&queue->qlen); // SYN 队列(半连接队列) 长度 减 1
    			// 将 新建的 客户端新连接 TCP 套接字(struct sock),
    			// 添加到 服务端监听套接字 @sk 的 accept 队列(全连接队列).
    			if (inet_csk_reqsk_queue_add(sk, req, child))
    				return child;
    		}
    		...
    
    // 接 s2.1 分析:
    // 将 新建的 客户端新连接 TCP 套接字(struct sock), 
    // 添加到 服务端监听套接字 @sk 的 accept 队列(全连接队列).
    inet_csk_reqsk_queue_add(sk, req, child)
    	struct request_sock_queue *queue = &inet_csk(sk)->icsk_accept_queue;
    
    	...
    	if (unlikely(sk->sk_state != TCP_LISTEN)) {
    		...
    	} else {
    		req->sk = child;
    		req->dl_next = NULL;
    		if (queue->rskq_accept_head == NULL)
    			queue->rskq_accept_head = req;
    		else
    			queue->rskq_accept_tail->dl_next = req;
    		queue->rskq_accept_tail = req;
    		sk_acceptq_added(sk);
    			sk->sk_ack_backlog++; /* accept 队列(全连接队列) 长度 加 1 */
    	}
    	...
    
    // s2.2 新连接初始化(MTU、缓冲等),然后唤醒在 accept() 调用中阻塞等待新连接的进程
    tcp_child_process(sk, nsk, skb)
    	int state = child->sk_state;
    
    	...
    	if (!sock_owned_by_user(child)) {
    		ret = tcp_rcv_state_process(child, skb);
    			...
    			switch (sk->sk_state) {
    			case TCP_SYN_RECV:
    				...
    				tcp_set_state(sk, TCP_ESTABLISHED); /* 与客户端的通信的套接字装换为已连接状态 ESTABLISHED */
    				...
    				tcp_init_wl(tp, TCP_SKB_CB(skb)->seq);
    				...
    				tcp_initialize_rcv_mss(sk);
    				...
    				break;
    			}
    			...
    		/* Wakeup parent, send SIGIO */
    		if (state == TCP_SYN_RECV && child->sk_state != state)
    			/* 唤醒在 accept() 中等待连接的进程 */
    			parent->sk_data_ready(parent) = sock_def_readable()
    				...
    				wq = rcu_dereference(sk->sk_wq);
    				if (skwq_has_sleeper(wq))
    					wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |
    													POLLRDNORM | POLLRDBAND);
    					sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
    				...
    	} else {
    		...
    	}
    	...
    	return 0;
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155

    到此,3.2 小节中图示的 TCP 连接建立的 三次握手 过程 已经全部分析完成。当然,这里分析的只是 TCP 连接建立的几种可能序列当中的一个,对其它的建立过程序列感兴趣的读者可以自行分析源码。另外,对于 SYN Cookies ,TFO(TCP Fast Open),端口重用 等特性,本文也未加讨论。

    3.2.1.8 TCP 三次握手 过程小结

    调用 listen() 后,服务端 监听套接字(即前面代码中的 server_fd 指代的套接字) 处于 LISTEN 状态,客户端套接字 处于 CLOSED 状态。客户端通过 connect() 调用,向 服务端 监听套接字 发送 SYN 请求,然后自身进入到 SYN-SENT 状态等待服务端对 SYN 请求SYN + ACK 回复,客户端在收到服务端的 SYN + ACK 回复后,也对服务端的 SYN 回复一个 ACK,之后自身进入到 ESTABLISHED 状态,并从 connect() 调用返回;而处于 LISTEN 状态的 服务端 监听套接字 收到客户端的 SYN 请求后,先是新建一个记录客户端连接信息的、初始为 SYN-RECEIVED 状态 的 轻量连接请求套接字(struct request_sock) ,接着将该轻量套接字添加到 服务端 监听套接字SYN 队列(半连接队列),回复 SYN + ACK 给客户端,然后服务端等待客户端回复 ACK,当客户端的 ACK 到来后,将前面新建的、记录客户端连接信息的 轻量套接字(struct request_sock),从服务端 监听套接字SYN 队列(半连接队列) 移出,并以该 轻量套接字 为模板,为客户端连接建立一个完成 TCP 套接字(struct sock),并将其添加到 服务端监听套接字 的 accept 队列(全连接队列),并唤醒可能阻塞在 accept() 等待连接到来的进程。细心的读者注意到了吧,服务端 监听套接字 server_fd 的状态不会发生变化,仍然处于 LISTEN 状态,这从 3.1,3.2 小节中的状态转换图是看不出来的,而且还会误解是监听套接字 server_fd 的状态发生了变化。服务端 真正变为 ESTABLISHED 状态的套接字是 accept() 调用中注释 /* (1) */ 处创建的 newsock,这个后面再做分析。
    TCP 连接的建立是一个双向的过程,不管是服务端还是客户端,都会向对端通过 SYN 发起连接请求,而对方也会在收到 SYN 后回应一个 ACK ,这样一个双向连接就建立好了。以这样的方式建立连接的基本理由,是因为 TCP 是全双工通信。从后面的章节也可以看到,拆除连接(四次挥手) 也是一个双向拆除的过程。
    另外,连接建立的过程,是由内核协议栈完成的。细心的童鞋会发现,在调用 listen() 后,即使服务端不调用 accept() ,客户端照样可以通过 connect() 建立连接,只是这样的连接,无法正常和服务端进行数据通信。这是因为 listen() 已经建立好了 accept 队列当客户端发起连接时,内核协议栈会把建立好的连接信息放入 accept 队列;而 accept() 只是从该队列中取出建立好的连接信息,它本身并不参与连接的建立过程。用 netstat 观察,可以发现这些 TCP 连接没有进程名信息,取而代之的是 -- 。这样的现象,可能会让初接触 TCP 编程的童鞋大吃一惊。

    3.2.2 TCP 连接断开的四次挥手过程分析

    当我们的通信完毕,可以通过调用 close() 关闭连接。和连接的建立一样,连接的关闭过程也存在多种可能的序列,本文以 3.2 小节中的连接关闭过程为例来进行分析。

    3.2.2.1 客户端通过 close() 向服务端发送 FIN,进入 FIN-WAIT-1 状态
    // 客户端发起本端的连接断开请求
    close(remote_fd);
    
    sys_close() // fs/open.c
    	__close_fd(current->files, fd)
    		filp_close(file, files)
    			// 中间过程有点小复杂,不是这里关注的重点
    			...
    			sock_close()
    
    sock_close() // net/socket.c
    	__sock_release(SOCKET_I(inode), inode)
    		if (sock->ops) {
    			...
    			sock->ops->release(sock) = inet_release() // net/ipv4/af_inet.c
    				struct sock *sk = sock->sk;
    				if (sk) {
    					...
    					sk->sk_prot->close(sk, timeout) = tcp_close()
    				}
    				return 0;
    			...
    		}
    
    tcp_close(sk, timeout) // net/ipv4/tcp.c
    	...
    	sk->sk_shutdown = SHUTDOWN_MASK;
    	...
    	
    	/*
    	 * 关闭连接时,需处理接收缓冲里还没有被应用层读取的数据,
    	 * 我们假定关闭连接时,客户端应用已经拿走了接收缓冲里的
    	 * 所有数据。
    	 * 对于客户端套接字缓冲还有未读取数据的情形,读者可自行分析。
    	 */
    	...
    	
    	sk_mem_reclaim(sk);
    
    	if (unlikely(tcp_sk(sk)->repair)) {
    		...
    	}  else if (data_was_unread) {
    		...
    	}  else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
    		...
    	} else if (tcp_close_state(sk)) { // 套接字由 ESTABLISHED 态转为 FIN-WAIT-1
    		tcp_send_fin(sk); // 向服务端发送 FIN 包
    	}
    	
    	...
    • 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
    3.2.2.2 服务端收取客户端 FIN,回以 ACK,进入 CLOSE-WAIT 状态

    客户端发送 FIN 包给服务端,服务端收到客户端的 FIN 包后,回应以一个 ACK

    xxx_nic_interrput()
    	...
    	tcp_v4_rcv()
    		...
    	lookup:
    		/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字 */
    		sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    			       th->dest, sdif, &refcounted);
    	...
    	process:
    		...
    		if (!sock_owned_by_user(sk)) {
    			ret = tcp_v4_do_rcv(sk, skb);
    				...
    				if (sk->sk_state == TCP_ESTABLISHED) {
    					...
    					tcp_rcv_established(sk, skb, tcp_hdr(skb)); // net/ipv4/tcp_input.c
    						...
    						tcp_send_ack(sk); // 回应 FIN 一个 ACK
    						...
    						tcp_data_queue(sk, skb);
    							if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
    								...
    								// 服务端收到客户端的 FIN ,除了回一个 ACK 外,
    								// 还要针对 FIN 包做一些和客户端连接的套接字的特定处理:
    								// 状态由 ESTABLISHED 转为 CLOSE-WAIT 等等...
    								if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
    									tcp_fin(sk);
    										sk->sk_shutdown |= RCV_SHUTDOWN;
    										sock_set_flag(sk, SOCK_DONE);
    										switch (sk->sk_state) {
    										case TCP_SYN_RECV:
    										case TCP_ESTABLISHED:
    											// ESTABLISHED => CLOSE-WAIT
    											tcp_set_state(sk, TCP_CLOSE_WAIT);
    											inet_csk(sk)->icsk_ack.pingpong = 1;
    											break;
    										...
    										}
    										...
    								...
    							}
    						...
    					return 0;
    				}
    		} else if (...) {
    			...
    		}
    • 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
    3.2.2.3 客户端收取服务端对 FIN 的回应 ACK,进入 FIN-WAIT-2 状态

    客户端在调用 close() 向服务端发送 FIN 包后,当前处于 FIN-WAIT-1 状态(如果没有设置SOCK_LINGERclose() 调用也已经返回);服务端对客户端的 FIN 包回应了一个 ACK,自身进入 CLOSE-WAIT 状态;客户端收到这个 ACK 后,进入 FIN-WAIT-2 状态,这时从服务端向客户端发送数据的通道就已经关闭了。看客户端处理 FINACK 包后,进入 FIN-WAIT-2 状态这一过程的代码实现细节:

    xxx_nic_interrput()
    	...
    	tcp_v4_rcv()
    		...
    	lookup:
    		/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字 */
    		sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    			       th->dest, sdif, &refcounted);
    		...
    	process:
    		...
    		if (!sock_owned_by_user(sk)) {
    			ret = tcp_v4_do_rcv(sk, skb);
    				...
    				if (tcp_rcv_state_process(sk, skb)) {
    					rsk = sk;
    					goto reset;
    				}
    				return 0;
    				...
    		} else if (tcp_add_backlog(sk, skb)) {
    			...
    		}
    	...
    
    tcp_rcv_state_process(sk, skb)
    	...
    	switch (sk->sk_state) {
    	...
    	case TCP_FIN_WAIT1: {
    		...
    		tcp_set_state(sk, TCP_FIN_WAIT2); /* 客户端: FIN-WAIT-1 => FIN-WAIT-2 */
    		sk->sk_shutdown |= SEND_SHUTDOWN;
    		...
    	}
    	...
    	}
    	...
    • 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
    3.2.2.4 服务端通过 close() 向客户端发 FIN,进入 LAST-ACK 状态

    此时,TCP 连接已经处于半关闭状态(客户端接收数据通道已经关闭)。同样的,在服务端不再想接收客户端的数据时,调用 close() 向客户端发送 FIN 包,然后服务端套接字由 CLOSE-WAIT 进入 LAST-ACK 状态:

    // 关闭服务端和客户端通信的套接字,注意,这里关闭的不是 server_fd
    close(client_fd);
    
    sys_close() // fs/open.c
    	...
    	sock_close() // net/socket.c
    		__sock_release(SOCKET_I(inode), inode)
    		if (sock->ops) {
    			...
    			sock->ops->release(sock) = inet_release() // net/ipv4/af_inet.c
    				struct sock *sk = sock->sk;
    				if (sk) {
    					...
    					sk->sk_prot->close(sk, timeout) = tcp_close()
    				}
    				return 0;
    			...
    		}
    
    tcp_close(sk, timeout) // net/ipv4/tcp.c
    	...
    	sk->sk_shutdown = SHUTDOWN_MASK;
    	...
    	
    	sk_mem_reclaim(sk);
    	
    	...
    	if (unlikely(tcp_sk(sk)->repair)) {
    		...
    	}  else if (data_was_unread) {
    		...
    	}  else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
    		...
    	} else if (tcp_close_state(sk)) { // 套接字由 CLOSE-WAIT 态转为 LAST-ACK
    		tcp_send_fin(sk); // 向客户端发送 FIN 包
    	}
    	...
    • 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
    3.2.2.5 客户端收取服务端 FIN,回以 ACK,进入 TIME-WAIT,超时后进入 CLOSED 终态

    客户端当前处于 FIN-WAIT-2 状态,收到服务端的 FIN 包后,回复服务端一个 ACK,然后自身进入 TIME-WAIT 状态:

    xxx_nic_interrput()
    	...
    	tcp_v4_rcv()
    		...
    	lookup:
    		/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字 */
    		sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    			       th->dest, sdif, &refcounted);
    		...
    	process:
    		...
    		if (!sock_owned_by_user(sk)) {
    			ret = tcp_v4_do_rcv(sk, skb);
    				...
    				if (tcp_rcv_state_process(sk, skb)) {
    					rsk = sk;
    					goto reset;
    				}
    				return 0;
    				...
    		} else if (tcp_add_backlog(sk, skb)) {
    			...
    		}
    	...
    
    tcp_rcv_state_process(sk, skb)
    	...
    	/* step 7: process the segment text */
    	switch (sk->sk_state) {
    	...
    	case TCP_FIN_WAIT1:
    	case TCP_FIN_WAIT2: // 客户端当前处于 FIN-WAIT-2 状态
    		...
    		/* Fall through */
    	case TCP_ESTABLISHED:
    		tcp_data_queue(sk, skb); // 处理服务端发送的 FIN , 并进入 TIME-WAIT 状态
    		queued = 1;
    		break;
    	}
    	...
    
    // 处理服务端发送的 FIN , 并进入 TIME-WAIT 状态
    tcp_data_queue(sk, skb)
    	...
    	if (TCP_SKB_CB(skb)->seq == tp->rcv_nxt) {
    		...
    		if (TCP_SKB_CB(skb)->tcp_flags & TCPHDR_FIN)
    			tcp_fin(sk);
    				switch (sk->sk_state) {
    				...
    				case TCP_FIN_WAIT2:
    					/* Received a FIN -- send ACK and enter TIME_WAIT. */
    					tcp_send_ack(sk); // 回复给服务端 ACK
    					tcp_time_wait(sk, TCP_TIME_WAIT, 0); // 进入 TIME-WAIT 态: FIN-WAIT-2 => TIME-WAIT
    					break;
    				...
    				}
    		...
    	}
    	...
    
    // 进入 TIME-WAIT 态: FIN-WAIT-2 => TIME-WAIT
    tcp_time_wait(sk, TCP_TIME_WAIT, 0);
    	...
    	struct inet_timewait_sock *tw;
    	...
    
    	/* 分配 TIME-WAIT 套接字数据,初始化包括 TIME-WAIT 超时定时器 等 */
    	tw = inet_twsk_alloc(sk, tcp_death_row, state);
    		...
    		tw = kmem_cache_alloc(sk->sk_prot_creator->twsk_prot->twsk_slab, GFP_ATOMIC);
    		...
    		if (tw) {
    			...
    			tw->tw_state	    = TCP_TIME_WAIT;
    			tw->tw_substate	    = state; // tw->tw_substate = TCP_TIME_WAIT
    			...
    			// 初始化 TIME-WAIT 超时定时器
    			setup_pinned_timer(&tw->tw_timer, tw_timer_handler, (unsigned long)tw);
    			...
    		}
    	if (tw) {
    		...
    		// TIME-WATI 超时时间设置
    		tw->tw_timeout = TCP_TIMEWAIT_LEN;
    		if (state == TCP_TIME_WAIT)
    			timeo = TCP_TIMEWAIT_LEN;
    		...
    		inet_twsk_schedule(tw, timeo); /* 启动 TIME-WAIT 超时定时器 */
    			__inet_twsk_schedule(tw, timeo, false);
    				tw->tw_kill = timeo <= 4*HZ;
    				if (!rearm) {
    					BUG_ON(mod_timer(&tw->tw_timer, jiffies + timeo)); // 启动 TIME-WAIT 超时定时器
    					atomic_inc(&tw->tw_dr->tw_count);
    				} else {
    					...
    				}
    		...
    	}
    
    	...
    	tcp_done(sk);
    		...
    		tcp_set_state(sk, TCP_CLOSE);
    		...
    
    // TIME-WAIT 定时器超时,触发 tw_timer_handler(),
    // 回收 TIME-WAIT (struct inet_timewait_sock) 套接字资源
    tw_timer_handler()
    	struct inet_timewait_sock *tw = (struct inet_timewait_sock *)data;
    
    	...
    	inet_twsk_kill(tw); // 回收 TIME-WAIT (struct inet_timewait_sock) 套接字资源
    • 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
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    3.2.2.6 服务端收取客户端对 FIN 的回应 ACK,进入 CLOSED 终态

    上面分析中,客户端收到服务端的 FIN 后,回复服务端一个 ACK,然后自身进入由 FIN-WAIT-2 进入到 TIME-WAIT ,并在超时时间到达后,进入终态 CLOSED 。服务端收到客户端对 FINACK 后,也由 LAST-ACK 转入终态 CLOSED ,到此,整个连接的双向关闭过程终结了。下面看服务端收到客户端对 FINACK,由 LAST-ACK 转入终态 CLOSED 的代码细节:

    xxx_nic_interrput()
    	...
    	tcp_v4_rcv()
    		...
    	lookup:
    		/* 用 【源、目标IP】和【源、目标端口】等找到接收 @skb 的目标通信套接字 */
    		sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,
    			       th->dest, sdif, &refcounted);
    		...
    	process:
    		...
    		if (!sock_owned_by_user(sk)) {
    			ret = tcp_v4_do_rcv(sk, skb);
    				...
    				if (tcp_rcv_state_process(sk, skb)) {
    					rsk = sk;
    					goto reset;
    				}
    				return 0;
    				...
    		} else if (tcp_add_backlog(sk, skb)) {
    			...
    		}
    	...
    
    // 服务端: LAST-ACK => CLOSED
    tcp_rcv_state_process(sk, skb)
    	...
    	switch (sk->sk_state) {
    	...
    	case TCP_LAST_ACK:
    		if (tp->snd_una == tp->write_seq) {
    			tcp_update_metrics(sk);
    			tcp_done(sk);
    				tcp_set_state(sk, TCP_CLOSE); // 服务端: LAST-ACK => CLOSED
    				tcp_clear_xmit_timers(sk);
    				...
    			goto discard;
    		}
    		break;
    	...
    	}
    	...
    • 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

    到此,TCP 连接断开的四次挥手过程已经分析完毕。细心的读者可能会发现,示例代码中的 server_fd 还没有关闭。是的,但本文不打算对此进行分析,相信有了前面的基础,读者自行分析有不会是多困难的事情。

    4. 抓包三握四挥过程示例

    可通过工具 tcpdump 观察 TCP 三握四挥 的过程,请参考博文 Linux: tcpdump抓包示例
    当然,Wireshark 可能是更好的选择。

    5. 参考资料

    rfc793: https://www.rfc-editor.org/rfc/rfc793.html
    https://mp.weixin.qq.com/s/tCXH8BTrgYaVmwVx_Ek1qA

  • 相关阅读:
    最长的可整合子数组的长度
    ArrayUtils使用
    看我如何连夜自建网站背刺我的求职对手们
    秋招应届毕业生求职 如何通过在线测评?
    微信小程序 -- 页面间通信
    资本视角下的Web3全家桶与Web3语境下的元宇宙
    flutter web 优化和flutter_admin_template
    【刷题篇】笔试真题
    实现性别平等,我们绝不能再等132年
    【Kotlin】从Java开发视角出发了解Kotlin
  • 原文地址:https://blog.csdn.net/JiMoKuangXiangQu/article/details/133276410