从一次HTTP访问解析TCP协议的连接建立与释放
要理解TCP的连接和断开其实不难,需要自己代入情境,如果是你来设计网络协议你会如何做。
连接建立
情况: 通信质量无法保障(IP层的不可靠性)的情况下,如何保证两者可靠通信。
- A: 在家吗,我想过来玩?[SYN]
- B: 在,你过来吧。[SYN,ACK]
- A: 好的。[ACK]
- (完成)A出发…
所谓三次握手,其实是发了三次包,代表一次握手过程中的三个步骤而已。
如果少了第三步,可以出现什么情况?因为通信糟糕(TCP设计于70年代,网络确实也较差),A有未收到答复2的可能性,那么B就无法确定A会不会来,只能一直等待(syn flood 攻击)。如果有第三步,系统可以设置较短的等待时间,过时不候。而如果设计上就没第三步,那么等于服务器收到请求即建立连接,这样会造成很多不必要的消耗。
其实3完成也还有不确定性,因为B没有对此作回复,所以B可能未收到,从而连接未建立,那么A过去就会被拒之门外,但关系不大,重新发起请求就行了。
或者也可以这么理解:
1 B收到A的SYN,B知道了A-->B可达
2 A收到B回复的SYN ACK,A知道了A-->B,B-->A可达
3 B收到A的ACK, B知道B-->A可达
也就是说如果少了第三步,B无法知道B-->A线路的可用性,而三步是双方都能确认双向可达性的最少步骤。
发送数据
当然仅靠握手还无法保证TCP的可靠性,于是就有seq和ack编号,来对每个数据块作确认,为了提高效率引入了pipeline思想,多线程同步发送,为了保障效率和控制拥塞又引入了window。
上图中TCP建立之后,就开始HTTP的GET操作,注意下文的TCP segment len : 140 ,此次GET操作传送的载荷是140Byte,所以紧接的回复ACK号是141,意为接下来准备接收从141字节起始的内容。
所以接下来的下一个GET,其SEQ为141。
所以数据传送期间,SEQ就是上次对方发送的ACK号,而ACK则是上次对方发送的SEQ+数据Length
特殊字段
TCP设计了一些十分有用的标识位,比如URG紧急,远程发出中断指令时可使用,配合紧急指针字段,指令将立即加入到缓存队列的最前端。
与之对应的是PSH推送,它代表对方收到此报文后应该立即交付应用层,而不是在缓冲中排队。注意这里没有相应的定位指针,因为PUSH总是单独创建的报文段。
可以看到HTTP GET使用了PSH,以便Server能更快地响应。之后的302回复也使用了PSH。
面向字节流
TCP没有protoTypea或者类似etherType的标志位,虽然会对数据分块,但是也没有类似IP那样的分片标志。因为TCP是面向字节流的,对PDU的内容不关心,由上层应用去解析。
Server对二次GET的响应内容较长,使用了多个TCP包来传送,可以看到client没有再逐个ACK。
Server发送数据期间,Client只收不发,所以Server方报文的ACK一直不变,而client的ACK号相应增加。
另外如果检查TCP的PDU(第一个segment),可以发现其内容直接就是:
下图中TCP头部以后,数据段起始前四字节,数值为0x48 0x54 0x54 0x50
0x48 0x54 0x54 0x50对应ascii就是HTTP
连接释放
TCP连接的释放过程比建立复杂一些,因为TCP是双工的,所以包含两个过程
- A数据传送完成,提出断开请求
- B确认(此时A—》B方向断开,但是B—》A依然畅通)
- B在己方数据传输完成后,(虽然此例中没有再传输数据,但是B是可以继续向A发数据的)也提出断开请求,
- A确认
- 至此完全断开
可以看到期间ACK一直是置位的,因为协议规定连接存续期间ACK为1。
后记
当然TCP远不止上面提到的那么简单,为了在不确定交付的IP上构建可靠连接,TCP设计了许多巧妙的机制,比如说它的状态机,它的和重传机制,想再一步深入推荐TCP的那些事儿,文章不长,但是信息量却是很大的。