PSH一般意味着当前方没有更多的数据要发送
SYN包可以包含数据, 但是标准的socket不支持, 因此不要使用
两个不常见的FLAG
CWR 拥塞窗口见面(让发送者减小发送速率)
ECE 发送方收到了一个更早的拥塞通知
同时SYN建立连接
双方必须在收到对方的SYN之前发出SYN, 双方都会基于现在已知的信息做出ACK
同时发送FIN挥手
双方互相发送ACK后,停留2MSL后关闭
初始SYN的选择
Linux选择使用一个和时间相关的随机偏移量作为哈希函数的一个输入参数, 其他的输入参数还包括了TCP的四员组, 这个偏移量每五分钟变化一次.
这样做保证了:
- 两次不同的连接不会产生误解
- 防止了某些中间人的攻击
TCP扩展头部
最初的TCP只定义了扩展头部结尾(EOL), 没有扩展头部(NOP),最大分段长度(MSS), 其他字段都是后期扩展的. 如下表
每一个扩展头部必须在一开始包含1Byte的类型说明和1Byte的长度说明.
扩展头部结尾(EOL)的出现纯属是因为TCP报文必须32Bit对齐
MSS字段
双方在各自的SYN包中描述自己希望的MSS大小, 如果没有描述, 则默认为536Byte(因为576Byte是所有支持IP的设备的最小接收单元)
MSS代表的是TCP的负载数据的大小
MSS 不是一种协商, 而是一种自己的上限, 自己不会再接受比MSS更大的报文
SACK
选择性确认功能有助于提升TCP传输的效率.
双方会在握手时提供自己是否支持SACK的选项.
每一个SACK会占用一对32bit空间, 因此会总共占用(2 + 8 * n)个字节, 扩展头部一般最多放3个选择确认区间.(假定现代TCP会通常使用Timestamp特性)
窗口扩展选项
在扩展头部中使用窗口扩展选项, 使得其中的位数能够和原有的窗口大小拼接, 组成更大的窗口.
双方必须在握手阶段声明自己支持窗口扩展, 如果有一方不支持, 那么双方都不起用. 如果双方声明的扩展窗口大小不一致, 则收/发方可以使用自己声明的长度来扩展窗口
最大可扩展14bit, 和原有的窗口拼接可以达到2^30Byte, 约为1GB
时间戳选项
每一个报文可以携带当前的时间戳, 当得到一个往返信息时, 节点可以计算最新的RTT.
因为常规的TCP连接对于一个窗口只会计算一次RTT, 使用时间戳选项后, TCP连接可以获得更加细粒度的RTT计算
时间戳的另外一个作用是防止先前的报文干扰
不常见的是, 时间戳还可以作为Seq序号的辅助. 考虑当一个大带宽传输时, Seq序号只有32位, 记录4GB大小的内容, 可能某个内容正在网络传输时, 下一个相同的Seq序号的报文也同时到达, 对方节点可以根据Timestamp作为区分的一个参考依据.
Timestamp不需要双方同步, 只要将自己的Timestamp单项递增即可
UTO User Timeout Option 用户超时选项
这个选项指定了超过多长时间没有数据发送就关闭连接. 遵循如下公式
实际超时时间 = min(本地系统最大超时时间, max( 本端宣告UTO, 远端宣告UTO, 最小超时时间))
TCP 认证选项
这个选项携带的是报文经过加密后的hash计算结果, 其并不包含预分发密钥, 因此应用需要自己解决如何生成共识密钥的过程, 这个认证选项只能作为判断报文是否被修改的一个依据.
路径MTU探测
根据SYN时互相提供的MTU或者默认值作为发送参数后, (由于路由的变化)双方可以根据连接过程中产生的ICMP报文来动态缩小MSS. 如果缩小后一定时间没有继续缩小, 可以逐渐增长.
但是防火墙阻挡或者某些主机不回应ICMP报文, 会引发TCP的black hole效应, 一个解决方法是TCP发现这种情况时尝试主动做小报文片段.
RST刺客
如上图所示, 当四次挥手已经顺利完成, Client已经进入TIME_WAIT阶段等待2MSL的时候, 此时Server发来一个之前的报文, Client进行ACK确认, 但是由于Server已经释放了链接, 并不能理解这个ACK报文, 因此发回来一个RST报文, 当Client收到RST的时候, TIME_WAIT阶段被强制终止
系统常见的解决方法是: 在TIME_WAIT阶段不会应答任何RST报文
Server上的TCP
连接排队
系统为每一个TCP的端口提供了两个队列
- 向上层递交的队列: 如果应用层不能及时处理这些连接, 那么这些连接就会在队列中排队保存
- 三次握手未完成的队列: 如果系统不能及时处理, 或者1中的队列已满, 那么系统可能会将这些半连接保存成队列, 也有可能直接发送RST拒绝服务.
应用只有当连接建立完成之后才能知道服务的对象是谁, 因此在连接建立完成之前没有权利去拒绝服务,只有在建立完成后才能FIN或者RST