计算机网络系列五篇文章传送门
UDP协议简介
- UDP(User Datagram Protocol:用户数据报协议)
- UDP相对于TCP来说,是一个非常简单的协议
数据报
:指的就是应用层所传输过来的一个完整的数据,UDP不会对这个完整的数据进行处理,不会进行拆分,也不会进行合并了再传输。
从数据报的定义可以看出,UDP协议的数据长度,主要由应用层传输的数据长度所决定的,应用层传的数据越长,UDP数据报文就越长。
UDP数据报的数据所处的位置:
UDP首部
端口号:
端口号在之前的文章中有提到,它标记的是「使用网络的进程」(源端口号就是原机器正在使用网络的进程,目的端口号就是目的机器正在使用网络的进程)16位UDP长度:
指的就是UDP数据报的长度(该长度包括UDP数据)16位UDP校验和:
检测UDP的用户数据报在传输中是否出错UDP数据:
实际要发的数据
UDP的特点
UDP是一个无连接
的协议
比如A和B进行电话通信,在通信之前,需要先拿出电话,然后拨号,这个是建立连接的过程。当电话拨通之后,说明连接已经建立起来了,此时就可以进行通信了。当通信之后,就将电话挂断,这个相当于结束连接。这个过程就是「有连接」的过程。而UDP是无连接的,也就是说,他在通信之前,不需要先建立连接,只要在想发数据的时候,直接就将数据发送出去了
UDP不能保证可靠
的交付数据
首先UDP是无连接的协议,在发送数据的时候想发就发,无法保证数据在网络中是否有丢失,即使有丢失,它也不会感知到从前边的UDP头部也可以看出来,它的头部非常简单,并没有提供任何的机制来保证数据可以可靠的传给对方
UDP是面向报文传输
的
UDP对应用层传输的数据并不会进行任何的处理,直接塞进UDP协议的数据中
UDP没有拥塞控制
如果把网络看做是一条公路,如果车辆特别多,就会导致拥塞。UDP并不会感知网络是否拥塞,不管是否拥塞,它都会尽量的把数据给发送出去
UDP的首部开销非常小
从上边的UDP首部图可以看出来,总共也就8个字节
TCP协议简介
- TCP(Transmission Control Protocol:传输控制协议)
- TCP协议是计算机网络中非常复杂的一个协议
TCP数据报所处的位置:
TCP协议的特点
TCP是面向连接
的协议
在上面UDP协议详解中有对面向连接进行了介绍,也就是在通信之前会先建立连接
TCP的一个连接有两端(点对点通信)
A和B要进行电话通信,那么A和B就是两个端点
TCP提供可靠
的传输服务
后边会对TCP实现可靠的传输做详细的介绍
TCP协议提供全双工的通信
全双工在网络概览中有提到,指的就是一条通信线路,双方都可以同时发送和接收消息。也就是说,如果两台计算机建立了TCP连接,那么这两台计算机都可以同时向连接中发送数据或接收数据
TCP是面向字节流
的协议
上面在介绍UDP的时候知道,UDP是面向用户数据报的协议。那么数据报和字节流的区别是什么?
流
指的是流入进程或流出进程的字节序列。传输层的数据都是由应用层传输下来的,是一块完整的数据。但是在TCP中,它不把应用层传输下来的数据看做是一块完整的数据,而是把它看做一整串的字节流。TCP不是面向一整块数据来进行处理的,而是面向一个一个的字节来进行处理的。所以,TCP就可能取出一块数据中的某一段来进行传输,而剩下的数据,再把它放到第二个TCP报文中进行传输。所以,在使用TCP协议进行数据传输时,可能对数据进行合并,也可能进行拆分,以实现更好的传输。
TCP协议头部
16位的源端口
和目的端口是和UDP中的一样的序号:
序号一共占用32个比特位,所以它可以表示的范围是0~2^32。因为TCP协议是面向字节流的,因此,每一个字节都有一个唯一的序号,这个序号就是用来标记传输的每一个字节的(一个字节一个序号)。这里的序号所代表的就是,这个TCP报文所传输的数据的第一个字节序号是什么确认号:
确认号所表示的范围也是0~2^32,也是一个字节一个确认号。确认号表示的就是期待收到数据的首字节序号是什么 假设有一个TCP数据报,它的序号是501,数据的长度是100个字节。某个计算机收到了这个数据,确认号就会说,501到600这个范围的数据,我都已经收到了,然后期望下一个传递给我的数据的确认号是601。所以确认号表示的就是,我期望下一个数据报里数据的首字节序号。确认号是配合序号一起来使用的。 如果某一个TCP数据报的数据确认号是N,则表示N-1序号的数据都已经收到数据偏移:
占4个比特位(015),单位是32位字,也就是说,每一个偏移都可以表示4个字节的偏移 它表示的就是真实的TCP数据,它偏离首部的距离(这个主要是由于TCP选项这个块的内容所导致的,因为我们并不知道这个选项的内容有多少,所以需要存储数据偏移) TCP头部有固定长度的20个字节(至少),那TCP首部最长有多少个字节? 此时可以对数据偏移进行一个简单的计算,以得到TCP首部最长有多少 因为数据偏移最大表示的是15,每一个偏移都可以表示4个字节。所以最大偏移是15乘4等于60字节,所以TCP首部的长度范围就是2060字节保留字段:
保留着的,还没有使用的TCP标记:
占6个比特位,每位都有不同的含义,他们分别是:在后边了解TCP的三次握手和四次挥手,都会用到这里的标记位
窗口:
占16个比特位,02^16-1。窗口的意思就是,指明允许对方发送的数据量。如果窗口大小为1000,则表明对方可以发送1000个字节 也可以结合窗口和确认号进行一个运算,假设确认号为501,窗口的值为1000,也就是说5011500之间字节的数据是可以接收的校验和:
和UDP里边的校验和意思是一样的,可以看一下我上一篇介绍UDP的文章紧急指针:
这个只有当前边TCP标记位的URG=1时才启用,它表示紧急数据位于报文的位置。对于TCP报文来说,是有部分的紧急数据可以保存在数据报中的,等对方接收到的时候,就可以通过紧急指针来找到紧急数据所位于数据报的位置TCP选项:
它是可选的,从前边通过对数据偏移的计算可以知道,TCP选项最多有40个字节。这个选项主要是为了支持协议未来的发展所使用的(支持未来的拓展)
可靠传输的基本原理
停止等待协议
假设现在将计算机分为发送方和接收方,之前的文章中有说到TCP是全双工通道的协议,也就是同一时刻计算机可以当做发送方,也可以当做接收方。下边是发送方计算机和接收方计算机的时间轴,停止等待协议的工作原理如下:
- 发送方生成TCP数据(消息1),然后将其发送出去,经过一段时间之后,到达接收方
- 接收方在接收到之后,再发送一个确认的消息,表示发送方发送的消息,它收到了
- 接收方将确认消息发送给发送方,一段时间后,发送方接收到确认消息。这样发送方就知道接收方接收到了它的消息
- 发送方生成消息2,然后将其发送给接收方,发送之后,发送方就会等待接收方的确认消息,当收到接收方的确认消息之后,它才会生成下一条要发送的消息
这个过程就是停止等待协议的过程,首先会发现,当发送方发送一个消息之后,它会停止生成新的消息,它会等待接收方发送确认消息,发送方接收到确认消息之后,才会生成新的消息。这整个过程就是停止---等待---停止---等待...(接收方也是一样,当没有消息进来的时候,它也会处于等待) 当然,上边其实讨论的是很理想的情况,因为网络环境是十分复杂的,数据在传输的过程中,可能会出现丢失,上边都没有考虑,所以上边讨论的就是无差错的情况。
如果消息传输过程中有差错,停止等待协议是如何处理的?
情景1:
发送方将数据发送出去之后,发生了丢失
假设发送方将数据发送出去之后,发生了丢失。也就是接收方并没有接收到消息。我们知道,当发送方将消息发送出去之后,就会等待接收方发送确认消息,发送方等待一段时间之后发现接收方并没有发送确认消息。对方是不是没有接收到消息?因此,发送方在等待一段时间之后,发现并没有接收到确认消息,就会重新发送消息,这个就属于超时重传。超时重传是保证停止等待协议可以进行可靠传输的一个方法。
情景2:
确认消息在传输过程中发生丢失
假设接收方在接收到消息之后,发送的确认消息在传输过程中丢失了。此时,发送方在等待一段时间之后还是没有收到确认消息,因此,发送方会进行超时重传,以保证发送的消息一定被接收方接收到。
情景3:
确认消息很久才到达发送方
假设接收方发送的确认消息经历了很久才到达发送方,此时也会发生超时重传,因为确认消息到的太晚,发送方会以为接收方没有收到消息
要实现超时重传,肯定是需要一个定时器,这个定时器被称为:超时定时器
,每发送一个消息,都需要设置一个定时器(用来计算一个消息什么时候过期了)。TCP中有四个定时器,超时定时器是其中一个,后边的文章会提到其它的三个定时器。
- 停止等待协议是最简单的可靠传输协议(只要消息没正确到达,就会进行超时重传)
- 停止等待协议对信道的利用效率不高(从上边的图中也可以看出来,发送每发送一个消息,都需要等待确认消息回来,只要确认消息没有正确的到达,发送方就会一直等待,这对这个连接来说是非常低效的)
连续ARQ协议
ARQ
(Automatic Repeat reQuest:自动重传请求),连续ARQ协议是在停止等待协议的基础上进行改造的。
既然单个发送和确认效率低,可不可以批量发送和确认?
正是因为这个思考,就产生了ARQ协议。例如:假设有编号为1~12的报文需要发送,这里可能就不是发送一个报文就等待一个确认消息了,而可能是连续的发送六个报文。假设发送了前六个报文之后,收到了编号为1和2的确认消息,此时会将窗口向前移动两个位置。接着就会发送编号为7和8的报文,等接收到其它报文的确认消息之后,再将窗口继续向后移动。这里的批量发送的报文大小,就被称为窗口
,可以向前滑动的窗口,就被称为滑动窗口
。
意思就是,只要是滑动窗口中的数据,都可以进行发送,只要发送出去的数据确认号到达了,就可以将窗口往前推动。其实,如果每一个报文都需要确认的话,确认消息的开销也是挺大的,因此,对滑动窗口来说,它并不需要对每一个报文都进行确认,而是采用累计确认
的方法
假设同时发送了编号为1 ~ 6的这六个报文,在某一个时刻,发送方接收到了编号为5的这个报文的确认消息。如果是采用累计确认的方法,5的这个确认消息就表示说,1 ~ 5的确认消息,发送方都已经收到了,因此就会将窗口向后移动5个位置,此时就可以发送7 ~ 11这五个报文了,这就属于累计确认。意思就是说,只要收到了某一个报文的确认消息,就表示说这个消息之前的所有消息都已经收到了确认消息。 通过累计确认,就可以大大减少确认报文的数量,以此来提升网络的效率。
TCP的可靠传输
TCP协议的可靠传输
- TCP的可靠传输是基于连续ARQ协议的
- ARQ协议中有两个重要的概念:滑动窗口和累计确认。 这两个概念在TCP的可靠传输中同样适用
- TCP的滑动窗口以字节为单位
假设有一段的字节流需要传输,滑动窗口的大小为7,这里是为了方便理解,所以窗口设的很小,实际情况下的窗口是很大的。窗口中的7个字节表示都是可以传输的,窗口左边的是已经确认的字节序号,窗口右边的是不允许发送的字节序号。图中的23表示的就是对方期待接收到的下一个字节,其实也就是TCP首部中的确认号的概念。
假如在某一个时刻23~26这四个字节已经发送出去了,但是没有收到确认信息,这个就属于已发送但未确认的字节。而窗口中剩下的三个字节就属于可用窗口,也就是后边三个字节是可以发送的。但是,因为发送出去的四个字节还没有收到确认,所以窗口还不能向后移动。假设经过一段时间之后,收到了23、24这两个字节的确认信息,此时就可以将窗口向后移动两个字节。
假设窗口中的7个字节都已经发送出去了,但是一个确认信息都没有收到,此时可用的窗口是0,因为窗口中所有发送的字节都没有收到确认,这个时候,滑动窗口不能向后推进。这是实际可能存在的情况。
假设窗口中的7个字节都已经发送出去了,在某一个时刻,收到了25和27这两个字节的确认信息。可以发现,这种情况,确认信息并不是按窗口中字节的顺序收到的,此时该怎么办?
首先可以知道的是,23、24这两个字节的确认信息是没有收到的,因此,窗口是不能往前推动的。假设现在超时时间已经到了,而23、24还没有收到确认消息,此时会怎么办?这种情况下,即使25、27这两个字节的确认信息已经收到了,但是,此时还是会从23开始重传消息,因为23、24的确认信息还没有收到。因此,超时之后,会从23开始重发消息,即使收到了后边字节的确认消息也没有用,这个就属于TCP协议的可靠传输。
从上边可以知道,如果窗口中的字节没有按序收到确认信息,那么超时之后就会对窗口中的字节进行重新发送。由此也可以看到,TCP的这种可靠传输,效率并不是很高,因为已经收到了25、27这两个字节的确认信息,意思就是这两个字节是准确的到达了接收方的,但是由于23、24这两个前边的字节没有收到确认信息,超时之后,还是需要对25、27进行传输,这样就导致可重传的效率并不高。有没有办法可以提高这个效率?有,就是选择重传。
选择重传
选择重传,顾名思义,就是可以选择一部分消息进行重传,而不是将窗口中所有的消息进行重新传输。
选择重传是如何进行工作的?
- 选择重传需要指定需要重传的字节
- 每一个字节都有唯一的32位序号(这个在TCP首部中,选择重传会指定这个32位序号是什么)
对于TCP首部,其实我们知道它并没有存储选择重传指定字节的位置。每一个字节都有一个唯一的序号,所以在存储需要选择重传的字节的序号时,也是需要耗费很多的空间的。因此,在实际的选择重传中,这个数据实际上是存储在TCP选项中的。
在上文TCP协议详解的中有进行过简单的计算,知道了TCP选项最多有40个字节。因为一个序号是占用4个字节,那也就是说,TCP选项中最多可以放10个序号。那是不是表示,选择重传只能重传10个字节?
其实并不是的,在了解TCP的时候知道,TCP的报文,一次是可以传输很多个字节的,前边的例子主要是为了方便对TCP可靠传输进行理解,才把每次可以传输的字节数设置的很少。对于实际的TCP传输,它一次可以传输上百或上千个字节。
因此,这里边的序号并不是说选择指定某一个字节的。因为,如果发生出错了,很可能整个TCP报文都丢失了,这种情况其实就连续的丢掉一段的字节,因此,这里的选择重传,更多的情况是对一段字节来进行重传的。所以这个时候序号所表达的意思,一般都是需要重传的边界。
看个例子,假设要传送10003500这么多字节的数据,把500个字节看做是一个TCP报文。假设10001500范围字节的TCP报文丢失了,这个时候就可以指定两个边界,分别是1000和1500,将他们存储到TCP首部中的TCP选项中,表示1000~1500这么多个字节,都是需要重新传输的。因此,此时TCP选项中的1000和1500并不是指重传1000和1500这两个序号的字节,而是重传1000到1500这个范围中的所有字节。
TCP的流量控制
流量控制是TCP协议特有的功能,对于UDP或者其它的一些协议,是没有流量控制。
流量控制
简单的来说就是,接收方希望发送方将数据发送的慢一些。一般来说,我们是希望越快越好,但是,还是需要考虑一些实际的情况。比如接收方不能那么快的去接收很大的流量,所以希望发送方的流量慢一些,这就是流量控制。
- 流量控制指让发送方发送速率不要太快
- 流量控制是使用滑动窗口来实现的
在TCP首部中有窗口这个字段,它占16个比特位,用来指明允许对方发送的数据量。在可靠传输的基本原理章节中有结合确认号和窗口进行了一个运算。比如说,假设确认号是501,窗口大小是1000,就表示说,发送方可以发送501~1500这个范围的1000个字节的数据,这个就是窗口所起到的作用。那滑动窗口是怎么做到流量控制的?
假设现在有一个发送方计算机和一个接收方计算机,从上往下是它们之间交互的时间轴:
- 假设发送方发送了一个序列号为1的数据,发送数据的大小是100个字节
- 发送方还可以进行第二次发送,此时发送的数据的序列号就应该是101,也假设发送数据的大小是100字节
- 此时接收方已经接收到了200个字节的数据,假设此时接收方发送了一个确认消息,确认号是201,201表示的就是期待接收到的下一个字节的序号,并且发送的确认消息中还有一个窗口的大小(rwnd),假设是300,表示告知发送方还可以发送300个字节的数据。确认消息中的ACK标记为1
- 发送方在知道了窗口大小为300之后,它就可以继续发送300个字节的数据。首先它可能就会发送序列号为201的100个字节的数据,此时发送方还能再发200个字节的数据
- 假设此时发送方发送了序列号为301的200个字节的数据,此时接收方就可以进行确认了,确认说,它已经接收到了601序列号之前的所有数据了,此时窗口大小为0,也就是说发送方不能再进行数据发送了
以上就是通过窗口大小控制对方发送速率
假如接收方发送了窗口为0的消息之后,它马上对接收到的数据进行处理,处理之后交给应用层,一段时间之后,接收方就又可以接收信息的消息了。此时,接收方就会向发送方发送一个消息,告诉它说,当前我的窗口是1000,也就是可以接收1000个字节的数据。发送方在收到这个消息之后,发送方又会进行数据的封装,并且将数据发送给接收方。这个就是接收方通过调整窗口的大小来告知发送方可以继续的发送数据。
假设接收方在通知发送方,它的窗口为1000的消息在传输过程中丢失了。这种情况会导致什么样的后果?
- 对于发送方:发送方会一直等待,因为它还是以为接收方的窗口为0,所以它一直等待接收方的窗口调大。
- 对于接收方:接收方已经将窗口调大的消息告诉发送方了,理论上,发送方在收到消息之后,会将信息的消息发送给接收方。因此,接收方也会一直等待。
所以,因为窗口数据报文的丢失,导致发送方和接收方都会一直的在等待,形成死锁的局面。此时可能就会有疑问,TCP不是可靠传输的吗?这个消息为什么会丢失呢?
其实在讨论TCP的可靠传输时,主要是从数据的角度去考虑的。也就是说,对于TCP的可靠传输,都是对数据的确认。比如序列号、以及确认号都是对数据的字节进行确认的,对于特殊的消息,比如窗口的大小,其实是没有超时重传机制的,因此就会出现上边提到的死锁局面。
要处理上边出现的死锁情况,就需要TCP中的第二个定时器——坚持定时器。
- 当发送方接收到窗口为0的消息,则启动坚持定时器
- 坚持定时器每隔一段时间发送一个窗口探测报文(用来询问窗口有没有增大,这样就可以解决因发送窗口大小的消息丢失导致的死锁局面)
TCP的拥塞控制
当网络中的数据报文过多的时候,就会造成网络的拥塞。
网络拥塞的根源
- 一条数据链路经过非常多的设备
- 数据链路的各个部分都可能成为网络传输的瓶颈(网络中各种路由器的性能可能不一样、或者传输媒介性能有差别)
- 网络对一些硬件设备的性能要求,大于可用资源,因此就导致了拥塞
TCP的拥塞控制和TCP的流量控制有什么区别?
流量控制
考虑点对点的通信量控制(主要是通过窗口来控制通信量,考虑接收方的接收性能)拥塞控制
考虑整个网络,是全局性的考虑(它会感知到整个网络是否发生拥塞)
拥塞控制是一个很庞大的问题,因为它考虑到了整个网络,并且对于拥塞控制,很难有最优解。这里只对拥塞控制有一个简单的认识。
如果要进行拥塞控制,首先需要有一个方法去判断网络是否发生拥塞。判断的方法简单粗暴,如果发送方发送的报文发生了超时,就认为网络发生了拥塞。但是,通过报文超时来判断网络是否一定拥塞是不成立的。
如果在传输的某一个阶段,把光纤给断了,这个也会导致报文超时,这时就不是因为拥塞所造成的了,而是网络故障所造成的。所以,报文超时只是判断网络拥塞的一个方法(下边的内容先不考虑网络故障的情况)。
拥塞控制的两个算法
慢启动算法
- 由小到大逐渐增加发送的数据量
- 每收到一个确认报文就加一(假如第一次收到了1个确认报文,下一次就发送2个报文;如果第二次收到2个确认报文,下一次就发送4个报文,依次1、2、4、8)
可以看到发送的报文是按指数增长的,指数增长的增长速率是非常快的。慢启动算法,它的指数增长会有一个阈值,称为慢启动阈值(ssthresh),增长到这个慢启动阈值之后就不再增长了。增长到这个阈值之后,它就会进行第二个算法。
拥塞避免算法
- 维护一个拥塞窗口的变量(这个变量大于慢启动阈值)
- 只要网络不拥塞(即只要报文不超时),就试探着增大拥塞窗口(每次加一)
假设慢启动到达了阈值(假设是16),此时就会启动拥塞避免算法,它会试探着将拥塞窗口调大,如果16个报文都收到了确认,它就会再发送17个报文,如果没有发生超时,下一次就会发送18个报文。一直这样一个一个的调大,直到发生拥塞。这就是拥塞避免算法。拥塞避免算法可以保证在网络不发生拥塞的情况下,更多的发送数据。
这是一张慢启动算法和拥塞避免算法的图(纵坐标:每一次发送数据报文的数量;横坐标:发送的轮次):
在到达阈值之前,数据报文的数量是指数增长的。当数据报文的数量达到阈值的时候就会启动拥塞避免算法,之后数据报文的数量就是线性增长的。
TCP的三次握手
首先要明白,TCP的三次握手是用来建立TCP连接的。
TCP标记
在TCP协议中,TCP首部有一个6比特位的TCP标记,是TCP首部的其中一个字段。TCP标记有不同的含义,分别是:
TCP三次握手过程
假设有一个发送方计算机和一个接收方计算机,纵向为时间轴:
第一次握手
假设首先是发送方主动和接收方建立连接,所以,发送方会第一次发送一个报文(此时SYN=1,表示这是一个连接请求的报文,seq=x是同步发送方自己的序列号)
第二次握手
接收方在接收到连接请求后,也就打开TCP连接,同时它也会发送一个报文,这个报文是第二次握手。报文信息中有:
- SYN=1:表示是一个连接请求
- ACK=1:表示对序列号的确认
- ack=x+1:小写的ack表示的是确认号。这里的ack=x+1,表示接受方期望收到的是x+1这个序列号的值
- seq=y:同时接收方发送的报文中也会携带自己的序列号,也就是seq=y
第三次握手
发送方接收到报文之后,会进行回应,回应中的报文内容:
- ACK=1:表示这个报文的确认号是有效的
- seq=x+1:发送方所携带的序列号,表示的是,当前发送方发送的数据序列号是x+1
- ack=y+1:确认号是y+1,表示发送方期望接收到接收方的序列号是y+1的数据
通过这三次的握手,TCP的连接就建立起来了。
三次握手中关键的信息:
- 第一次和第二次握手都有SYN标记,表示这是一个连接的请求
- 第二次和第三次握手都有ack标记,对于ack这个标记,它其实是先对连接双方的序列号进行同步。比如说,通过两次的ack同步,发送方已经知道了接收方的ack是什么了,同时,接收方也知道了发送方的ack是什么了,通过三次握手,它们不仅仅将连接建立起来,并且也同步了各自的序号
在三次握手的时间轴中,不同的时间,接收方和发送方有不同的状态:
- 在接收方没有接收到数据之前,它一直处于监听状态(Listen)
- 发送方在第一个报文发送出去,到接收到第一个报文的响应之间,属于同步已发送状态(SYNC-SENT),表示已经将SYN发送出去了,并且等待对方的SYN信息
- 从接收方发送第一个报文,到接收到第二个报文之间,属于同步已接收状态(SYNC-RCVD),表示发送方发送给我的SYN信息,我已经收到了
- 然后发送方就进入建立连接(ESTABLISHED)的状态了
- 对发送方来说,只要第二次握手成功之后,发送方就建立起连接了。但是对接收方来说,只有接收到发送方的第三次握手之后,才是建立连接的状态(ESTABLISHED)
双方对于建立连接状态的时间是不一样的,发送方只要在第二次握手成功之后,就变成了建立连接的状态。但是对接收方来说,只有接收到发送方的第三次握手之后,才是建立连接的状态。双方都进入建立连接的状态之后就可以进行数据的传输了。
重要思考1:为什么发送方要发出第三个确认报文呢?为什么两次不行?
结论:避免已经失效的连接请求报文传送到对方,引起错误
假设此时有一个发送方计算机和一个接收方计算机。首先发送方需要发送一个建立连接的请求报文(第一次握手),假设第一次握手的报文在网络中传输很久才到达接收方,因为发送了很久,所以,发送方很久都没有收到接收方的确认消息。发送方就会认为第一个报文已经超时了,所以,发送方就会第二次发送同样的报文。
假设第二次发送的报文,很快就到达了对方,接收方在收到第二次的连接请求报文之后,就会进行回应,并且建立起它们之间的连接。那么,对于发送方发送的第一次的请求报文,就应该是一个失效的请求报文,因为它的功能已经被第二次的连接请求所完成了。所以,对于第一次发送的请求连接报文,在网络中游荡了很久,其实就是一个失效的请求报文了,没有作用了。
如果发送方发送的两次连接请求都建立起连接了会怎么样?
首先考虑第二次请求的报文,这个报文是提前到达接收方的,接收方会对它进行一个回应,回应确认之后,就建立起连接了(因为我们是假设两次握手就建立起连接)。
现在考虑第一次发送的连接请求,如果两次握手就建立连接的话,对于失效的请求,它也会建立起连接,因为只要接收方回应了,就表示连接已经建立了。
这样就会导致,同样的请求发送了两次,就会建立两个TCP连接的情况。这种情况是错误的,所以说,两次握手是不正确的。
重要思考2:三次握手是如何解决两次握手导致的问题?
对于两次握手,只要接收方回应了,就表示连接建立了。而对于三次握手来说,第一个确认报文会首先到达发送方,然后发送方再发送一个确认报文(第三次握手),此时才算建立起连接。
现在来考虑那个比较慢到达接收方的连接请求报文,这个报文,接收方也会发送一个确认报文给发送方(第二次握手)。但是发送方已经进行第三次握手了,因此发送方对于第二次的确认消息会忽略掉,并不会进行任何的操作。这样,第一次比较慢到达的连接请求就不会建立起连接,这就避免了两次握手所导致的错误。
上边就是三次握手的作用,它避免了失效的请求到达对方,并且引发不应该有的错误。
TCP连接的四次挥
上一节介绍了TCP的三次握手,本节是相反的过程,是对TCP连接的释放。
TCP四次挥手过程
还是假设这里有一个发送方结算机和一个接收方计算机,纵向为时间轴。连接正常的时候,双方是可以一直进行数据传输的。假设数据传输完成了,此时就会进行TCP连接的释放。假设发送方主动的进行了连接的释放:
image.png
第一次挥手
发送方发送第一次挥手的报文,报文内容:
- FIN=1:该标记表示需要释放连接
- seq=u:同步自己的序列号给接收方
此时发送方就进入了连接结束的第一个等待状态(FIN-WAIT-1)。
第二次挥手
接收方在收到发送方的断开连接请求之后,它也会发送一个报文去确认,确认对方给我发送的序列号我已经收到了,确认报文内容是:
- ACK=1:表示这个报文已经确认
- seq=v:同步自身的序列号
- ack=u+1:确认号是u+1,表示接收方期望接收到接发送方的序列号是u+1的数据
发送方接收到确认报文之后,就进入了连接结束的第二个等待状态(FIN-WAIT-2)。而接收方在发送了第一个确认报文之后就进入了关闭等待状态(CLOSE-WAIT)。
这个时候其实接收方还是可以进行数据的发送的,因为释放连接的请求是发送方发起的,表示说发送方的数据发送完成了,但是接收方可能还没有发送完成。
第三次挥手
接收方发送完第一个确认报文之后,又会发送一个新的报文,这个报文会携带FIN=1的标记,表示它也可以进行连接释放了,并且里边会携带一个ack,表示重复的对发送方发送的序列号进行确认,该报文中的完整内容:
- FIN=1:该标记表示需要释放连接,是一个释放连接的请求
- ACK=1:表示确认报文已经收到
- seq=w:给发送方同步自己的序列号
- ack=u+1:确认号是u+1,表示接收方期望接收到接发送方的序列号是u+1的数据
第四次挥手
发送方接收到接收方的确认报文之后,又会发送一个确认报文,确认接收方发送的报文我已经收到了,此时可以释放掉连接了。
在接收方发送断开连接的请求到发送方的确认报文被接收方收到这之间,接收方处于最后确认状态(LAST-ACK)。是为了确认发送方已经接收到了连接释放的报文,此时发送方进入了等待计时器
状态(TIME-WAIT)。发送方会在这个时间等待状态中等待一段时间,确保这段时间没有出现任何的问题,此时才进入关闭状态(CLOSE)。
等待计时器
等待计时器,它会等待2MSL(MAX Segment Lifetime最长报文段寿命),通常MSL是设置成2分钟的。
我们知道每一个TCP连接都会占用一个端口,在一个连接状态中,如果想启用另外一个网络进程去复用这个端口的话是不行的,因为这个端口已经被占用了。在等待计时器这个状态中,连接是不会释放的,也就是不会释放当前占用的端口。 只有在等待计时器状态结束之后才会释放这个端口。
其实平时如果在进行TCP编程的时候,会发现,如果你主动的释放了这个连接,然后马上复用这个端口,其实是不行的。主要是因为主动释放的这一方进入了等待计时器状态,在这个状态中,是不会释放占用的端口的,需要等计时器结束。
为什么需要等待计时器?为什么是2MSL?
只要发送方发送了第四次挥手的报文之后,就进入可等待状态,在进入等待的时候,最后一个报文是没有进行确认的。这个等待计时器主要是为了确保发送方的ACK可以到达接收方。
2MSL是报文可以在网络中存活的最长的时间,如果在2MSL的时间里,第四次挥手的报文没有被接收方接收到的话,接收方就会认为,我发送的第二次报文(也就是第三次挥手的报文)没有被发送方接收到,因此接收方会把第三次挥手的报文再发送一次,也就是重复一次第三次挥手。因此发送方会重新的构造一个报文,再次进行第四次挥手,这就是等待计时器的作用,它主要是为了确保第四次挥手的报文可以正确的到达对方,如果没到达,接收方就会重新发送一次第三次挥手的报文。
等待计时器其实还有一个作用就是:确保当前连接的所有报文都已经过期了(因为最后一个确认断开的报文都已经过期了,其它的报文肯定也已经过期了)。
套接字
通过端口(Port)可以唯一的标识不同的网络进程
。如果有一个进程在使用网络的话,那肯定是会占用一个端口的,计算机就是通过这个端口来区分不通的网络进程的。
端口(Port)使用16比特位表示(0~65535)。由端口以及IP就可以指定网络中某一台主机的具体进程是哪一个「IP:Port」。关于IP和端口的组合,有一个名字叫:套接字
(Socket)。
套接字(Socket)是抽象的概念,表示TCP连接的一端(我们知道TCP是端到端(点到点)的通信,两个端点之间会有一个TCP连接来进行通信,这个套接字就可以表示通信的一端)。
通过套接字可以进行数据发送或接收。很多时候对网络编程的时候,实际上就是对套接字的编程,通过套接字来进行数据的发送和接收。
因为TCP连接是由两端所组成的,因此就可以表示为两个套接字,通过这两个套接字就可以指定唯一的一个TCP连接,而套接字又可以表示为IP和端口的组合(一个IP可以有多个套接字,因为它可能会有不同的端口)。
TCP
={Socket1:Socket2}
={{IP:Port}:{IP:Port}}
如果平时对套接字进行编程的话,很多时候都是将这个架构看做是C/S架构。客户端和服务端通过TCP连接连接起来,不管是客户端还是服务端,都会使用一个Socket来进行数据的发送和接收。C/S架构的TCP通信过程:
- 客户端:创建套接字 -> 绑定套接字 -> 监听套接字 -> 接收/处理信息
- 服务端:创建套接字 -> 连接套接字 -> 发送信息
网络套接字与域套接字的区别?
对于网络套接字,不管是跨计算机还是在同一台计算机,如果使用网络套接字的话,数据都会经过网络中的协议栈。
域套接字主要是通过域套接字文件
来进行通信的。如果通过域套接字来进行通信的话,数据就不需要经过协议栈。所以,如果是单机的通信,推荐使用域套接字进行通信,因为它处理流程简单,而且不经过协议栈,对系统的消耗比较小。如果是跨机器或跨网络的通信,就必须得使用网络套接字进行通信。
UDP和TCP应用
UDP和TCP是两类非常不同的传输层协议,在两协议之上,就会有不同的应用层服务:
UDP主要提供多媒体分发
:视频、语音和实时信息等(因为这些场景下,即使丢失了一秒的数据也没有关系,马上就有下一秒的数据到来)。TCP主要提供可靠消息传输
:金融交易、可靠通讯和MQ(消息队列)等。