一、TCP报文段结构
1、源端口号和目的端口号都是16位,范围从(1-65535,0不可用)
2、序列号:在建立连接时由内核生成的随机数作为其初始值,通过 SYN 报文传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。
3、确认号:指下一次「期望」收到的数据的序列号,发送端收到接收方发来的 ACK 确认报文以后,就可以认为在这个序号以前的数据都已经被正常接收。用来解决丢包的问题。
4、控制位:用来标识 TCP 报文是什么类型的报文,比如是 SYN 报文、数据报文、ACK 报文,FIN 报文等。
1、TCP序列号和确认号万能公式
机器发送的 TCP 报文公式如下:
- 公式一:序列号 = 上一次发送的序列号 +上一次发送的数据长度 len(数据长度)。特殊情况,如果上一次发送的报文是 SYN 报文或者 FIN 报文,则改为上一次发送的序列号 + 1。
- 公式二:确认号 = 上一次收到的报文中的序列号 + 上一次收到的报文长度len(数据长度)。特殊情况,如果收到的是 SYN 报文或者 FIN 报文,则改为上一次收到的报文中的序列号 + 1。
SYN 报文长度为1
SYNACK报文长度为1
FIN报文长度为1
单纯的ACK报文长度为0
二、TCP建立连接(三次握手)和数据传输
1、连接建立(三次握手)
- 1、初始状态:一开始客户端和服务端都是属于关闭(CLOSE)状态,服务端通过
ServerSocket serverSocket = new ServerSocket(8081,1);开启监听(LISTEN); - 2、第一步,客户端的TCP首先向服务器的TCP发送一个特殊的TCP报文段,该报文段被称为SYN报文段。该报文段中不包含应用层数据,但是会将TCP报文段首部中的一个标志位即SYN比特设置为1;因此这个特殊的报文段被称为SYN报文段;另外,客户端会随机的生成一个初始序号(client_isn),并将此编号放置于该报文段的序列号(Seq)字段中;即Seq= client_isn=x此时客户端会进入SYS_SEND状态。
- 3、第二步、一旦服务器收到了SYN报文,需要确认客户端的报文,会将确认报文中的SYN比特设置为1,ACK比特设置为1,并将该报文中的确认号Ack设置为client_isn+1(公式2),即Ack=client_isn+1,最后服务器会生成自己的初始序列(server_isn),并将之放到报文段的序列号字段上,即Seq=server_isn=y;该报文表示:“我收到了你发起建立连接的SYN分组,该分组带有初始序号client_isn,我同意建立连接,我自己的初始序列是server_isn” 该允许连接的报文段有时被称为SYNACK报文段SYNACK segment,此时服务端进入SYN_RECV状态,并且该连接进入到syn queue 队列中去;
- 4、 第三步、在客户端收到SYNACK报文段后,需要对该SYNACK报文段确认,需要将ACK设置为1,序列号为Seq= client_isn+1 (公式1)确认号为Ack=server_isn+1(公式2);因为此时连接已经建立,所以SYN被设置为0,并且后续的数据发送SYN字段也被设置为0。TCP规定这个报文段可以携带数据也可以不携带数据,如果不携带数据,那么数据长度就是0,根据上述公式1那么下一个数据报文段的序号仍是 seq = client_isn + 1。这时,客户端进入 ESTABLISHED (已连接) 状态。
- 5、服务器收到客户的确认后,也进入 ESTABLISHED 状态。
2、数据传输
客户端发送 10 字节的数据,通常 TCP 数据报文的控制位是 [PSH, ACK],此时该 TCP 数据报文的序列号和确认号分别设置为:
- 1、序列号设置为 client_isn + 1。客户端上一次发送报文是 ACK 报文(第三次握手),该报文的 seq = client_isn + 1,由于是一个单纯的 ACK 报文,没有携带用户数据,所以 len = 0。根据公式 1(序列号 = 上一次发送的序列号 + len),可以得出当前的序列号为 client_isn + 1 + 0,即 client_isn + 1。
- 2、确认号设置为 server_isn + 1。没错,还是和第三次握手的 ACK 报文的确认号一样,这是因为客户端三次握手之后,发送 TCP 数据报文 之前,如果没有收到服务端的 TCP 数据报文,确认号还是延用上一次的,其实根据==公式 2 ==你也能得到这个结论。
可以看到,客户端与服务端完成 TCP 三次握手后,发送的第一个 「TCP 数据报文的序列号和确认号」都是和「第三次握手的 ACK 报文中序列号和确认号」一样的。
如果客户端发送的第三次握手 ACK 报文丢失了,处于 SYN_RCVD 状态服务端收到了客户端第一个 TCP 数据报文会发生什么?
发送的第一个 「TCP 数据报文的序列号和确认号」都是和「第三次握手的 ACK 报文中序列号和确认号」一样的,并且该 TCP 数据报文也有将 ACK 标记位置为 1。所以,服务端收到这个数据报文,是可以正常完成连接的建立,然后就可以正常接收这个数据包了。
三、四次挥手
数据传输阶段结束后,客户端发起了 FIN 报文,请求服务端端开该 TCP 连接,此时就进入了 TCP 四次挥手阶段,如下图:
- 1、客户端发起第一次挥手。FIN设置为1,ACK设置为1,该报文长度为1;
序列号为上一次发送的序列号+上一次发送的数据长度,即Seq=client_isn+11;
确认号是上一次收到的报文中的序列号 +上一次收到的报文长度len,上一次收到的序列号为server_isn+1,因为上一次收到的是单纯的ACK报文,所以长度为0,所以Ack=server_isn+1+0=server_isn+1 - 2、服务端发起第二次挥手。ACK设置为1,该报文长度为0;
序列号为上一次发送的序列号+上一次发送的数据长度,即Seq=server_isn+1,因为上一次收到的是单纯的ACK报文,所以长度为0,所以Seq=server_isn+1+0=server_isn+1;
确认号为上一次收到的报文中的序列号 +上一次收到的报文长度len,上一次收到的是FIN报文,故而长度为1;即Ack=client_isn+11+1=client_isn+12; - 3、服务端发起第三次挥手。SYN设置为1,该报文长度为1;
序列号为上一次发送的序列号+一上次发送的数据长度,因为上一次发送的是单纯的ACK报文,所以长度为0,即Seq=server_isn+1+0=server_isn+1;
确认号为上一次收到的报文中的序列号 +上一次收到的报文长度len,上一次收到的序列号是client_isn+11,因为收到的是FIN报文,长度为1,所以Ack=client_isn+11+1=client_isn+12; - 4、客户端发起第四次挥手。ACK设置为1,该报文长度为0;
序列号为上一次发送的序列号+一上次发送的数据长度,上一次发送的序列号是client_isn+11,因为发送的是FIN报文,长度为1,所以Seq=client_isn+11+1=client_isn+12(上述图中有误,请注意,懒得改了);
确认号为上一次收到的报文中的序列号 +上一次收到的报文长度len,上一次收到的序列号为server_isn+1,报文是FIN报文,长度是1;即Ack=server_isn+1+1=server_isn+2(上述图中有误,请注意,懒得改了);
引用
https://zhuanlan.zhihu.com/p/577528304