文章目录
- 1. 前言
- 2. 什么是 TCP 序列号?
- 3. TCP 序号 的 初始值设置 和 后续变化过程
- 3.1 三次握手 连接建立 期间 客户端 和 服务端 序号 的 变化过程
- 3.1.1 客户端 socket 初始序号 的 建立
- 3.1.2 服务端 socket 初始序号 的 建立
- 3.1.3 客户端 socket 接收 服务端 SACK,然后回复 服务端 ACK
- 3.1.4 服务端 接收 客户端 ACK,完成 三次握手
- 3.1.5 三次握手期间的序号变化总结
- 3.2 连接建立后,数据 通信 过程中 序号 的变化
- 3.3 四次挥手 连接断开 期间 序号 的变化
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 什么是 TCP 序列号?
TCP 序列号
,是为 TCP 协议通信数据中的每一个字节赋予一个唯一编号,其作用可以概括如下:
1. 数据分段(Segment)与重组一次发送的数据长度如果超过设定的 MSS(Maximum Segment Size),就会被分成多个带有 TCP 协议头的段(Segment)分别发送出去;接收端需将接收的分段(Segment)数据按每个 TCP 数据段(Segment)头部中的序列号来进行重组。
2. 数据的可靠性传输接收端在收到发送端的数据后,通过 ACK 返回接收数据的序列号,这样发送端就可以确认数据已抵达接收端。
3. 流量控制TCP 通过滑动窗口来进行流量控制。通信两端各自维护自身的 发送 和 接收滑动窗口大小,并将自身接收滑动窗口大小 和 序列号 发送给对端,让对端知道当前最大可以发送的数据量。
4. 防止重复报文TCP 有超时重发机制,如果发送的数据超过一定时间没有收到对方的确认ACK,则会认为数据可能丢失而进行重发。在有些情形下,数据并没有丢失,只是因为某些原因导致在发送路途中消耗时间过长,如果这个时间超过了 TCP 的超时重发时间,则接收端会重复接收到同一数据,这时可通过丢弃具有相同序列号的数据来去重。
一图胜千言,还是上一张图来直观感受下:
上图是 TCP 协议数据头部,红框中的部分:序号
表示发送端所发送数据的序列号
;确认号
表示接收端已收到发送端序号为确认号之前
的所有数据,回送确认号告诉发送端,可以继续发送序号从确认号开始的数据。TCP 序列号以一个 32 位无符号数
表示,最大值为 2^32 - 1,到达最大值后回卷到 0
。
3. TCP 序号 的 初始值设置 和 后续变化过程
从前面的章节中,我们对 TCP 序号
有了一个初步了解,本文剩余部分将结合图示和内核源码,来说说 连接建立初期 TCP 序号初始值的设定过程
、以及 连接建立后 TCP 通信当中序号的变化过程
。
在正式讨论 TCP 序号
的相关细节前,首先要明确的是,TCP 序号 是基于每 socket 进行维护的,即每个 socket 都有自己独立的序号
,从后面的代码分析我们将看到这一点。
本文以典型的 TCP 服务端
和 客户端
通信过程,说明 TCP 序号
的维护细节。本文基于 Linux 4.14
内核源码进行分析。先上两张图,分别描述了 TCP 套接字状态机
和 TCP socket 通信序列号变化过程(包括 三握四挥、数据传输)
,如下:
3.1 三次握手 连接建立 期间 客户端 和 服务端 序号 的 变化过程
TCP 服务端
和 客户端
socket 各自的 序号初始值
在连接建立的 三次握手
过程中建立,分析过程参考上面的第二张图
进行(我们假定 服务端 当前处于 LISTEN 态(TCP_LISTEN)
(即已经调用了 listen()
)。
3.1.1 客户端 socket 初始序号 的 建立
客户端 socket
通过 connect()
系统调用,构建 SYN 包
向 服务端
发起连接请求,其 初始序号
建立的主要流程如下:
sys_connect()...tcp_v4_connect()...tcp_set_state(sk, TCP_SYN_SENT); /* 标记 客户端 socket 进入 TCP_SYN_SENT 状态 */...tp->write_seq = secure_tcp_seq(...); /* 初始化 客户端 socket 序号 为 随机值 x (假定 x = 4065008942) */...tcp_connect(sk);struct tcp_sock *tp = tcp_sk(sk); /* 客户端 TCP socket 对象 */struct sk_buff *buff;...buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true); /* 为 SYN 包分配空间 */.../** 初始化 TCP SYN 包 TCP 控制块(tcp_skb_cb): * . 设置 SYN 包 初始 序号* . 设置 SYN 包 SYN 标志*/tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);TCP_SKB_CB(skb)->tcp_flags = flags; /* 设置 SYN 标志位 */TCP_SKB_CB(skb)->sacked = 0;...TCP_SKB_CB(skb)->seq = seq; /* SYN 包 序号 设为 x (x = 4065008942) */......tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);__tcp_transmit_skb(sk, skb, clone_it, gfp_mask, tcp_sk(sk)->rcv_nxt);struct tcphdr *th; /* TCP 数据包头部 */...th->seq = htonl(tcb->seq); /* 客户端 发往 服务端 SYN 包:序号 为 x (x = 4065008942) */th->ack_seq = htonl(rcv_nxt); /* 客户端 发往 服务端 的 SYN 包:确认号 为 0 *//* 设置 SYN 标志位 */*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) | tcb->tcp_flags);......tp->snd_nxt = tp->write_seq; /* 更新 套接字 的 发送滑动窗口 下一要发送字节 的 序号 */...
从上面的代码分析看到,客户端 socket
在 connect()
中构建一个 SYN 包
,在 SYN 包
构建过程中确定了 初始序号,且设置了 SYN 标志位
。此时 客户端 socket 的状态
也由 CLOSED(TCP_CLOSED)
转化为 SYN-SENT(TCP_SYN_SENT)
。用 tcpdump
在 服务端
抓包,结果如下:
00:30:27.921869 IP 169.228.88.168.52524 > 169.228.88.188.8888: Flags [S], seq 4065008942, win 64240, options [mss 1460,sackOK,TS val 3875254422 ecr 0,nop,wscale 7], length 0
可以看到,服务端 169.228.88.188.8888
收到了从 客户端 169.228.88.168.52524
发送的 SYN
包,客户端初始序号
为 x=4065008942
。tcpdump
用 [S]
标记 SYN 包
。
3.1.2 服务端 socket 初始序号 的 建立
服务端
收到 客户端
序号
为 x
的 SYN 包
后,会回复 确认号
为 x+1
的 ACK
给 客户端
;同时构建一个 序号
为 y
的 SYN 包
,向 客户端
发起连接请求。很自然的,服务端
将 回复 ACK
和 请求 SYN
放在同一个 TCP 包里,一同发往 客户端
,而不是分开发送,这就是平常所见的 SACK
包,或者 SYN + ACK
包。前述过程的主要代码流程如下:
/* * 从网卡接收数据中断接口开始: * 服务端 收到 客户端 的 SYN 连接请求后,回复 客户端 SYN + ACK.*/
xxx_nic_interrput()...ip_rcv()...tcp_v4_rcv()...const struct iphdr *iph; /* IP 头部 */const struct tcphdr *th; /* TCP 头部 */...struct sock *sk; /* TCP 服务端 socket 对象 */.../* 找到 TCP 服务端 socket 对象 */th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);lookup:sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, sdif, &refcounted); .../* 从 客户端 socket 发送的 SYN 包 提取 序号 x (x = 4065008942)、确认号 等(到 TCP 控制块 struct tcp_skb_cb) */th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);tcp_v4_fill_cb(skb, iph, th);TCP_SKB_CB(skb)->seq = ntohl(th->seq); /* TCP_SKB_CB(skb)->seq = x; (x = 4065008942) */...TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq); /* 0 */...TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th); /* SYN */TCP_SKB_CB(skb)->tcp_tw_isn = 0;......if (sk->sk_state == TCP_LISTEN) { /* TCP 服务端 socket 当前处于 LISTEN 状态(TCP_LISTEN) */ret = tcp_v4_do_rcv(sk, skb);...tcp_rcv_state_process(sk, skb))struct tcp_sock *tp = tcp_sk(sk);...const struct tcphdr *th = tcp_hdr(skb);...switch (sk->sk_state) {...case TCP_LISTEN: /* TCP 服务端 socket 当前处于 LISTEN 状态(TCP_LISTEN) */...if (th->syn) { /* 是 客户端 socket 发送的 SYN 包(设置了 SYN 标志位) */.../* 调用 tcp_v4_conn_request(), 见后续分析 */acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;...consume_skb(skb); /* 消耗 客户端 socket 的 SYN 包 */return 0;}......}......}/* 接上面的分析 */
acceptable = tcp_v4_conn_request(sk, skb) >= 0;tcp_conn_request(&tcp_request_sock_ops, &tcp_request_sock_ipv4_ops, sk, skb);...__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn; // 0.../* 服务端 TCP socket, 在关闭前会一直处于 TCP_LISTEN 态, 通过 accept() 接收客户端的 SYN 连接请求 */struct tcp_sock *tp = tcp_sk(sk);.../* 即将 在 服务端 新建一个 socket 对象, 和 客户端 发起连接 的 socket 通信 */struct request_sock *req;.../** !!!* 为 客户端 连接请求 分配 轻量 socket 对象 struct request_sock.* 记住 这个 轻量 socket 对象,它将用于 服务端 在接收 客户端 ACK 的处理.*/req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie); /* (1) */struct request_sock *req = reqsk_alloc(ops, sk_listener, attach_listener);struct request_sock *req;req = kmem_cache_alloc(ops->slab, GFP_ATOMIC | __GFP_NOWARN);...if (attach_listener) {.../* * 记录 客户端 连接信息 的 轻量 socket 是 向 服务端 socket 对象 @sk_listener* 发起的,即 @req->rsk_listener 记录了 服务端 socket 对象。*/req->rsk_listener = sk_listener;}...if (req) {struct inet_request_sock *ireq = inet_rsk(req);.../* 记录 客户端 连接的 轻量 socket 初始为 SYN-RECEIVED(TCP_NEW_SYN_RECV) 态 */ireq->ireq_state = TCP_NEW_SYN_RECV;write_pnet(&ireq->ireq_net, sock_net(sk_listener));ireq->ireq_family = sk_listener->sk_family;}...tcp_rsk(req)->af_specific = af_ops; /* &tcp_request_sock_ipv4_ops */...tcp_openreq_init(req, &tmp_opt, skb, sk);...req->rsk_rcv_wnd = 0;.../* 记录 客户端 的 初始序号 x (x = 4065008942) */tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq; /* tcp_rsk(req)->rcv_isn = x; *//* 服务端 期待 收到 的 下一 客户端 数据的 序号 x + 1 (x + 1 = 4065008942 + 1 = 4065008943) */tcp_rsk(req)->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; /* tcp_rsk(req)->rcv_nxt = x + 1; */....../* 为 服务端 新建的、用来和 客户端 通信的 socket 生成 初始 序号(ISN: Initial Sequence Number) */if (!want_cookie && !isn) {...isn = af_ops->init_seq(skb); /* tcp_v4_init_seq() */tcp_v4_init_seq(skb)secure_tcp_seq(...)}/* 设定 服务端 新建的、用来和 客户端 通信 socket 的 初始 序号 y (假定 y = 1093122830) */tcp_rsk(req)->snt_isn = isn;...if (fastopen_sk) {...} else {if (!want_cookie)/* 将记录 客户端 连接的 轻量级 的 socket 添加到 服务端 socket 的 SYN 队列(半连接队列) */inet_csk_reqsk_queue_hash_add(sk, req, tcp_timeout_init((struct sock *)req));/* 服务端 回复 客户端 ACK,同时发送 SYN 连接请求 (tcp_v4_send_synack()) */af_ops->send_synack(sk, dst, &fl, req, &foc,!want_cookie ? TCP_SYNACK_NORMAL : TCP_SYNACK_COOKIE);tcp_v4_send_synack(sk, dst, &fl, req, &foc, TCP_SYNACK_NORMAL).../** 构建 SYN + ACK 包: * . ACK 是对 客户端 SYN 连接请求的回复;* . SYN 是 服务端 向 客户端 发起的连接请求.*/skb = tcp_make_synack(sk, dst, req, foc, synack_type);struct tcphdr *th;...skb = alloc_skb(MAX_TCP_HEADER, GFP_ATOMIC); /* 分配 skb 空间 */...th = (struct tcphdr *)skb->data;memset(th, 0, sizeof(struct tcphdr));th->syn = 1; /* 设置 SYN 标志位 */th->ack = 1; /* 设置 ACK 标志位 */.../* th->seq = y; (y = 1093122830) */th->seq = htonl(tcp_rsk(req)->snt_isn);/* th->ack_seq = x + 1; (x + 1 = 4065008942 + 1 = 4065008943) */th->ack_seq = htonl(tcp_rsk(req)->rcv_nxt);...if (skb) {.../* 服务端 回复 客户端 SYN 请求 ACK, 同时向 客户端 发送 连接请求 SYN */err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,ireq->ir_rmt_addr,rcu_dereference(ireq->ireq_opt));...}}
3.1.3 客户端 socket 接收 服务端 SACK,然后回复 服务端 ACK
接下来 客户端 socket
收到 服务端 的 SYN + ACK
包,其中 ACK
是 服务端 对 客户端 SYN
的回复,而 SYN
是来自 服务端 的连接请求。从 3.1.1
的分析了解到,客户端套接字当前处于 SYN-SENT(TCP_SYN_SENT)
状态。来看 客户端 收取 服务端 ACK
并 回复 服务端 SYN
的 ACK
的代码细节:
/** 从网卡接收数据中断接口开始:* 客户端 接收 服务端 对 自身 SYN 请求的 ACK, 并对 服务端 的 SYN 请求 回复 ACK.*/
xxx_nic_interrput()...ip_rcv()tcp_v4_rcv()...if (!sock_owned_by_user(sk)) {ret = tcp_v4_do_rcv(sk, skb);...tcp_rcv_state_process(sk, skb)...switch (sk->sk_state) {...case TCP_SYN_SENT: /* 客户端 socket 当前处于 SYN-SENT(TCP_SYN_SENT) 态 */queued = tcp_rcv_synsent_state_process(sk, skb, th); /* 见后续分析 */...return 0;...}} else if (tcp_add_backlog(sk, skb)) {...}.../* 接上面的分析 */
queued = tcp_rcv_synsent_state_process(sk, skb, th);...if (th->ack) { /* 客户端 收到 服务端 对 SYN 回复的 SYN + ACK */...if (!th->syn) /* 不是 (SYN + ACK) */goto discard_and_undo;.../* 客户端 期待的 服务端 的下一数据 序号 y + 1 */tp->rcv_nxt = TCP_SKB_CB(skb)->seq + 1; /* tp->rcv_nxt = y + 1; (y + 1 = 1093122830 + 1 = 1093122831) */tp->rcv_wup = TCP_SKB_CB(skb)->seq + 1;.../** 客户端 完成 连接建立工作:* 客户端 socket 由 SYN-SENT(TCP_SYN_SENT) 转为 ESTABLISHED(TCP_ESTABLISHED) 态. */tcp_finish_connect(sk, skb);...tcp_set_state(sk, TCP_ESTABLISHED); /* 客户端 socket 进入 ESTABLISHED(TCP_ESTABLISHED) 态 */......if (...) {...} else {tcp_send_ack(sk); /* 构建 + 向 服务端 回送 ACK 包 */__tcp_send_ack(sk, tcp_sk(sk)->rcv_nxt);struct sk_buff *buff;...buff = alloc_skb(MAX_TCP_HEADER, ...);...tcp_init_nondata_skb(buff, tcp_acceptable_seq(sk), TCPHDR_ACK);...__tcp_transmit_skb(sk, buff, 0, (__force gfp_t)0, rcv_nxt);...struct tcp_skb_cb *tcb;...struct tcphdr *th;...tcb = TCP_SKB_CB(skb);...th = (struct tcphdr *)skb->data;.../* th->seq = x + 1; (x + 1 = 4065008942 + 1 = 4065008943) */th->seq = htonl(tcb->seq);/* th->ack_seq = y + 1; (y + 1 = 1093122830 + 1 = 1093122831) */th->ack_seq = htonl(rcv_nxt); /* 设置 TCP 头部的 ACK 标记 */*(((__be16 *)th) + 6) = htons(((tcp_header_size >> 2) << 12) | tcb->tcp_flags);.../* 将 ACK 向下传递给 网络层 发送出去 */err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl); /* ip_queue_xmit() */}...}
至此,客户端 ==> 服务端 的 单边连接
已经建立,客户端 socket
已经进入 ESTABLISHED(TCP_ESTABLISHED)
状态。用 tcpdump
在 服务端
抓包,结果如下:
00:30:28.005237 IP 169.228.88.188.8888 > 169.228.88.168.52524: Flags [S.], seq 1093122830, ack 4065008943, win 65160, options [mss 1460,sackOK,TS val 3225418910 ecr 3875254422,nop,wscale 5], length 0
可以看到,服务端 169.228.88.188.8888
向 客户端 169.228.88.168.52524
回复了 一个 SACK 包(SYN + ACK)
,服务端初始序号
为 y=1093122830
,确认号
为 x+1=4065008943
。tcpdump
用 [S.]
标记 SACK
包。
3.1.4 服务端 接收 客户端 ACK,完成 三次握手
从上一小节 3.1.3
了解到,客户端 ==> 服务端 的 单边连接
已经建立,但 服务端 ==> 客户端 的 单边连接
尚未得到确认(即 服务端 还没有收取 客户端 对 SYN
的 ACK
回复)。下面来看 服务端
接收 客户端
ACK
完成 三次握手
的代码细节:
xxx_nic_interrput()...ip_rcv()tcp_v4_rcv()...const struct iphdr *iph;const struct tcphdr *th;......th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);lookup:/* * !!! * 注意,这里查找到的 socket 对象,不再是 服务端 socket 套接字,* 而是 服务端 收到 客户端 SYN 时 新建的、记录了 客户端连接信息* 的 轻量 socket 对象 struct request_sock ,即 3.1.2 代码分* 析注释中、 标记 (1) 处建立的 socket 对象。*/sk = __inet_lookup_skb(&tcp_hashinfo, skb, __tcp_hdrlen(th), th->source,th->dest, sdif, &refcounted);...if (sk->sk_state == TCP_NEW_SYN_RECV) {struct request_sock *req = inet_reqsk(sk); /* 服务端 记录 客户端连接信息的 轻量 socket */sk = req->rsk_listener; /* sk = 服务端 监听 socket */...if (!tcp_filter(sk, skb)) {th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);tcp_v4_fill_cb(skb, iph, th);/* TCP_SKB_CB(skb)->seq = x + 1; (x + 1 = 4065008942 + 1 = 4065008943) */TCP_SKB_CB(skb)->seq = ntohl(th->seq);/* TCP_SKB_CB(skb)->ack_seq = y + 1; (y + 1 = 1093122830 + 1 = 1093122831) */TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);...TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th); /* ACK */TCP_SKB_CB(skb)->tcp_tw_isn = 0;......}...}
至此,客户端 <==> 服务端 的双向连接
已经建立完成。用 tcpdump
在 服务端
抓包,结果如下:
00:30:28.006550 IP 169.228.88.168.52524 > 169.228.88.188.8888: Flags [.], ack 1093122831, win 502, options [nop,nop,TS val 3875254506 ecr 3225418910], length 0
可以看到,服务端 169.228.88.188.8888
收到 客户端 169.228.88.168.52524
发送的 ACK
,确认号
为 y+1 = 1093122831
。tcpdump
用 [.]
标识 ACK
。
3.1.5 三次握手期间的序号变化总结
下面描述中的 seq
表示 序号
,ack_req
表示 确认号
,flags
表示 TCP 协议头部标记(SYN,ACK 等)
。
1. 客户端 向 服务端 发起 SYN 连接请求,SYN 包的 TCP 头部中: seq=x, ack_req=0, flags=SYN;
2. 服务端 收到 客户端 的 SYN 后,回复 客户端 SACK 包(SYN+ACK),SACK 包的 TCP 头部中: seq=y, ack_req=x+1, flags=SYN|ACK;
3. 客户端 收到 的 SACK 后,回复 服务端 一个 ACK 包,ACK 包的 TCP 头部中:seq=x+1, ack_req=y+1 。
可以看到,通信两端(服务端 和 客户端)
建立 初始序号
后,在 三次握手 期间,各自的 SYN 消耗了 1 个序号
,最终,通信两端 的 序号 分别 停在了 x+1
和 y+1
上。
3.2 连接建立后,数据 通信 过程中 序号 的变化
在连接建立后,通信两端分别在当前序号 x+1
和 y+1
的基础上,每次以发送数据长度递增序号
,发往对端数据的 确认号 为 接收到的对端数据中 序号 加 1
。看一下 tcpdump
抓包情况:
00:30:57.585899 IP 169.228.88.168.52524 > 169.228.88.188.8888: Flags [P.], seq 4065008943:4065009967, ack 1093122831, win 502, options [nop,nop,TS val 3875284086 ecr 3225418910], length 1024
00:30:57.605209 IP 169.228.88.188.8888 > 169.228.88.168.52524: Flags [.], ack 4065009967, win 2005, options [nop,nop,TS val 3225448498 ecr 3875284086], length 0
可以看到,客户端 169.228.88.168.52524
向 服务端 169.228.88.188.8888
,发送了 1024
个字节,数据 序号 位于 半开半闭
区间 [x+1=4065008943
, x+1024=4065009967
) (不包括 4065009967
);而 服务端 169.228.88.188.8888
确认收到 客户端 169.228.88.168.52524
的所有 1024 个字节后,回复 客户端 169.228.88.168.52524
一个 ACK
,其 确认号为 x+1024=4065009967
。
3.3 四次挥手 连接断开 期间 序号 的变化
本文不对 四次挥手
期间 序号 的变化过程进行分析,感兴趣的读者可参考博文 Linux:TCP三握四挥简析,自行阅读源码分析。看一下 tcpdump
抓包情况:
00:30:57.605252 IP 169.228.88.168.52524 > 169.228.88.188.8888: Flags [F.], seq 4065009967, ack 1093122831, win 502, options [nop,nop,TS val 3875284086 ecr 3225418910], length 0
00:30:57.669707 IP 169.228.88.188.8888 > 169.228.88.168.52524: Flags [.], ack 4065009968, win 2005, options [nop,nop,TS val 3225448552 ecr 3875284086], length 0
00:31:02.468946 IP 169.228.88.188.8888 > 169.228.88.168.52524: Flags [F.], seq 1093122831, ack 4065009968, win 2005, options [nop,nop,TS val 3225453368 ecr 3875284086], length 0
00:31:02.469779 IP 169.228.88.168.52524 > 169.228.88.188.8888: Flags [.], ack 1093122832, win 502, options [nop,nop,TS val 3875288970 ecr 3225453368], length 0
可以看到:
1. `客户端 169.228.88.168.52524` 主动发起关闭(调用 `close()`),向 `服务端 169.228.88.188.8888` 发送 `序号` 为 `x+1024=4065009967`、`确认号` 为 `y+1=1093122831` 的 `FIN` 包。`客户端` 发送了 1024 个字节后,再没有数据发送,所以 `FIN` 包 序号 紧接数据序号 之后;同时由于 `服务端` 没有 发送数据,所以 `确认号` 没有变化。
2. `服务端 169.228.88.188.8888` 回复 `客户端 169.228.88.168.52524` 的 `FIN` 包 一个 `ACK` ,`确认号` 为 `x+1024+1=4065009968`。至此,`客户端 到 服务端 的单向连接 已经断开`。
3. `服务端 169.228.88.188.8888` 向 `客户端 169.228.88.168.52524` 发送 FIN 包,请求断开连接,该包的 `序号` 为y+1=1093122831,确认号 为 `x+1024+1=4065009968`,因为 客户端 到 服务端 的单向连接 已经断开,不会再有从 客户端到 服务端 的数据,所以 确认号 不再变换。
4. `客户端 169.228.88.168.52524` 回复 `服务端 169.228.88.188.8888` 的 `FIN` 包 一个 `ACK` ,`确认号` 为 `y+1+1=1093122832`。至此,`服务端 到 客户端 的单向连接 已经断开`,于是整个连接彻底断开。