网络原理3—TCP 2 - 详解
上篇文章,我们讲到了TCP保证可靠性传输的其中两个机制——确认应答和超时重传,这篇文章。我们来讲述TCP保证可靠性的第三个机制——连接管理。
连接管理(重点!!!)
连接管理分为建立连接和断开连接。
建立连接——三次握手
TCP是有连接的,我们在TCP的回显服务器的客户端写过这么一行代码:socket = new Socket(severIp,severPort)在建立连接!!!就是,这个操作就
在操作系统的内核中完成的。就是上述代码中,我们是在应用层调用了socket的API,真正建立连接的过程,
内核中是如何搞定上述“连接建立”的过程的呢?->"三次握手"。
此处我们建立的连接,实际上是一种“虚拟的”、“抽象的”连接,为了让通信双方都能保存对方的相关信息。就是目的
首先客户端会向服务器发送一段syn(同步报文段)。
syn其实是synchronize的缩写,是不是很眼熟,这个也是大家之前上锁的关键字。这里我们翻译为同步的意思。
在建立连接的过程中,客户端是主动的一方,第一次交互,一定是客户端主动发起的。
一个特殊的TCP数据报(就是syn1、没有载荷,不会携带应用层数据(传输层的载荷)。2、六个标志位的第五位SYN为1),这个syn会表达一层语义:我想和你建立连接。
虽然syn没有携带应用层资料,但是也会有IP报头、以太网数据帧帧头和TCP报头,TCP报头会具备客户端的端口和服务器的端口,IP报头中,也会涵盖客户端的IP和服务器的IP,这个过程也是客户端在告诉服务器要跟你连接的人是谁。
客户端发出syn同步报文段之后,会有两种可能性:
1、服务器同意了,服务器回复:好啊好啊,我也想和你建立连接。
用来为客户端提供服务的)。就是2、服务器没同意。这种情况一般比较少见,只有当服务器处在负载非常高的情况下,此时客户端太多了,完全没办法再处理请求了,也就没办法处理下文了(只要服务器有空闲,就一定会想和客户端建立连接,毕竟服务器就
服务器接收到客户端的syn同步报文段之后,就会返回一个ACK(应答报文),语义就是收到!!!
表示:我收到了你的连接请求,现在的syn才表示:我愿意和你建立连接)。 就是接下来,服务器返回ack之后,还会再返回一个syn,这个syn的意思是:我接受你的连接(刚刚的ack
客户端在收到服务器发过来的syn之后,也会再给服务器返回一个ack报文,表示:好的,我知道你愿意和我建立连接了。 
到了这里,客户端和服务器在内核中建立连接的过程就做完了。
下面是一些细节的补充:
1、syn我们翻译成:“同步”。在多线程和这里是不同的含义:多线程加锁的synchronized同步,是为了协调多个线程之间的执行顺序;TCP这里则是建立连接状态,客户端和服务器要求相互配合来完成一系列的工作业务。也就是说,双方一旦建立连接之后,力就得往一处使,也能够理解为一种同步。
2、哪怕第一次“握手”,客户端就把自己的信息告诉服务器了,但是服务器具体是否要存储该信息,还需要观望观望,等握手环节结束之后,服务器才会最终保存好客户端的相关信息。这里的过程“握手”(handshake)是一个形象的比喻。在日常生活中,握手是打招呼。syn这样的数据报,不携带载荷,没有应用层数据,也就没有任何的业务逻辑,syn的作用就仅仅只是“打招呼”,所以我们在这里就形象的称这个操作为“握手”。打完招呼之后,后面我们双方就可以商讨具体的细节了(商讨细节,就是业务逻辑,应用程序/应用层可以开始完毕业务逻辑了)。
3、我们不是将建立连接的过程称为“三次握手”吗?刚才图里明明出现的是四次交互过程啊!
通过这是因为:中间的两次管理,能够进行合并。在六个关键位中,SYN和ACK是在不同位置上的,SYN是第五位,ACK是第二位,当发送的时候,让第二位和第五位同时为1即可。(我们之前讲过:网络传输的过程是需要涉及到封装和分用的。将ack和syn合并,就将原来需要两次封装分用的过程,减少为一次,既提高了效率,也降低了我们的成本)。 
下面是其他资料对TCP建立连接(“三次握手”)的描述: 
这里我们重点关注两个状态:
1、LISTEN:表示服务器这边创建好SeverSocket了,并且绑定好端口号了。
2、ESTABLISHED:已确立的。客户端和服务器已经建立好连接了(三次握手握完了)。
三次握手的意义
1、关注中间过程:三次握手,可以先针对通信路径,进行一个“投石问路”的操作,初步确认下通信链路是否通畅,就像地铁每天运营前都要先跑一趟空车那样。(可靠性的前提条件)
2、关注两端的状态:三次握手,也是在验证通信双方,发送能力和接收能力是否正常。
举个例子:
通过我和朋友玩王者荣耀,王者荣耀是需要我们两互相配合的,于是我们两得进行语言通信。在进行握手之前,我们都是不知道自己的麦克风和耳机,是否是能够正常工作的。
三次握手过程如下:
经过上面的三次握手过程,我们两就知道:自己的耳机和麦克风是正常的了。
上面的麦克风代指“发送数据的能力”,耳机代指“接收数据的能力”。
拓展问题:
能不能是四次握手呢?—— 许可但没必要,会降低连接的效率,浪费更多资源。
两次握手呢?—— 不行,原因:就是能不能上图红色字体的部分。
3、三次握手的过程中,也会协商一些必要的参数(往往以“选项”部分体现的)—— 通信是客户端服务器两方的事情,双方要进行配合,其中的内容要保持一致。
通过这个选项,大家前面提到过,最少能够是0字节,最多是40字节(TCP报头的总长度最多是2^4*4 = 60,去掉前面固定的20,还剩40字节),
其中有一个信息是十分关键的,TCP通信的序号起始值。TCP一次通信过程中,序号并不是从0或者1开始计算的,而是先选择一个比较大的数字,以这个数字开头来继续计算。即使是同一个服务器合客户端,每次连接,开始的序号都是不一样的。
上面的操作主要是为了避免一个情况——前朝的剑,来斩本朝的官。
比如下面这个情况: 
由于互联网中,会广泛存在“先发后至”的情况。
可能在第一次连接过程中,通信操作中,传输的一个数据报,在路上堵车了,迟迟没有到达对端。等到这个堵车迟到的数据报到达对端的时候,发现:咦?已经改朝换代了(已经是另一个客户端和服务器连接了)。此时,这份素材报就应该被丢弃掉。
如何识别出这份数据报到底是不是“前朝的数据报”呢?
利用我们刚才的序号来区分,我们刚才讲过:连接开始的序号的起始值,并不是固定的,而是随机分配的。因此这两次连接的序号差异就会很大,接收缓冲区读到差异很大的序号就会自动丢弃掉。
断开连接——四次挥手
连接,本质上就是让通信双方来保存对方的信息,每个客户端/服务器,都要保存对端的信息,这些信息需要一定的数据结构来进行管理。
断开连接的目:就是为了把对端的信息,从数据结构中给删除掉/释放掉。
此处的四次挥手,就是通信双方都同意断开连接(相当于“和离”),如果是单方面断开连接的情况,四次挥手不一定适用,就会有其他方式来释放这里的连接,也就是说,断开连接,不一定是这里要讲的“四次挥手”
“四次挥手”的断开连接的执行,就不一定非得是客户端先发fin(结束报文段)了,服务器也可能先发fin,和建立连接不一样,建立连接,一定是客户端主动的。
四次挥手中,谁先发fin都是可以的,看我们代码的实现逻辑了,调用socket.close()就会触发fin(fin也是内核中负责完成的,如果让进程直接结束,也会触发fin) 
fin,同样是六个标志位之一,即finish的缩写。和建立连接一样,断开连接的时候,同样会发送ack和fin报文给客户端。 
最后客户端返回一个ack表示收到。
这样就完成了“四次挥手”的过程,通信双方各自给对方发起fin,再各自给对方返回ack,“四次挥手”代表着双方已经和平分手了。
那上面四次挥手的过程,能不能像三次握手那样,把中间两次的交互合二为一呢?
如合,即有时候可以,有时候不可能。
三次握手的情况: 
无关的)。 就是客户端执行newSocket(severIp,severPort)代码时,就会发送syn数据报,中间服务器发送syn+ack的触发时机是完全一致的,都是内核在收到syn之后,就立即触发(这两个数据报的发送,和我们程序员写的应用程序的代码
但是四次挥手的过程,是和我们写的应用程序代码有关系的:
服务器发送fin的时候,是受限于代码的。还记得我们在之前的服务器代码中,最后在finally中加入的close方法吗?那就是一个会发送fin的代码,
这中间返回ack和fin的时间间隔,如果比较长,就无法进行合并了,只能分为两次进行传输(若是当前时间间隔很小,也是可能合并的)。
详细版本:
1、CLOSE_WAIT:收到对方的fin后进入这个状态,接下来代码要求调用close来主动fin(谁被动断开连接,谁才会进入这个状态)。
2、TIME_WAIT:本端给对方发起fin后,对端也给我发fin。(谁主动断开连接,谁进入这个状态) 。
进入TIME_WAIT状态后,是为了给最后一个ACK重传留有一定的时间,也是为了防止末了一个ACK丢包。
三次握手和四次挥手的相似和不同之处
相似之处:1、都是通信双方发起一个syn/fin,各自给对方返回ack。
2、数据传输顺序:syn/ack/syn/ack fin/ack/fin/ack。
不同之处:1、三次握手中间两次交互一定能合并,四次挥手则不一定。
2、三次握手一定是客户端主动的,四次挥手客户端和服务器都可以主动

浙公网安备 33010602011771号