一、概述
我们在最开始介绍TCP头结构的时候,里面有个URG的标志位,还有一个Urgent Pointer的16bits字段。当URG标志位有效的时候,Urgent Poinert用来指示紧急数据的相对于TCP头中系列号Seq的位置,系列号和紧急指针值的和我们称呼为退出点(exit point)。应用程序写入数据的时候可以通过MSG_OOB的socket选项来指定紧急数据。实际上因为紧急数据只有一个指针来指示并没类似长度的字段,因此紧急数据也只能有1bytes。RFC6093已经建议不要在继续使用紧急数据了。当TCP接收端接收到URG标志位有效的报文的时候,接收端进入紧急模式(urgent mode)。在RFC6093之前,紧急指针有指向紧急数据对应的byte的,也有指向紧急数据的下一个byte的。RFC6093则规定Urgent Poniter用来指示紧急数据的下一个byte。Linux下则可以通过/proc/sys/net/ipv4/tcp_stdurg参数控制TCP接收到紧急指针时候的解析行为,该参数默认值为0,此时TCP按照RFC6093来解析紧急数据。当该参数设置为非0的时候,TCP则认为紧急指针指向了紧急数据对应的byte。
在Socket编程中,紧急数据被称呼为out-of-band(OOB)数据,实际上TCP并没有实现真实的OOB能力。如果接收者要读取这样OOB数据,必须通过MSG_OOB选项来读取1byte的紧急数据,或者指定MSG_OOBINCLINE来让紧急数据随普通数据一起读取出来。
二、wireshark示例
1、紧急指针与window probe的交互
client端连续写入4000bytes、3000bytes、4000bytes、4000bytes、1byte紧急数据、1000bytes、休眠1500ms、最后在写入1byte的紧急数据
server端设置固定收窗口为7000,先休眠1000ms后读取出缓存中的全部数据,接着在休眠1000ms。然后读取处缓存中全部数据
最终client和server的交互如下图所示,window probe和延迟ACK等相关内容前面示例已经解释很多次了,此处不再解释
在下面图示中可以看到在有紧急指针数据的时候,persist定时器每次超时的时候会连续发送两个window probe报文,如No8和No9、No11和No12,linux实现上每次persist timer定时器超时,如果当前有紧急数据则会先以snd_una为系列号发送一个Len=0的probe报文,然后在以(snd_una-1)为系列号发送一个Len=0的报文。之前我们已经说过以(snd_una-1)为系列号的报文会被linux认定为无效系列号而直接丢弃,因此会在发送这个报文之前以snd_una为系列号发送一个报文指示紧急数据。但是实际上两个probe报文都指示了有效的紧急数据位置。例如No8报文指示的紧急数据的下一个byte系列号为7001+8001,No9报文指示的位置为7000+8002,可见两者指示的紧急数据的位置是相同的。但是同时注意到这两个报文中虽然指示了紧急指针的位置但是,对应位置的紧急数据并没有在这两个报文中传输。
另外注意No18的时候Urg=1001,然后client端写入1byte的紧急数据后,到No21的时候,Urg=2002,可见新写入的紧急数据会覆盖旧的紧急数据。