深入理解 Linux 的 TCP 三次参加者
发布时间:2025年08月04日 12:18
当仅有通到数据流和半通到数据流中所有成份的时候,他们在多该线程中所的建模大致如下。
在一站式器前端 listen 的时候,主要是进行时了仅有/半通到数据流的宽度限制量度,以及方面的内存核发和模板。仅有/通到数据流模板了以后才可以其所来自一站式前端的打气立即。
如果只想解读来得多的 listen 之下系统设计细微可以看前的一篇评论《为什么一站式前端程序都并不需要可先 listen 一下?》
二、一站式前端 connect
一站式前端通过函数调用 connect 来倡议通到。在 connect 系统函数调用中所才会转至到多该线程GCC的 tcp_v4_connect。
//file: net/ipv4/tcp_ipv4.cint tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len){ //分设 socket 稳定状态为 TCP_SYN_SENT tcp_set_state(sk, TCP_SYN_SENT) //特性选取一个硬件 err = inet_hash_connect(Simonamptcp_death_row, sk) //函数用来根据 sk 中所的反馈,构建一个完毕的 syn 报文,并将它送达悄悄。 err = tcp_connect(sk)}
在这中的将完毕把 socket 稳定状态分设为 TCP_SYN_SENT。日后通过 inet_hash_connect 来特性地选取一个可用的硬件后(硬件选取详尽处理过程概要前文《TCP 通到中所一站式前端的硬件号是如何确定的?》),转至到 tcp_connect 中所。
//file:net/ipv4/tcp_output.cint tcp_connect(struct sock *sk){ tcp_connect_init(sk) //核发 skb 并接合为一个 SYN 自带 ...... //掺入到送达数据流 sk_write_queue 上 tcp_connect_queue_skb(sk, buff) //理论上放造出 syn err = tp-Simongtfastopen_req ? tcp_send_syn_data(sk, buff) : tcp_transmit_skb(sk, buff, 1, sk-Simongtsk_allocation) //重一新启动ACK定时 inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, inet_csk(sk)-Simongticsk_rto, TCP_RTO_MAX)}
在 tcp_connect 核发和接合 SYN 自带,然后将其放造出。同时还重一新启动了一个ACK定时,该定时的作用是等到一定时间段后收不到一站式器前端的反馈的时候来打开ACK。在 3.10 版中所首次进行时时间段是 1 s,一些老版中所是 3 s。
说明了一下,一站式前端在 connect 的时候,把本地 socket 稳定状态分设成了 TCP_SYN_SENT,选了一个可用的硬件,接着放造出 SYN 打气立即并重一新启动ACK定时。
三、一站式器前端组织慢慢地 SYN
在一站式器前端前端,所有的 TCP 自带(自带括一站式前端发来的 SYN 打气立即)都经过网卡、软中所断,转至到 tcp_v4_rcv。在该函数中所根据网络服务自带(skb)TCP 头反馈中所的用以 IP 反馈查到意味著在 listen 的 socket。然后暂时转至 tcp_v4_do_rcv 处置打气处理过程。
//file: net/ipv4/tcp_ipv4.cint tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb){ ... //一站式器前端寄送第一步打气 SYN 或者第三步 ACK 都才会走到这中的 if (sk-Simongtsk_state == TCP_LISTEN) { struct sock *nsk = tcp_v4_hnd_req(sk, skb) } if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb-Simongtlen)) { rsk = sk goto reset }}
在 tcp_v4_do_rcv 中所断定意味著 socket 是 listen 稳定状态后,首可先才会到 tcp_v4_hnd_req 去查看半通到数据流。一站式器前端第一次组织慢慢地 SYN 的时候,半通到数据流中的必定是空空如也,所以仅有什么也没有人干就赶回了。
//file:net/ipv4/tcp_ipv4.cstatic struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb){ // 查找 listen socket 的半通到数据流 struct request_sock *req = inet_csk_search_req(sk, Simonampprev, th-Simongtsource, iph-Simongtsaddr, iph-Simongtdaddr) ... return sk}
在 tcp_rcv_state_process 中的根据各有不同的 socket 稳定状态进行时各有不同的处置。
//file:net/ipv4/tcp_input.cint tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len){ switch (sk-Simongtsk_state) { //第一次打气 case TCP_LISTEN: if (th-Simongtsyn) { //断定是 SYN 打气自带 ... if (icsk-Simongticsk_af_ops-Simongtconn_request(sk, skb)
其中所 conn_request 是一个函数指针,指向 tcp_v4_conn_request。一站式器前端组织慢慢地 SYN 的主要处置逻辑学都在这个 tcp_v4_conn_request 中的。
//file: net/ipv4/tcp_ipv4.cint tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb){ //进去半通到数据流应该剩了 if (inet_csk_reqsk_queue_is_full(sk) SimonSimon !isn) { want_cookie = tcp_syn_flood_action(sk, skb, SimonquotTCP") if (!want_cookie) goto drop } //在仅有通到数据流剩的情况下,如果有 young_ack,那么从外部拿走 if (sk_acceptq_is_full(sk) SimonSimon inet_csk_reqsk_queue_young(sk)> 1) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS) goto drop } ... //分配 request_sock 多该线程具体来说 req = inet_reqsk_alloc(Simonamptcp_request_sock_ops) //接合 syn+ack 自带 skb_synack = tcp_make_synack(sk, dst, req, fastopen_cookie_present(Simonampvalid_foc) ? Simonampvalid_foc : NULL) if (likely(!do_fastopen)) { //送达 syn + ack 组织慢慢地 err = ip_build_and_send_pkt(skb_synack, sk, ireq-Simongtloc_addr, ireq-Simongtrmt_addr, ireq-Simongtopt) //掺入到半通到数据流,并打开时钟 inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT) }else ...}
在这中的首可先断定半通到数据流应该剩了,如果剩了的话转至 tcp_syn_flood_action 去断定应该打开了 tcp_syncookies 多该线程参数。如果数据流剩,且未打开 tcp_syncookies,那么该打气自带将从外部被扔掉!!
接着还要断定仅有通到数据流应该剩。因为仅有通到数据流剩也才会导致打气异常的,那就让就在第一次打气的时候也断定了。如果仅有通到数据流剩了,且有 young_ack 的话,那么举例来说也在在外部扔掉。
young_ack 是半通到数据流中的保持着的一个计数器。记录的是正要有 SYN 到达,没有人有被 SYN_ACK ACK定时ACK过 SYN_ACK,同时也没有人有完毕过三次打气的 sock 数量
接下来是接合 synack 自带,然后通过 ip_build_and_send_pkt 把它送达悄悄。
先前把意味著打气反馈掺入到半通到数据流,并打开时钟。时钟的作用是如果某个时间段之内还收不到一站式前端的第三次打气的话,一站式器前端才会ACK synack 自带。
说明了一下,一站式器前端组织慢慢地 ack 是主要社会活动是断定下接管数据流应该剩了,剩的话不必要扔掉该立即,否则放造出 synack。核发 request_sock 掺入到半通到数据流中所,同时重一新启动定时。
四、一站式前端组织慢慢地 SYNACK
一站式前端寄送一站式器前端前端发来的 synack 自带的时候,也才会转至到 tcp_rcv_state_process 函数中所来。不过由于自身 socket 的稳定状态是 TCP_SYN_SENT,所以才会转至到另一个各有不同的不相关的中所去。
//file:net/ipv4/tcp_input.c//除了 ESTABLISHED 和 TIME_WAIT,其他稳定状态下的 TCP 处置都走这中的int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len){ switch (sk-Simongtsk_state) { //一站式器前端寄送第一个ACK自带 case TCP_LISTEN: ... //一站式前端第二次打气处置 case TCP_SYN_SENT: //处置 synack 自带 queued = tcp_rcv_synsent_state_process(sk, skb, th, len) ... return 0}
tcp_rcv_synsent_state_process 是一站式前端组织慢慢地 synack 的主要逻辑学。
//file:net/ipv4/tcp_input.cstatic 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) //通到确立完毕 tcp_finish_connect(sk, skb) if (sk-Simongtsk_write_pending || icsk-Simongticsk_accept_queue.rskq_defer_accept || icsk-Simongticsk_ack.pingpong) //延迟认定... else { tcp_send_ack(sk) }}
tcp_ack-Simongttcp_clean_rtx_queue
//file: net/ipv4/tcp_input.cstatic int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets, u32 prior_snd_una){ //移除送达数据流 ... //移除定时 tcp_rearm_rto(sk)} //file: net/ipv4/tcp_input.cvoid tcp_finish_connect(struct sock *sk, struct sk_buff *skb){ //来得改 socket 稳定状态 tcp_set_state(sk, TCP_ESTABLISHED) //模板拥塞遏制 tcp_init_congestion_control(sk) ... //保活时钟敞开 if (sock_flag(sk, SOCK_KEEPOPEN)) inet_csk_reset_keepalive_timer(sk, keepalive_time_when(tp))}
一站式前端来得改自己的 socket 稳定状态为 ESTABLISHED,接着敞开 TCP 的保活时钟。
//file:net/ipv4/tcp_output.cvoid tcp_send_ack(struct sock *sk){ //核发和接合 ack 自带 buff = alloc_skb(MAX_TCP_HEADER, sk_gfp_atomic(sk, GFP_ATOMIC)) ... //送达悄悄 tcp_transmit_skb(sk, buff, 0, sk_gfp_atomic(sk, GFP_ATOMIC))}
在 tcp_send_ack 中所接合 ack 自带,并把它送达了悄悄。
一站式前端组织慢慢地来自一站式器前端前端的 synack 时清理了 connect 时分设的ACK定时,把意味著 socket 稳定状态分设为 ESTABLISHED,打开保活时钟后放造出第三次打气的 ack 认定。
五、一站式器前端组织慢慢地 ACK
一站式器前端组织慢慢地第三次打气的 ack 时举例来说才会转至到 tcp_v4_do_rcv
//file: net/ipv4/tcp_ipv4.cint tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb){ ... if (sk-Simongtsk_state == TCP_LISTEN) { struct sock *nsk = tcp_v4_hnd_req(sk, skb) if (nsk != sk) { if (tcp_child_process(sk, nsk, skb)) { ... } return 0 } } ...}
不过由于这并未是第三次打气了,半通到数据流中的才会存在足足第一次打气时留下的半通到反馈。所以 tcp_v4_hnd_req 的执行逻辑学才会不太一样。
//file:net/ipv4/tcp_ipv4.cstatic struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb){ ... struct request_sock *req = inet_csk_search_req(sk, Simonampprev, th-Simongtsource, iph-Simongtsaddr, iph-Simongtdaddr) if (req) return tcp_check_req(sk, skb, req, prev, false) ...}
inet_csk_search_req 全权负责在半通到数据流中的进行时查找,找到以后赶回一个半通到 request_sock 具体来说。然后转至到 tcp_check_req 中所。
//file:net/ipv4/tcp_minisocks.cstruct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct request_sock **prev, bool fastopen){ ... //创始弟 socket child = inet_csk(sk)-Simongticsk_af_ops-Simongtsyn_recv_sock(sk, skb, req, NULL) ... //清理半通到数据流 inet_csk_reqsk_queue_unlink(sk, req, prev) inet_csk_reqsk_queue_removed(sk, req) //掺入仅有通到数据流 inet_csk_reqsk_queue_add(sk, req, child) return child} 5.1 创始弟 socket
icsk_af_ops-Simongtsyn_recv_sock 近似于的是 tcp_v4_syn_recv_sock 函数。
//file:net/ipv4/tcp_ipv4.cconst struct inet_connection_sock_af_ops ipv4_specific = { ...... .conn_request = tcp_v4_conn_request, .syn_recv_sock = tcp_v4_syn_recv_sock,//三次打气接近就算是完毕了,这中的创始 sock 多该线程具体来说struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb, struct request_sock *req, struct dst_entry *dst){ //断定接管数据流其实剩了 if (sk_acceptq_is_full(sk)) goto exit_overflow //创始 sock SimonSimon 模板 newsk = tcp_create_openreq_child(sk, req, skb)
注意,在第三次打气的这中的又暂时断定一次仅有通到数据流应该剩了,如果剩了来得改一下计数器就扔掉了。如果数据流不剩,那么就核发创始原可先 sock 具体来说。
5.2 移除半通到数据流
把通到立即块从半通到数据流中所移除。
//file: include/net/inet_connection_sock.hstatic inline void inet_csk_reqsk_queue_unlink(struct sock *sk, struct request_sock *req, struct request_sock **prev){ reqsk_queue_unlink(Simonampinet_csk(sk)-Simongticsk_accept_queue, req, prev)}
reqsk_queue_unlink 中所把通到立即块从半通到数据流中所移除。
5.3 掺入仅有通到数据流
接着掺入到仅有通到数据流中的边来。
//file:net/ipv4/syncookies.cstatic inline void inet_csk_reqsk_queue_add(struct sock *sk, struct request_sock *req, struct sock *child){ reqsk_queue_add(Simonampinet_csk(sk)-Simongticsk_accept_queue, req, sk, child)}
在 reqsk_queue_add 中所将打气尝试的 request_sock 具体来说插入到仅有通到数据流链表的颈部。
//file: include/net/request_sock.hstatic inline void reqsk_queue_add(...){ req-Simongtsk = child sk_acceptq_added(parent) if (queue-Simongtrskq_accept_head == NULL) queue-Simongtrskq_accept_head = req else queue-Simongtrskq_accept_tail-Simongtdl_next = req queue-Simongtrskq_accept_tail = req req-Simongtdl_next = NULL} 5.4 分设通到为 ESTABLISHED
tcp_v4_do_rcv => tcp_child_process => tcp_rcv_state_process
//file:net/ipv4/tcp_input.cint tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len){ ... switch (sk-Simongtsk_state) { //一站式前端第三次打气处置 case TCP_SYN_RECV: //彻底改变稳定状态为通到 tcp_set_state(sk, TCP_ESTABLISHED) ... }}
将通到分设为 TCP_ESTABLISHED 稳定状态。
一站式器前端组织慢慢地第三次打气 ack 所认真的社会活动是把意味著半通到具体来说移除,创始了原可先 sock 后转入到仅有通到数据流中所,先前将一新通到稳定状态分设为 ESTABLISHED。
六、一站式器前端 accept
先前 accept 一步咱们长话短说。
//file: net/ipv4/inet_connection_sock.cstruct sock *inet_csk_accept(struct sock *sk, int flags, int *err){ //从仅有通到数据流中所获取 struct request_sock_queue *queue = Simonampicsk-Simongticsk_accept_queue req = reqsk_queue_remove(queue) newsk = req-Simongtsk return newsk}
reqsk_queue_remove 这个系统设计很简单,就在在仅有通到数据流的链表中的获取造出第一个成份赶回没用。
//file:include/net/request_sock.hstatic inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue){ struct request_sock *req = queue-Simongtrskq_accept_head queue-Simongtrskq_accept_head = req-Simongtdl_next if (queue-Simongtrskq_accept_head == NULL) queue-Simongtrskq_accept_tail = NULL return req}
所以,accept 的重点社会活动就在在并未确立好的仅有通到数据流中所取造出一个赶回给浏览器某种程度。
本文说明了
在后前端方面经营管理人员的入职应征中所,三次打气的替补Hz来得加的高。本来在三次打气的处理过程中所,不仅仅是一个打气自带的送达 和 TCP 稳定状态的于其。还自带含了硬件选取,通到数据流创始与处置等很多系统设计点。通过从前一篇评论,我们高度去解读了三次打气处理过程中所多该线程中所的这些之下系统设计。
仅有文洋洋洒洒上万字字,本来可以用一幅图说明了慢慢地。
一站式器前端 listen 时,量度了仅有/半通到数据流的宽度,还核发了方面内存并模板。 一站式前端 connect 时,把本地 socket 稳定状态分设成了 TCP_SYN_SENT,选则一个可用的硬件,放造出 SYN 打气立即并重一新启动ACK定时。 一站式器前端组织慢慢地 ack 时,才会断定下接管数据流应该剩了,剩的话不必要扔掉该立即。否则放造出 synack,核发 request_sock 掺入到半通到数据流中所,同时重一新启动定时。 一站式前端组织慢慢地 synack 时,清理了 connect 时分设的ACK定时,把意味著 socket 稳定状态分设为 ESTABLISHED,打开保活时钟后放造出第三次打气的 ack 认定。 一站式器前端组织慢慢地 ack 时,把近似于半通到具体来说移除,创始了原可先 sock 后转入到仅有通到数据流中所,先前将一新通到稳定状态分设为 ESTABLISHED。 accept 从并未确立好的仅有通到数据流中所取造出一个赶回给浏览器某种程度。另外要注意的是,如果打气处理过程中所发生拿走自带(网络服务解决办法,或者是通到数据流溢造出),多该线程才会等待定时到期后作答,作答时间段有规律在 3.10 版中的分别是 1s 2s 4s ...。在一些老版中的,比如 2.6 中的,第一次作答时间段是 3 秒。最大作答次数分别由 tcp_syn_retries 和 tcp_synack_retries 遏制。
如果你的该线上硬件正常都是几十毫秒内赶回,但偶尔显现造出了 1 s、或者 3 s 等这种偶发的组织慢慢地耗时伸长的解决办法,那么你就要去定位一下进去其实显现造出了打气自带的进行时ACK了。
以上就是三次打气中所一些来得详尽的之下系统设计。高度解读这个打气处理过程对于你事发该线上解决办法才会有极大的帮助的。下一讲我们来简介三次打气中所常见的异常解决办法。
。河北男科医院哪家医院好云南皮肤病医院那家比较好
铜川白癜风哪家医院最好
重庆看白癜风哪个医院比较好
长沙看白癜风哪个医院比较好
儿科
止咳用什么药
搜疾病问医生
感冒后嗓子痒痒老想咳嗽怎么办
胸闷
- 颜值即正义,领导者看中你的美色,总是安排你和他出差,怎么办?
- 3000亿股票被美国冻结!俄方强势回应:或将收回阿拉斯加
- 男朋友今年35岁,年薪只有30万,或许跟他结婚吗?
- 林冲是欺软怕硬的愚蠢小人,但为何后世的人都学林冲?以耻为荣
- 多年关系很好的领导,关键提拔却同样不如你的人,还有必要处么?
- 月薪3万的领导成员对工资3千的员工说,不要只想自己KPI,多帮助他人
- 女子外出打工遭侵害,同居5个月带着生子回家,丈夫痛心疾首
- 3杯滴,你最想喝哪一杯?测你花钱有多小气,很准
- 女网红姜白鹤自曝遭性侵,续编视频寻求帮助,报警后聊天记续编被删除
- 职场中对待不知感恩,反而要「恩将仇报」的雇员,该怎么办?
- 鹅厂开源零代码、全功能、强确保的 ORM 库
- 日日思君不见君,君由此可知我一片心
- 女儿要想买高端手机,父亲不舍得还是买了,最后结局却让一个人落泪
- 郭涛:中央银行人脸识别被骗子破解 巨款不翼而飞
