一、TCP协议首部
二、序号(Sequence Number)
32位,表示该报文段所发送数据的第一个字节的编号。
实际上 TCP 的序号并不是按照 “一条两条” 这样的方式来编号的。在TCP连接中所传输字节流的每一个字节都会按顺序编号,由于序列号是由32位表示,所以每2^32个字节,就会产生序列号回绕,再次从0开始。
TCP在建立连接时由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机,每发送一次数据,就「累加」一次该值。
每个TCP数据报报头填写的序号只需要写TCP数据的头一个字节的序号即可。
这样设计序号的好处?
TCP首部时可以携带选择性确认(Selective Acknowledgment,SACK)选项。如果通信双方支持SACK,那么在数据传输过程中发生了数据丢失或延迟等其他原因造成的接收端数据不连续,那么接收端就可以发送一个SACK包,通知哪一部分失序了。
- Left Edge of Block, 不连续块的第一个数据的序列号
- Right Edge of Block, 不连续块的最后一个数据的序列号之后的序列号
该选项参数告诉对方已经接收到并缓存的不连续的数据块,注意都是已经接收的,发送方可根据此信息检查究竟是哪个块丢失,从而发送相应的数据块。
这样就不用重传整个数据包。
三、确认号(Acknowledgment Number)
32位,表示接收方期望收到发送方下一个报文段的第一个字节数据的编号,也就是告诉发送方:我希望你下次发送的数据的第一个字节数据的编号为此确认号。确认号只有在ACK标志为1时才有效。发送端收到确认应答后代表确认序号之前的数据都已经被正常接收。
TCP规定,建立连接后,ACK必须为1,带ACK标志的TCP报文段称为确认报文段,也就是说连接建立之后所有的数据包都会设置确认号。
四、抓包测试
启动一个TCP server
https://github.com/NoevilMe/tinymuduo
这里是我改写的一个muduo网络库,去掉了boost依赖,带了一个sample tcp server。
# 编译
mkdir build && cd build && cmake .. -DBUILD_TINYMODUO_EXAMPLES=ON && make
# 启动
./test_tcp_server 38880
监听38880端口。
启动一个客户端
直接使用了网络调试助手,选择tcp client, 填写好地址和端口,然后连接
tcp server 日志
包含了整个过程的连接与断开,发送了两次TCP数据,每次发送3068字节,发送内容我省去了。
2024/03/20 12:10:43.148583 109826 INFO TcpServer::NewConnection [Sample Tcp Server] - new connection[Sample Tcp Server-0.0.0.0:38880#3] from 180.165.165.51:2142 - tcp_server.cxx:56
2024/03/20 12:10:43.148635 109826 DEBUG TcpConnection TcpConnection::ctor[Sample Tcp Server-0.0.0.0:38880#3] at 0x562100CCD100 fd=23 - tcp_connection.cxx:23
2024/03/20 12:10:43.148714 109829 INFO new connection from 180.165.165.51:2142 - test_tcp_server.cxx:14
2024/03/20 12:10:46.762928 109829 TRACE HandleRead TcpConnection::HandleRead length 3068 - tcp_connection.cxx:129
2024/03/20 12:10:46.762982 109829 INFO Sample Tcp Server-0.0.0.0:38880#3 receive 3068 bytes, content: http://www.cmsoft.cn- ...
- test_tcp_server.cxx:26
2024/03/20 12:10:48.987427 109829 TRACE HandleRead TcpConnection::HandleRead length 3068 - tcp_connection.cxx:129
2024/03/20 12:10:48.987481 109829 INFO Sample Tcp Server-0.0.0.0:38880#3 receive 3068 bytes, content: http://www.cmsoft.cn- ...
- test_tcp_server.cxx:26
2024/03/20 12:10:51.141446 109829 TRACE HandleRead TcpConnection::HandleRead length 0 - tcp_connection.cxx:129
2024/03/20 12:10:51.141487 109829 DEBUG HandleClose TcpConnection::HandleClose 23 - tcp_connection.cxx:177
2024/03/20 12:10:51.141499 109829 INFO destory connection from 180.165.165.51:2142 - test_tcp_server.cxx:18
2024/03/20 12:10:51.141513 109829 INFO TcpServer::RemoveConnection [Sample Tcp Server] - connection Sample Tcp Server-0.0.0.0:38880#3 - tcp_server.cxx:77
2024/03/20 12:10:51.141539 109826 INFO TcpServer::RemoveConnectionInLoop [Sample Tcp Server] - connection Sample Tcp Server-0.0.0.0:38880#3 - tcp_server.cxx:85
2024/03/20 12:10:51.141565 109829 DEBUG ConnectDestroyed TcpConnection::ConnectDestroyed 23 - tcp_connection.cxx:214
2024/03/20 12:10:51.141574 109829 DEBUG ~TcpConnection TcpConnection::dtor[Sample Tcp Server-0.0.0.0:38880#3] at 0x562100CCD100 fd=23 - tcp_connection.cxx:29
五、测试结论
1. 序号是按发送数据长度增长的
也就是说,数据是按字节编号,报文段首部填写的序号就是所发送数据的第一个字节的编号。
413抓包显示是10.10.11.14第一次发送的3068字节数据。
421抓包显示10.10.11.14在收到服务器数据之后发送的一个ACK包,它的seq已经变了,距离上一次发送的序号正好等于上一次发送的数据长度。
452抓包显示10.10.11.14在发送ACK包之后,没有收到服务器数据包,但是又要发送第二个3068字节数据了。
2. 序号是依赖通信对端确认号的
确认号的本质就是接收方期望收到发送方下一个报文段的第一个字节数据的编号。所以有了ACK报文,己方的seq才会增长。
如下图,服务器方期望下一次收到3069编号
客户端发出的ACK包seq就变成了3069
3. 不带数据的纯ACK包不会增长seq
不带数据的ACK包发出之后,对方不会再发送一个ACK包过来,所以也就不会增长seq了。
如下图:客户端发送ACK包时候用的序号3069。
当发送第二次数据的时候仍然使用序号3069。
4. 如果没有收到新数据,确认号不会增长
如果一方需要连续发出报文,这时间段内没有收到任何数据,就会继续沿用上一次的确认号。
下一个报文虽然发送了数据出去,但是上一次发送之后没有收到数据,所以仍然使用同样的确认号。