在《三次握手与Socket API》中我们详细讲解了三次握手以及与之相关的API,三次握手是TCP协议的初始阶段,用来建立双方通信连接,显然有建立连接就有断开连接,那么TCP是如何断开连接的呢?
奇怪的四次挥手
TCP协议建立连接需要进行三次信息交互,断开连接时却需要四次信息交互,这四次信息交互被形象的称为四次挥手,那么TCP在断开连接时为什么需要奇怪的四次挥手而不是三次挥手呢?要想回答这个问题我们必须对TCP协议有进一步的理解。
话多的人与话少的人
生活中总会有这两类人,一类话多,一类话少,话少的那个言简意赅很快就无话可说了,话多的那个滔滔不绝没完没了,因此就会有这样的情形:
A: 今天天气这不错啊
A: 今天饭也好吃
A: 是吧
是的 :B
我说完了 :B
A: 我还没说完呢
A: 明天就周五啦
A: 开心
A: blablabla
A: 我也说完了
当这两类人在聊天时就会出现B说完但A没说完的情况,这时B不再说话但是可以继续听A说,当A也说完后整个聊天过程才算结束。
TCP通信过程和人聊天又有多大区别呢?
有话多话少的人当然也会有数据多数据少的客户端以及服务器。
为什么要四次挥手:半关闭
在TCP通信中一方表示“我说完了”实际上是通过向对方发送一条带有FIN标识的消息来实现的,当一方收到FIN信息时就知道接下来对方就不会再发送数据了,这在TCP协议中被称为半关闭,half-close。
TCP为什么要支持半关闭呢?为什么不是一方说完大家都闭嘴呢?
原来TCP通信和两个人聊天是一样的,你在说话的同时也在听,也就是说你可以同时收发信息,这就是所谓的全双工通信,在这种通信方式下你说完了不代表对方也说完了,因此只有双方都说完了连接才能完全关闭。
TCP也是全双工通信,TCP规定通信的任意一方都可以向对方发送FIN信息(表示我说完了),但这也仅仅表示一方没什么要发送的了,但这并不影响对方继续发送数据(对方还没发送完数据)。
这就是为什么TCP支持半关闭的原因,半关闭的本质就是TCP通信中一方已经没什么数据要发送了,虽然没有数据要发送但还可以继续接收数据
四次挥手的过程
理解了前几节接下来就简单啦,假设有一个话少的客户端和一个话多的服务器端就会有如下情形:
从图中我们可以看出虽然客户端发送了FIN信息但此时服务器端还有数据要发送,因此服务器端没有理会客户端发送的FIN而是继续发送数据直到服务器端也发送完,此时服务器端发送FIN表示数据发送完毕,客户端接收到消息后进行ACK回复,这时TCP连接才真正断开。
不过值得注意的是,作为程序员在网络编程中我们几乎不会遇到上述情形,通常客户端向服务器端发送FIN后服务器端就不会再向客户端继续发送数据了,一般情况下TCP通信的整个过程都是客户端来主导的,客户端主动发起连接,获取到想要的信息后主动断开连接,服务器端只是被动同客户端建立链接,客户端想要什么服务器端经过处理后就返回给客户端什么,当客户端不想再聊天时服务器端也不会还在滔滔不绝,总而言之,客户端与服务器就好比用户和客服一样,整个过程都是用户来主导的,客服只是被动的来回答问题,当用户没什么问题后没有哪个客服还会继续和用户聊下去,当然客服让用户给好评除外,放心,服务器端是不需要客户端给好评的 :) ,因此我们通常看到的这样的情形:
有的同学可能会想,为什么B发送的ACK和FIN不能和三次握手一样合并成一次呢?就像这样:
如果只有三次挥手那么TCP就没有办法支持半关闭了,三次挥手实际是在说“如果有一方闭嘴那么我们都闭嘴”,虽然作为程序员我们在网络编程时遇到的都是这种情形,但是从TCP协议层面来说应该支持半关闭,也就是支持通信双方中一个话多一个话少的情形。
现在你应该明白为什么要四次挥手而不能是简单的三次了吧。
四次挥手与close
和三次握手一样,四次挥手也有与之相对应的API,这样的API有两个,一个是我们常用的close,另一个是shutdown。
close是我们最常使用的一个,既然close是最常用的,那么close也就用在“一方闭嘴大家都闭嘴”的情形,如图所示:
我们已经知道四次挥手通常是由客户端来发起的,当客户端执行close函数后会向服务器端发出FIN消息,服务器端操作系统接收到该信息后向服务器进程发出EOF(end of file),这时服务器无论调用send还是recv都会失败,此时服务器端认为客户端已经关闭了连接,因此调用close函数关闭同客户端的连接,当客户端接收到FIN信息后进行ACK回复确认,整个四次挥手的过程完毕。
四次挥手与shutdown:半关闭
客户端和服务器使用close来完成四次挥手是最常见的过程,即“一方闭嘴大家都闭嘴”,我们知道作为全双工通信协议的TCP还需要支持半关闭,也就是说一方说完但另一方还在说的场景,这时先说完的就只能听了,作为程序员我们需要知道几乎没有什么应用场景要用到TCP提供的半关闭能力。
当客户端调用close后如果服务器端没有理会而是继续向客户端发数据,那么此时客户端是接收不到的,当然这就是客户端调用close函数的目的。
而如果客户端表示:“我没什么要说的了,所以要向服务器发送FIN,但是我还想听服务器啰嗦一会儿直到对方也说完”该怎么办呢?这时客户端就不能调用close了而是应该调用socket提供的另一个API,也就是shutdown,典型的场景如图所示:
虽然客户端调用shutdown向服务器端发送了FIN表示自己说完了,但是服务器端还是可以继续向客户端发送任意多的数据,而客户端依然可以接收到数据,直到服务器端也发送FIN消息客户端回复ACK确认后连接才算是真正关闭。
现在你应该明白如何使用shutdown利用TCP的半关闭能力了吧。
总结
本文是继《三次握手与Socket API》的第二篇,讲述了四次挥手的过程以及为什么需要四次挥手,之所以需要四次挥手是因为TCP协议需要提供一种被称为半关闭的能力,但是很少有应用场景需要依赖该能力。作为程序员我们需要知道大部分情况下使用close就可以完成四次挥手,但是如果需要依赖TCP提供的半关闭能力那么需要使用另一个API,也就是shutdown。
如果你喜欢这一系列的文章,也欢迎关注我的微信公共账号,码农的荒岛求生,获取更多内容。
计算机内功决定程序员职业生涯高度
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。