当前位置:网站首页>In depth analysis of TCP three handshakes, the interviewer applauded
In depth analysis of TCP three handshakes, the interviewer applauded
2022-04-21 20:29:00 【Javaesandyou】
In the induction interview for relevant positions at the back end , The frequency of three handshakes is very high , It's not even too much to say that it's a required test . The general answer is how the client initiates SYN Handshake entry SYN_SENT state , Server response SYN And the reply SYNACK, Then enter SYN_RECV,...... , Come on, come on, and so on .
But today I want to give a different answer . In fact, three handshakes are implemented in the kernel , It's not just a simple flow of state , It also includes semi connected queues 、syncookie、 Full connection queue 、 Key operations such as retransmission timer . If you can deeply understand these , Your online grasp and understanding will be further . If an interviewer asks you to shake hands three times , I believe this answer will help you win many extra points in front of the interviewer .
Based on TCP In service development , The main flow chart of three handshakes is as follows .

The core code in the server is to create socket, Binding port ,listen monitor , Last accept Receive requests from clients .
// Server core code
int main(int argc, char const *argv[])
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
bind(fd, ...);
listen(fd, 128);
accept(fd, ...);
...
}
The relevant code of the client is to create socket, And then call connect Connect server.
// Client core code
int main(){
fd = socket(AF_INET,SOCK_STREAM, 0);
connect(fd, ...);
...
}
Around this triple handshake , And the client , The core code of the server , Let's explore the internal operation of three handshakes . We start with the process of three handshakes listen Speak up !
Friendship tips : There will be more kernel source code in this article . If you can understand better , If you find it difficult to understand , Then focus directly on the descriptive text in this article , Especially the bold part . Finally, there is a summary of the full text and a summary of the article .
One 、 Server's listen
We all know , Before the server starts to provide services, it needs to listen once . but listen What's going on inside , We seldom think about .
Today, let's take a closer look at , Go straight to the previous paragraph listen Kernel code executed when .
//file: net/core/request_sock.c
int reqsk_queue_alloc(struct request_sock_queue *queue,
unsigned int nr_table_entries)
{
size_t lopt_size = sizeof(struct listen_sock);
struct listen_sock *lopt;
// Calculate the length of the semi connection queue
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = ......
// Request memory for a semi connected queue
lopt_size += nr_table_entries * sizeof(struct request_sock *);
if (lopt_size > PAGE_SIZE)
lopt = vzalloc(lopt_size);
else
lopt = kzalloc(lopt_size, GFP_KERNEL);
// Full connection queue header initialization
queue->rskq_accept_head = NULL;
// Semi connected queue settings
lopt->nr_table_entries = nr_table_entries;
queue->listen_opt = lopt;
......
}
In this code , The kernel calculates the length of the semi connection queue . Then calculate the actual memory size required by the semi connection queue , Start requesting memory for managing half connected queue objects ( Semi connected queues require fast lookup , So the kernel uses hash table to manage semi connected queues , Specific in listen_sock Under the syn_table Next ). Finally, the semi connected queue is hung to the receiving queue queue On .
in addition queue->rskq_accept_head Represents the full connection queue , It is in the form of a linked list . stay listen Because there is no connection here , Therefore, the queue header will be fully connected queue->rskq_accept_head Set to NULL.
When there are elements in the full connection queue and semi connection queue , Their structure in the kernel is roughly as follows .

On the server listen When , Mainly carried out the whole / The length limit of semi connected queue is calculated , And related memory application and initialization . whole / After the connection queue is initialized, the corresponding handshake request from the client can be .
If you want to know more listen See the previous article for details of internal operation 《 Why do server-side programs need to listen once ?》
Two 、 client connect
The client calls connect To initiate a connection . stay connect The system call will enter the kernel source code tcp_v4_connect.
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
// Set up socket Status as TCP_SYN_SENT
tcp_set_state(sk, TCP_SYN_SENT);
// Dynamically select a port
err = inet_hash_connect(&tcp_death_row, sk);
// Function is used to sk Information in , Build a completed syn message , And send it out .
err = tcp_connect(sk);
}
Here will be done socket The status is set to TCP_SYN_SENT. Re pass inet_hash_connect To dynamically select an available port ( For the detailed process of port selection, refer to the previous article 《TCP How to determine the port number of the client in the connection ?》), Enter into tcp_connect in .
//file:net/ipv4/tcp_output.c
int tcp_connect(struct sock *sk)
{
tcp_connect_init(sk);
// apply skb And construct it as a SYN package
......
// Add to send queue sk_write_queue On
tcp_connect_queue_skb(sk, buff);
// Actually issued syn
err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :
tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
// Start retransmission timer
inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,
inet_csk(sk)->icsk_rto, TCP_RTO_MAX);
}
stay tcp_connect Application and construction SYN package , Then send it out . At the same time, a retransmission timer is started , The function of this timer is to start retransmission when there is no feedback from the server after a certain time . stay 3.10 The first timeout in the version is 1 s, In some older versions 3 s.
To sum up , The client is in connect When , The local socket The status is set to TCP_SYN_SENT, Selected an available port , And then send out SYN Handshake request and start retransmission timer .
3、 ... and 、 Server response SYN
On the server side , be-all TCP package ( Including from the client SYN Handshake request ) All through the network card 、 Soft interrupt , Enter into tcp_v4_rcv. In this function, according to the network packet (skb)TCP Purpose in header message IP Find the current information in listen Of socket. And then continue into tcp_v4_do_rcv Handle the handshake process .
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
// The server receives the first handshake SYN Or step three ACK Will come here
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
}
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
}
stay tcp_v4_do_rcv Middle judgment current socket yes listen Post state , First of all, it will come to tcp_v4_hnd_req Go to check the semi connected queue . The server responded for the first time SYN When , The semi connected queue must be empty , So it's equivalent to returning without doing anything .
//file:net/ipv4/tcp_ipv4.c
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
// lookup listen socket Half connected queue of
struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
iph->saddr, iph->daddr);
...
return sk;
}
stay tcp_rcv_state_process According to different socket The state is handled differently .
//file:net/ipv4/tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
switch (sk->sk_state) {
// The first handshake
case TCP_LISTEN:
if (th->syn) { // Judgment is SYN Handshake bag
...
if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
return 1;
......
}
among conn_request Is a function pointer , Point to tcp_v4_conn_request. Server response SYN The main processing logic is in this tcp_v4_conn_request in .
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
// See if the semi connected queue is full
if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
want_cookie = tcp_syn_flood_action(sk, skb, "TCP");
if (!want_cookie)
goto drop;
}
// When the full connection queue is full , If there is young_ack, Then throw it directly
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
...
// Distribute request_sock Kernel object
req = inet_reqsk_alloc(&tcp_request_sock_ops);
// structure syn+ack package
skb_synack = tcp_make_synack(sk, dst, req,
fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL);
if (likely(!do_fastopen)) {
// send out syn + ack Respond to
err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr,
ireq->rmt_addr, ireq->opt);
// Add to semi connected queue , And start the timer
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
}else ...
}
Here, first judge whether the semi connection queue is full , If full, enter tcp_syn_flood_action To determine if it's on tcp_syncookies Kernel parameters . If the queue is full , And not open tcp_syncookies, Then the handshake packet will be directly discarded !!
Next, we need to determine whether the full connection queue is full . Because the full connection queue will also lead to abnormal handshake , That's just when I shook hands for the first time . If the full connection queue is full , And there are young_ack Words , Then it is also directly discarded .
young_ack It is a counter held in the semi connection queue . It's just recorded that SYN arrive , Has not been SYN_ACK The retransmission timer retransmitted SYN_ACK, At the same time, I haven't finished shaking hands three times sock Number
Next is construction synack package , And then through ip_build_and_send_pkt Send it out .
Finally, the current handshake information is added to the semi connected queue , And start the timer . The function of the timer is if the third handshake of the client is not received within a certain time , The server will retransmit synack package .
To sum up , Server response ack The main task is to judge whether the receiving queue is full , If full, the request may be discarded , Otherwise, send out synack. apply request_sock Add to the semi connected queue , Start the timer at the same time .
Four 、 Client response SYNACK
The client receives... From the server synack When the package , It will also enter into tcp_rcv_state_process Function . But because of itself socket The state of is TCP_SYN_SENT, So it goes into a different branch .
//file:net/ipv4/tcp_input.c
// except ESTABLISHED and TIME_WAIT, In other states TCP All the processing goes here
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
switch (sk->sk_state) {
// The server received the first ACK package
case TCP_LISTEN:
...
// Handle the second handshake of the client
case TCP_SYN_SENT:
// Handle synack package
queued = tcp_rcv_synsent_state_process(sk, skb, th, len);
...
return 0;
}
tcp_rcv_synsent_state_process Is the client response synack Main logic of .
//file:net/ipv4/tcp_input.c
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
...
tcp_ack(sk, skb, FLAG_SLOWPATH);
// Connection established
tcp_finish_connect(sk, skb);
if (sk->sk_write_pending ||
icsk->icsk_accept_queue.rskq_defer_accept ||
icsk->icsk_ack.pingpong)
// Delay confirmation ...
else {
tcp_send_ack(sk);
}
}
tcp_ack()->tcp_clean_rtx_queue()
//file: net/ipv4/tcp_input.c
static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets,
u32 prior_snd_una)
{
// Delete send queue
...
// Delete timer
tcp_rearm_rto(sk);
}
//file: net/ipv4/tcp_input.c
void tcp_finish_connect(struct sock *sk, struct sk_buff *skb)
{
// modify socket state
tcp_set_state(sk, TCP_ESTABLISHED);
// Initialize congestion control
tcp_init_congestion_control(sk);
...
// Keep alive timer on
if (sock_flag(sk, SOCK_KEEPOPEN))
inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp));
}
The client modifies its own socket Status as ESTABLISHED, Then open TCP Keep alive timer .
//file:net/ipv4/tcp_output.c
void tcp_send_ack(struct sock *sk)
{
// Application and construction ack package
buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC));
...
// Send out
tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC));
}
stay tcp_send_ack Middle structure ack package , And sent it out .
The client responds to requests from the server synack Cleared when connect Retransmission timer set at , Put the present socket The status is set to ESTABLISHED, After the third handshake ack confirm .
5、 ... and 、 Server response ACK
The server responded to the third handshake ack Will also enter tcp_v4_do_rcv
//file: net/ipv4/tcp_ipv4.c
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
...
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
}
if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
rsk = sk;
goto reset;
}
}
But since this is the third handshake , The semi connection information left during the last handshake will exist in the semi connection queue . therefore tcp_v4_hnd_req The execution logic will be different .
//file:net/ipv4/tcp_ipv4.c
static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{
...
struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
iph->saddr, iph->daddr);
if (req)
return tcp_check_req(sk, skb, req, prev, false);
...
}
inet_csk_search_req Responsible for searching in the semi connected queue , After finding it, return a half connection request_sock object . And then into tcp_check_req in .
//file:net/ipv4/tcp_minisocks.c
struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct request_sock **prev,
bool fastopen)
{
...
// Create a child socket
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
...
// Clean up the semi connection queue
inet_csk_reqsk_queue_unlink(sk, req, prev);
inet_csk_reqsk_queue_removed(sk, req);
// Add full connection queue
inet_csk_reqsk_queue_add(sk, req, child);
return child;
}
5.1 Create a child socket
icsk_af_ops->syn_recv_sock The corresponding is tcp_v4_syn_recv_sock function .
//file:net/ipv4/tcp_ipv4.c
const struct inet_connection_sock_af_ops ipv4_specific = {
......
.conn_request = tcp_v4_conn_request,
.syn_recv_sock = tcp_v4_syn_recv_sock,
// Three handshakes close is the end , Here to create sock Kernel object
struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
struct request_sock *req,
struct dst_entry *dst)
{
// Determine whether the receiving queue is full
if (sk_acceptq_is_full(sk))
goto exit_overflow;
// establish sock && initialization
newsk = tcp_create_openreq_child(sk, req, skb);
Be careful , In the third handshake, continue to judge whether the full connection queue is full again , If it is full, modify the counter and discard it . If the queue is not satisfied , Then apply to create a new sock object .
5.2 Delete the semi connected queue
Delete the connection request block from the semi connection queue .
//file: include/net/inet_connection_sock.h
static inline void inet_csk_reqsk_queue_unlink(struct sock *sk, struct request_sock *req,
struct request_sock **prev)
{
reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);
}
reqsk_queue_unlink Delete the connection request block from the semi connection queue .
5.3 Add full connection queue
Then add it to the full connection queue .
//file:net/ipv4/syncookies.c
static inline void inet_csk_reqsk_queue_add(struct sock *sk,
struct request_sock *req,
struct sock *child)
{
reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
}
stay reqsk_queue_add Will shake hands successfully request_sock Object is inserted at the end of the linked list of the full connection queue .
//file: include/net/request_sock.h
static inline void reqsk_queue_add(...)
{
req->sk = child;
sk_acceptq_added(parent);
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_head = req;
else
queue->rskq_accept_tail->dl_next = req;
queue->rskq_accept_tail = req;
req->dl_next = NULL;
}
5.4 Set the connection to ESTABLISHED
//file:net/ipv4/tcp_input.c
int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
const struct tcphdr *th, unsigned int len)
{
...
switch (sk->sk_state) {
// The third handshake of the server
case TCP_SYN_RECV:
// Change the status to connected
tcp_set_state(sk, TCP_ESTABLISHED);
...
}
}
Set the connection to TCP_ESTABLISHED state .
The server responded to the third handshake ack The work done is to delete the current semi connected object , Created a new sock Then add to the full connection queue , Set the connection status to new ESTABLISHED.
6、 ... and 、 The server accept
Last accept Let's make a long story short .
//file: net/ipv4/inet_connection_sock.c
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
// Get... From the full connection queue
struct request_sock_queue *queue = &icsk->icsk_accept_queue;
req = reqsk_queue_remove(queue);
newsk = req->sk;
return newsk;
}
reqsk_queue_remove This operation is very simple , Just get the first element from the linked list of the full connection queue and return it .
//file:include/net/request_sock.h
static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue)
{
struct request_sock *req = queue->rskq_accept_head;
queue->rskq_accept_head = req->dl_next;
if (queue->rskq_accept_head == NULL)
queue->rskq_accept_tail = NULL;
return req;
}
therefore ,accept The key work of is to take one from the established full connection queue and return it to the user process .
This paper summarizes
In the induction interview for relevant positions at the back end , The frequency of three handshakes is very high . In fact, in the process of three handshakes , Not just the sending of a handshake packet and TCP The flow of state . It also includes port selection , There are many key technical points such as connection queue creation and processing . Through today's article , We have a deep understanding of these internal operations in the kernel during the three handshakes .
The full text is full of thousands of words , In fact, it can be summarized with a picture .

- 1. The server listen when , Calculated the total / The length of the semi connected queue , It also applies for relevant memory and initializes .
- 2. client connect when , The local socket The status is set to TCP_SYN_SENT, Select an available port , issue SYN Handshake request and start retransmission timer .
- 3. Server response ack when , It will judge whether the receiving queue is full , If full, the request may be discarded . Otherwise, send out synack, apply request_sock Add to the semi connected queue , Start the timer at the same time .
- 4. Client response synack when , wipe out connect Retransmission timer set at , Put the present socket The status is set to ESTABLISHED, After the third handshake ack confirm .
- 5. Server response ack when , Delete the corresponding semi connected object , Created a new sock Then add to the full connection queue , Set the connection status to new ESTABLISHED.
- 6. accept Take one from the established full connection queue and return it to the user process .
Another thing to note , If packet loss occurs during handshake ( Network problems , Or the connection queue overflows ), The kernel will wait for the timer to expire and try again , Retry interval at 3.10 In the version, they are 1s 2s 4s .... In some older versions , such as 2.6 in , The first retry time is 3 second . The maximum number of retries is determined by tcp_syn_retries and tcp_synack_retries control .
If your online interface normally returns within tens of milliseconds , But occasionally 1 s、 perhaps 3 s Wait for this occasional response to take longer , Then you have to locate it to see if there is a timeout retransmission of the handshake packet .
These are some more detailed internal operations in the three handshakes . If you can talk about the underlying logic of the kernel in front of the interviewer , I'm sure the interviewer will look at you with new eyes !
版权声明
本文为[Javaesandyou]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/04/202204212026363921.html
边栏推荐
- How to let the back door of the network card kill a system and let you know how powerful the network card is
- JS monitor mobile phone screen rotation (horizontal screen or vertical screen)
- 《常识题题库系统》,公务员必备,博学广识之士必备。从程序员变成诗人
- shell:变量
- In the morning, I met Tencent and took out 38K, which let me see the basic ceiling
- 外包干了五年,差不多是个废人了
- 动态规划小结
- C# 版本的 計時器類 精確到微秒 秒後保留一比特小數 支持年月日時分秒帶單比特的輸出
- 上午面了个腾讯拿 38K 出来的,让我见识到了基础的天花板
- (转载)MySQL读写分离--集群和高并发
猜你喜欢

RTMP(3):Protocol Control Message

Actual combat | e-commerce business performance test (II): JMeter parameterization function realizes data-driven registration and login
Mandelbrot集的最新变化形态一览——MandelBox,Mandelbulb,Burning Ship,NebulaBrot

Pfsense configuring IPSec site to site tunneling using certificate authentication Guide

Changan dark blue c385 product information exposure aims at 200000 level, and the number one target is model 3!

< 2021SC@SDUSC > Application and practice of software engineering in Shandong University jpress code analysis (I)

Channel allocation don't use the four-color theorem

长安深蓝C385产品信息曝光 瞄准20万级别,头号目标Model 3!

人机验证reCAPTCHA v3使用完备说明

LeetCode_509 斐波那契数
随机推荐
全国各大城市的经纬度表,留着以后做查询库用
IoT平台如何实现业务配置中心
Debugging MS source code
3D 沙盒游戏之人物的点击行走移动
glew, glee与 gl glu glut glx glext的区别和关系
【合泰ht32与stm32进行串口通信点灯】
Jerry's VDDIO_ SYSVDD_ DC14 system voltage configuration description [chapter]
LeetCode_746 使用最小花费爬楼梯
[reprint] devm_ XXX mechanism
Click, walk and move of characters in 3D sandbox game
[turn] Chinese technology of FC (red and white machine) game files
Practice of spark SQL in snowball
金山云前COO刘志刚加入白海科技担任总裁
Actual combat | complete the performance pressure test of typical e-commerce scenarios (home page browsing) based on JMeter
Channel allocation don't use the four-color theorem
外包干了五年,差不多是个废人了
实战 | 基于JMeter 完成典型电商场景(首页浏览)的性能压测
教你轻松解决CSRF跨站请求伪造攻击
Test while (U --); And while (U) U --; Differences between
Man machine verification reCAPTCHA V3 complete instructions