1、TCP 的特点:
基于字节流
面向连接
可靠传输
缓冲传输
全双工
流量控制 2、头部格式和说明
图源百度。如下图示,就是 TCP 包的头部结构。可以看到这个头部最少有 4x5=20 个字节。
另外还需要理解 TCP 协议是承载在 IP 协议中的。关于 IP 协议可以参考:http://www.cnblogs.com/xcywt/p/8067521.html
源端口号和目的端口号:再加上 Ip 首部的源 IP 地址和目的 IP 地址可以唯一确定一个 TCP 连接
数据序号:表示在这个报文段中的第一个数据字节序号
确认序号:仅当 ACK 标志为 1 时有效。确认号表示期望收到的下一个字节的序号(这个下面再详细分析)
偏移:就是头部长度,有 4 位,跟 IP 头部一样,以 4 字节为单位。最大是 60 个字节
保留位:6 位,必须为 0
6 个标志位:
URG - 紧急指针有效
ACK - 确认序号有效
PSH - 接收方应尽快将这个报文交给应用层
RST - 连接重置
SYN - 同步序号用来发起一个连接
FIN - 终止一个连接
窗口字段:16 位,代表的是窗口的字节容量,也就是 TCP 的标准窗口最大为 2^16 - 1 = 65535 个字节(这个下面再详细分析)
校验和:源机器基于数据内容计算一个数值,收信息机要与源机器数值 结果完全一样,从而证明数据的有效性。检验和覆盖了整个的 TCP 报文段:这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证的。
紧急指针:是一个正偏移量,与序号字段中的值相加表示紧急数据最后一个字节的序号。TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式
选项与填充(必须为 4 字节整数倍,不够补 0):
最常见的可选字段的最长报文大小 MSS(Maximum Segment Size),每个连接方通常都在一个报文段中指明这个选项。它指明本端所能接收的最大长度的报文段。
该选项如果不设置,默认为 536(20+20+536=576 字节的 IP 数据报)3、TCP 如何保证可靠性
1)应用数据被分割成 TCP 认为最合适发送的数据块。称为段(Segment)传递给 IP 层
2)当 TCP 发出一个段后,它会启动一个定时器,等待目的端确认收到这个报文段。若没有及时收到确认,将重新发送这个报文段
3)当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。这个确认不是立即发送的,通常将推迟几分之一秒。
4)TCP 将保持它首部和数据的校验和,这是一个端到端的校验和,目的是检测数据在传输过程中的任何变化。如果收到段的校验和有差错,TCP 将丢弃这个报文也不进行确认(对方就会重复发送了)。
5)TCP 承载与 IP 数据报来传输,而 IP 数据报可能会失序,所以 TCP 的报文段到达时也可能会失序。但是 TCP 收到数据后会重新排序到正确的顺序(通过序号)。
6)IP 数据报会发生重复,TCP 的接收端必须丢弃重复是数据
7)TCP 还能提供流量控制,TCP 连接的每一方都有一定大小的缓冲空间 4、滑动窗口协议(也就是对包头中窗口字段的理解)
参考 1:https://www.cnblogs.com/ulihj/archive/2011/01/06/1927613.html
参考 2:http://blog.chinaunix.net/uid-26275986-id-4109679.html
先上两个概念:
通告接收窗口(rwnd):预防应用程序发送的数据超过对方的缓冲区,接收方使用的流量控制。
拥塞窗口(cwnd):预防应用程序发送的数据超过了网络所能承载的能力。发送方使用的流量控制。
发送窗口:就是指上面两者的较小值
由于 TCP 的全双工的,所以其实 TCP 双方各自都维护一个发送窗口和接收窗口。
假设是主机 A 发送给主机 B
A 和 B 都会维护一个数据帧的序列,这个序列称为窗口。发送方的窗口大小由接收方确定。目的在于控制发送速度。以免接收方的缓存不够大而导致溢出,同时流量控制也可以避免网络拥塞。
这里其实是指 A 的发送窗口。
假设 A 发送了很多段给 B,序号是 1-10. 这些段会处于种状态:
1)已发送,已确认
2)已发送,未确认
3)等待发送
4)不允许发送
正常情况下,每个段都会由 4 状态 ->3 状态 ->2 状态 ->1 状态。而窗口就是指处于状态 2 和状态 3 的总数。
由 2 状态 ->1 状态的时候,窗口就会往后滑动一下,表示最近那个 4 状态的段可以变成 3 状态了。
如果接收方一直不确认,那么处于 4 状态的段将永远不会被发送。
当窗口满了的时候,4 状态的段将不会变成 3 状态。从而达到了控制发送速度的作用。
就像上图一样,123 处于 1 状态,456 处于 2 状态,789 处于 3 状态,10 以后的处于 4 状态。而窗口就是指哪个框起来的。这里为 6。
随着发送段被逐一的确认,这个窗口会往右滑动。
就像一个水池,总体积 V,进水速度是 s1,出水速度 s2。当水池满了就不能再注入了,强行注入会溢出丢失。窗口就是那个水池。
滑动窗口实现面向流的可靠性:
1)最基本的传输可靠性来源于确认重传机制
2)滑动窗口的可靠性也是建立在确认重传机制上的
3)发送窗口只有收到目的端口对本段发送窗口内字节的 ACK 确认,才会移动发送串口的左边界。
4)接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有段未收到确认,但是收到了后面段的情况下,窗口不会移动,也不对后续段进行确认。以此确保发送端会对这个数据重传。5、关于包头中确认号 ack 的理解
确认序号:仅当 ACK 标志为 1 时有效。确认号表示期望收到的下一个字节的序号
这里是拿三次握手之后,开始传输数据了进行分析。
服务器向客户端发送一个数据包后,客户端收到了这个数据包,会向服务器发送一个确认数据包。
传输数据的简要过程如下:
1)发送数据:服务器向客户端发送一个带有数据的数据包。该数据包中的序列号和确认号与建立连接第三步的数据包找那个的序列号和确认号相同。
2)确认收到:客户端收到该数据包,向服务器发送一个确认数据包。该数据包中,序列号是为上一个数据包中的确认号值。
而确认号为服务器发送的上一个数据包中的序列号 + 该数据包中所带数据的大小。
回复确认收到的 ack = 收到了序列号 + 数据的大小(同时也表示下一次期望收到的序号)
这里我们直接拿 Wireshark 抓包进行分析:
实例 1:客户端给服务器发送了 "xcychongyong" 共 13 个字节。
先看,服务器收到的,也就是客户端发送的:seq 是 10,数据长度是 13.
再来看服务器发送给客户端的确认包:根据上面的说明。ack 应该是 10 + 13 = 23
实例 2:
如下图,208(就是 192.168.0.208)一共向 182(就是 192.168.0.182)发送了 6 组数据。
过滤条件:tcp and (ip.src==192.168.0.182 or ip.dst==192.168.0.182)
对于 182 来说:
第一次回应时 ack 是 4,结果 208 下一次发送的序号就是 4。
第二次回应时 ack 是 10,结果 208 下一次发送的序号就是 10。
第三次回应时 ack 是 19,结果 208 下一次发送的序号就是 19。
以此类推…
再来分析一个互相发送的:
如图,一共发送了 5 次:
第一次 182 发给 208,发的长度是 7,seq 是 1. 所以 208 回复的 ack 是 8。也相当于告诉 182:"182,你下次发的时候,序号就从 8 开始"。看第 2 个红框,seq 就是 8.
第二次 208 发给 182,发的长度是 11,seq 是 1,所以 182 回复的 ack 是 12。也相当于告诉 208:"208,你下次发的时候,序号就从 12 开始"。看第 2 个绿框,seq 就是 12.
同理,
182 再发送一次给 208,seq 应该是 17
208 再发送一次给 182,seq 应该是 33
ack 表示期望下次接收到的序号。
那么 ack 是如何算出来的呢,就是通过收到的序号,和数据长度相加得来。
假设 A 收到 B 过来的数据(seq = 5,len = 15)。len 表示数据长度。
那么 A 就会回复 B,"刚才的数据我已经收到了,你接下来就发序号为 20 的包给我吧"。这样就保证了数据不会乱序。
综上,确认号就是下一次将要收到包的序号。同时也等于发送方的序号 + 数据长度(确认号在 ACK 标志位有效时才有用。)