继续画图带你学习TCP 其他 7 大特性(四)
八、捎带应答 (效率机制)
在延迟应答的基础上,为了进一步提高程序运行效率而引入的机制
在很多情况下,客户端和服务器的通信模式一般都是 Request - Response 模式,即 “一问一答”
如图:
注意:
三次握手中间的 SYN 和 ACK 都是由内核决定的,不涉及不同的时机
上述提到的四次挥手的过程,ACK 是内核决定的,发的 FIN (close方法) 是应用程序代码决定的
九、粘包问题
严格说,粘包问题不是 TCP 自身的机制,而是面向字节流传输所具备的共性问题
粘包,指粘的是应用层数据包,导致数据在处理的时候,容易读取半个应用层数据包
面向字节流: 指的是一次读一个字节,或者一次读两个字节,或者一次读 N 个字节都行
举例:双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构
- “你好不好”
- “好个P”
读多少个字节才是一个完整的应用层数据包,这个是不清楚的
若一次读一个汉字,读出来就是 “好”;若一次读三个汉字,读出来就是 “好个P”
读法不一样,最终的含义差异也很大;读取应用层数据,就不应该只读半个包
如何避免粘包问题?
归根结底就是一句话,明确两个包之间的边界
TCP 协议本身不帮你区分应用层数据包,相对而言,UDP 协议没这个问题 (UDP 协议就是按照数据包为单位进行收发的)
- 方式1 - 使用分隔符
比如,上述回答改为 “好个P;”
用分号 ;当做两个包的分隔符,读数据,一直读到分号;才认为是一个完整的应用层数据包
应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可**
- 方式2 - 明确包的长度
比如,上述用例改为 “4你好不好3好个P”
先读取最开始的四个字节,得到包的长度3;继续读取3个汉字,于是就读取一个完整的包
HTTP 协议基于 TCP 的应用层协议,自己就会处理好粘包问题,上述两种方式都使用到了:
对于 GET 请求,分隔符就是空行
对于 POST 请求,Content-length 来指定包的长度
思考:对于UDP协议来说,是否也存在 “粘包问题” 呢?
- 对于UDP,如果还没有上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交付给应用层;就有很明确的数据边界
- 站在应用层的站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收;不会出现"半个"的情况
十、保活机制
双方建立交互的连接,并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会,那么在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启,还是中间路由网络无故断开、NAT超时等各种意外
在这些 “异常情况” 下,TCP 对于连接会有一些特殊的处理
举例:
1.进程崩溃: 这种情况,TCP 连接会正常四次挥手 (只要是进程退出,都会自动关闭相关的文件)
2.主机关机(按照流程关机):关机的时候会强制先杀进程,杀进程过程之中就要进行四次挥手了
3.主机断电 / 网线断开:
- a) 接收方断电。 对端尝试发送消息的时候,就会出现没有 ACK 的情况 — 于是就会触发超时重传 — 重传一定次数,就会重置连接 — 放弃连接
- b) 发送方断电。对端尝试接收消息,对于接收端来说,本来也不知道发送方什么时候发送,难道就一直等吗?
解决方案 — 心跳包 TCP 的通信双方,即使在没有数据交互的过程中,也会定时相互传输一个没有数据业务意义的 “心跳包”,只是为了证明 “我活着”,一旦隔了一段时间都没有收到对方的心跳包,就可以认为对端"挂"了
TCP总结
TCP 之所以复杂,是因为它既要保证可靠性,同时又尽可能地提高性能
可靠性:
校验和,序列号
确认应答,超时重传
连接管理,流量控制,拥塞控制
提高性能:
滑动窗口,快速重传
延迟应答,捎带应答
其他:
定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)
文章转自公众号:三友的java日记