TCP协议的特点
百度百科定义,TCP全称Transmission Control Protocol, 翻译为传输控制协议。是一种面向连接的,可靠的,基于字节流的传输层通信协议。根据定义我们一点点来看。
传输层协议
TCP协议是作用在传输层的协议,是IP协议所在的网络层更上一层,更接近应用层。
基于连接
使用TCP协议通信的双方必须先建立连接,双方需要为连接在内核中分配一定资源来管理连接的状态与数据传输。在完成数据交换后,需要断开连接释放资源。TCP 连接是一对一的,所以TCP并不擅长处理广播多播。
可靠的
TCP 采用发送应答机制,发送的每个TCP报文段都需要对方进行确认,确认后才认为传输成功。超时或失败会触发重传机制。TCP报文是通过IP报文发送的,IP报文可能是乱序重复的,TCP会重新编排后再交付应用层。
基于字节流
基于字节流相对的概念是基于数据报,基于数字报要求双方收发次数是一一对应,发送端写多少次,接收端就要读多少次。
基于字节流因为在收发端都有缓冲区,以发送为例,用户空间调用 send() 只是把需要发送的数据拷贝到了发送缓冲区,而实际发送时机由内核决定,在实际发送时,这些数据可能被封装成一个或者多个报文段发送。在接收端收到报文后会按照序号一次放入接受缓冲区通知用户空间通过 recv() 来读,读取可以一次全部读出也可以分多次来读。基于字节流发送接受次数没有固定数量关系。
这样设计的原因是根据TCP的特性决定的,因为TCP面向连接,所以在数据传输是一个套接字收到的数据都是同一个发送端发出的,所以只要保证有序,分几次读取无所谓。而UDP如果一次读取多个报文,不同来源的报文混在一起是无法分开的,读出的数据也是无意义的。
TCP 头部结构
固定结构
- 16位源端口号,16位目的端口号。告知主机报文来自那个端口,并由哪个上层协议或应用处理。通信时,客户端端口通常由系统选择,服务器端口使用约定好的端口。
- 32位序号(sequence number): 一次通信过程中,某个传输方向上字节流每各字节的编号。例如A到B通信,A的字节流初始值为seq,后续从A到B通信的序号将被设置为seq加上这个报文携带数据的第一个字节在整个数据流的偏移值offset, A(seq) = seq + offset。
- 32位确认号(acknowledgement number):用作对另一端发来数据的响应, 值为收到的TCP报文段序号加 1。接着上边的例子,B收到A的报文,然后再B回复时会填充自己的序列号 B(seq),还回填充确认号表示已经接受到的报文 B(ack) = A(seq) + 1。
- 4位头部长度:表示TCP头有多少个4字节(32bit), 所以头部长度最大为60字节。
- 6位标志位:
- URG:紧急指针是否有效
- ACK:确认号是否有效
- PSH:表示应用程序应该立刻从TCP接受缓冲区读走数据
- RST:表示要求重新建立连接
- SYN:表示请求建立连接
- FIN:通知对方本端要关闭、
- 16位窗口大小:用在流量控制,告诉对方本端缓冲区还能接受多少字节数据,让对方控制发送速度
- 16位校验:发送端填充,接收端用于计算数据是否损坏,校验包括头部和数据部分。
- 16位紧急指针:是数据的偏移量,与序号相加表示紧急数据开始的序号用来发送紧急数据。
选项
是一个可边长信息,最大40字节。
一般结构为
kind 1字节 | length 1字节 | info n字节 | 作用 |
---|---|---|---|
0 | / | / | 结束选项 |
1 | / | / | 无含义。用于补齐字节数 |
2 | 4 | 最大segment长度 2字节 | 最大报文长度选项,防止发生IP分片 |
3 | 3 | 位移数1字节 | 窗口扩大选项 |
4 | 2 | / | SACK是否支持SACK技术优化重传 |
5 | N*8 + 2 | B1L,B1R,B2L,B2R…B4R | SACK技术工作选项,同步已收到的不连续块用于发送端计算重传 |
8 | 10 | 时间戳4字节,时间戳应答4字节 | 提供计算通信回路时间的方法 |
使用tcpdump观察
服务器在9999端口启动了一个echo服务,使用客户端连接,下面是服务收到的第一个tcp包。
# tcpdump -ntvx tcp port 9999
tcpdump: listening on bond0, link-type EN10MB (Ethernet), capture size 65535 bytes
IP (tos 0x0, ttl 127, id 21139, offset 0, flags [DF], proto TCP (6), length 52)
10.1.45.14.61997 > 10.1.60.34.distinct: Flags [S], cksum 0x9efa (correct), seq 3262413889, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
0x0000: 4500 0034 5293 4000 7f06 2bff 0a01 2d0e
0x0010: 0a01 3c22 f22d 270f c274 7c41 0000 0000
0x0020: 8002 faf0 9efa 0000 0204 05b4 0103 0308
0x0030: 0101 0402
16进制数 | 10进制 | 对应信息 |
---|---|---|
0x4500 … 0x3c22 | / | 前20字节对应IP头 |
0xf22d | 61997 | 源端口 |
0x270f | 9999 | 目的端口 |
0xc2747c41 | 3262413889 | 序列号 |
0x00000000 | 0 | 确认号 |
0x8 | 8 | TCP头部长度 8*4 = 32字节 |
0x002 | 只设置了syn | |
0xfaf0 | 64240 | 接受窗口大小 |
0x9efa | 40698 | 头部校验和 |
0x000 | 0 | 没有晋级指针 |
0x0204 | 最大报文长度选项的kind 2 和length 4 | |
0x05b4 | 最大报文长度1460(1500 - ip头 - tcp头) | |
0x01 | 空选项 | |
0x030308 | 窗口扩大因子选项 kind 3 lenght 3 位移数 8 | |
0x0101 | 空选项 | |
0x0402 | 支持SACK功能 |
TCP 连接的建立与关闭
TCP连接和关闭,应该多少都听说过三次握手四次挥手。下面使用tcpdump观察这一过程。
继续使用上边的例子,服务器在9999端口启动了一个echo服务,客户端连接后主动断开。
# tcpdump -nt tcp port 9999
client> server.distinct: Flags [S], seq 1781272829, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
server.distinct > 10.1.45.14.52255: Flags [S.], seq 145108604, ack 1781272830, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
client> server.distinct: Flags [.], ack 1, win 8212, length 0
client> server.distinct: Flags [F.], seq 1, ack 1, win 8212, length 0
server.distinct > 10.1.45.14.52255: Flags [.], ack 2, win 115, length 0
server.distinct > 10.1.45.14.52255: Flags [F.], seq 1, ack 2, win 115, length 0
client> server.distinct: Flags [.], ack 2, win 8212, length 0
第三条报文开始ack与seq显示在第三个协议包后都改为偏移值显示,需要绝对值显示可以用tcpdump -S选项
从图中可以看出
- 是由客户端发起包含SYN标记的报文段发起连接请求,seq为1781272829
- 服务器同意连接,发送SYN标记,自己的seq为145108604, 同时回复ack=1781272830。建立连接的同步报文和断开连接的结束报文没有数据也要占用一个字节,所以服务器发出ack = 客户端的seq +1
- 客户端确认建立连接,三次握手结束。之后输出的seq和ack值都是初始值的偏移量,可以用 tcpdump -S 来显示绝对值。
- 客户端主动发起断开连接, 发送FIN包
- 服务器收到后确认客户端断开
- 服务器也断开连接,发送FIN包。为了介绍报文数量,tcp会开启了延迟确认机制,这时5、6两个报文是合并发送的。大多数情况下我们观察到关闭也是三个报文。可以设置socket属性TCP_QUICKACK快速应答来立刻应答。
- 客户端确认服务器关闭,四次挥手结束。
TCP 状态转移
上边的图描述了客户端服服务器TCP连接的状态转换图。其中粗虚线是服务器状态转换,粗实线是客户端状态转换。
服务器状态转移
- 通过系统调用listen()从CLOSED进入LISTEN状态,等待客户端连接。
- 收到客户端sync包后,将该连接放入内核的等待队列,然后回复客户端带SYN标志的确认报文,此时处于SYN_RCVD状态。
- 收到客户端返回的确认报文,进入ESTABLISHED状态,双发可以双向数据传输
- 客户端主动断开连接,服务器返回确认报文进入CLOSE_WAIT状态
- 服务器发送结束报文来关闭连接,等待客户端回复,进入LAST_ACK状态
- 收到客户单确认报文断开连接,进入CLOSED状态
客户端状态转移
- 通过connect调用与服务器建立连接,发送一个sync包,进入SYNC_SENT状态
- 收到服务器确认后进出ESTABLISHED状态可以通信
- 等待建立连接时程序关闭或超时返回CLOSED状态
- 当客户端主动请求关闭时,发送结束报文,进入FIN_WAIT_1状态
- 收到服务器确认后进入FIN_WAIT_2状态等待服务器关闭
- 收到服务器请求关闭的报文后进入TIME_WAIT状态,如果服务确认和请求关闭是同一个报文,会从FIN_WAIT_1直接进入TIME_WAIT状态。
- 客户端在TIME_WAIT状态等待2MSL(报文最大生存时间)后进入CLOSED状态。为了可靠的终止连接,确保迟到的TCP报文被识别丢弃。
以上介绍了TCP状态迁移常见情况,其他状态迁移,例如同时打开关闭,暂不做讨论。
总结
本文介绍了TCP协议的特点,协议头设计,TCP连接建立关闭发的过程以及状态迁移的流程。后续将介绍TCP 协议的数据交互。
荐一个零声学院免费教程,个人觉得老师讲得不错,分享给大家:[Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
链接