在上一篇文章里讲到了网络的分层,提到了TCP/IP组模型,html
以下:git
可是是否有这样的疑问,为何网络要进行分层呢?github
计算机网络是一个很是复杂的系统,要想在其中两台主机创建链接,必需要考虑很是多的场景,假如咱们要在两台主机之间传送一个文件,那么咱们须要考虑的方法具体有下面几点:算法
这里只列举了大概几种状况,还有其余不少场景要处理,我这里就不一一列出来了;缓存
从这里能够看出,就只是一个文件的传递,都须要考虑这么多场景,可想而知,计算机网络是何其的复杂;服务器
那么既然有这些问题存在,让咱们试想着如何去解决这种问题;markdown
既然场景这么多,那么咱们能不能将这些场景进行归类,而后进行分层,每一类场景交由某个类别去进行处理呢?网络
答案是:能够的;socket
上面这个文件的传送,咱们能够归类为三层:文件传输,通信服务,网络接入这几层;tcp
看一下大概的流程图:
固然上面只是咱们的假想,实际上的模块划分远比这个复杂的多;
好比最开始的OSI模型,足足分了七层;
到这里你是否有疑问了,这样进行分层的好处有哪些呢?请看下面;
分层的好处:
TCP(传输控制协议)和UDP(用户数据报协议)是传输层的协议,基于网络层IP协议来实现的;
TCP协议为两个主机的通信提供可靠性传输,经过将数据切分为适合传输的大小,使用超时重传,确认到达等一系列操做来保证数据能够准确的进行传输;因为提供了端到端的可靠性传输,应用层的软件就不须要关心传输的相关细节了;
UDP协议提供简单快速的数据传送,可是不保证数据的准确性,稳定性,所以使用这个协议的应用层的软件须要考虑稳定性和准确性的问题;
应用程序在通信的时候,须要先创建起链接,而创建链接的基础须要先标志本身的进程id,也就是PID,若是只是本地通信的话,PID基本上不会有冲突,可是和网络上的其余主机通信时,PID的冲突概率就会大大增长;
那么要怎么解决这个问题呢?
咱们知道IP层的协议的IP地址在网络中是惟一的,不会和其余主机冲突,那么咱们能够利用这个来避免端口号冲突,也就是IP地址+端口号的实现;
而这种标识就是socket通信,最终的格式:Socket=(IP地址:端口号);
socket翻译过来的意思就是插座,用于应用程序经过网络进行通信,也被称为套接字;
套接字能够看出是两个应用程序经过传输层传输数据时,使用的端点,而套接字是一个抽象的概念,具体实现是由计算机来实现的;
好比当前主机要和另一台主机进行数据的传输,会先将数据写入到socket中,而后经过传输层传输到另一台主机的socket中,而后应用程序再从socket中读取数据;
socket是"打开—读/写—关闭"的实现模式,具体实现以下:
TCP是面向链接的协议,也就是在进行通讯以前,必须先创建起链接,通讯完以后,必需要释放链接;
每条TCP链接只能有两个端点,不能一对多,或者多对一,只能一一对应,不支持多播(multicast)和广播(broadcast)的传输方式;
TCP提供可靠交付,保证数据不出错,不重复,不丢失而且有序的传达给对方;
TCP通讯的双方均可以同时向对方发送数据和接收对方发送的数据,而且在本地设置有发送缓存和接收缓存,用于应用程序发送数据和取数据,这样应用程序就不用关心数据的发送和接收了,只须要将数据交给传输层便可;
流:指的是流入到进程或者从进程流出的字节序列;
应用程序将要传输的数据写入到TCP的发送缓存中,而后TCP再根据实际传输,从发送缓存取出必定的字节序列来进行传输,而另外一端接收到数据后,会将数据放入到接收缓存中,应用程序再从接收缓存中读取字节数据;
这其中,应用程序写入发送缓存的字节序列并不表明就是TCP要发送的字节序列,TCP会将发送缓存中的字节序列拆分红适合传输的序列来进行传输;
这个图只讲了大概的流程,实际上数据还得经过IP层和数据链路层的封装才能进行传输;
(1)源端口号和目标端口号:每一个TCP段都包含源端口号和目标端口号,用于确认TCP数据的来源和要发送的端口号,而计算机上的程序是经过监听端口号来获取TCP数据包的;
(2)序号:用于标记这个TCP包的顺序,由于TCP在传送过程当中,会先将数据切割为无数个小的数据包,标记了顺序以后,在送达到终端时,从新拼接的时候就不会出错,用于解决数据包错乱的问题; 既然每一个传输的字节都被计数,确认序号包含发送确认的一端所指望收到的下一个序号。所以,确认序号应当是上次已成功收到数据字节序号加1。只有ACK标志(下面介绍)为1时确认序号字段才有效。
(3)确认序号:用于确认接收终端须要接收的下一个包的序号,经过为当前包的序号+1,用于保证包传送的顺序;
(4)头部长段:因为TCP首部包含一个可变的部分,所以须要一个值来表示当前TCP数据的长度,也就是头部长段;
(5)保留位:留待之后使用;
(6)标志位:指数据包的属性,用于控制TCP的状态机;
URG
: 表示报文段是否包含紧急数据,当URG=1时才表示有紧急数据;
ACK
: 表示报文段确认序号是否有效,当ACK=1才表示有效,而TCP创建链接以后,发送报文的标志位必须是ACK=1;
PSH
: 用于告知接收方是否须要当即将数据传递给上层,当PSH=1生效,不然就缓存起来;
RST
: 当RST=1,表示当前连接出现了不可知的错误,须要从新创建链接以确保正常的通信;
SYN
: 在创建链接时使用,当SYN=1时表示用于请求创建链接或者相应创建链接,用于TCP握手使用;
FIN
: 用于标记数据是否发送完毕,当FIN=1时生效;
(7)窗口:大小有字节表示,申明接收端所指望接收的字节大小,用于流量控制,不至于传的数据包过大致使接收端接收不了数据,或者传的数据包太小,致使浪费网络资源;
(8)校验和:用于TCP数据的校验,是TCP传输可靠性的保障之一,由发送端填充,接收端进行校验;
(9)选项:用于额外的功能实现;
TCP状态机是指网络通讯过程当中,状态变化的状况;
在TCP通讯的三次握手和四次挥手的过程当中,就是TCP状态变化的过程,因为TCP是面向链接的,具备可靠性的链接,每个要通讯的主机都得和对方创建起链接,而这过程会经历链接,传输,关闭这几个步骤,在双方进行通讯的过程,TCP的状态变化是不同的;
来看一下《计算机网络》关于状态机的流程图:
至于状态机的变化,咱们下面的三次握手和四次挥手会讲到;
对于人类来讲,握手表示双方友好交互的意思;而对于计算机来讲,握手的意义就大不相同了;
来看一下wiki对于计算机握手的定义:
意思就是:在信息传输以前,握手用于确认参数和其余协议特性达成一致;
而对于tcp的握手来讲,是两台主机之间互相交换一次tcp数据,用于确认参数和序列;每一次握手,主机都得向对方发送一次tcp数据;
1,客户端发送信息告诉服务端我要进行链接;
这时候发起链接的主机会先发送一个TCP数据包,数据包含标志位SYN=1,序列号 seq=x,而后发起链接的主机进入SYN-SENT(同步已发送阶段);
第一次握手完成;
2,服务端告诉客户端我收到请求,而且准备好接收数据了;
收到发起链接请求的主机会先解析数据,而后发送一个TCP数据包作为回应,数据包包含 ACK=1(表示当前确认号有效),SYN=1(表示请求链接有效),确认号ack=x+1(表示指望下一次收到TCP数据的序列号), seq=y(表示为本身初始化一个序列号),而后接收链接的主机会进入SYN-RCVD(同步接收状态);
第二次握手完成;
3,客户端发送消息表示我要开始发送数据了,服务端收到后表示链接创建成功;
发起请求的主机接收到另外一台主机发送的确认数据后,会再向要链接的主机再发送一个确认数据,数据包包含ACK=1(表示当前确认号有效),确认号ack=y+1(表示指望下一次收到TCP数据的序列号),seq=x+1(本身的初始化序列号+1),此时TCP创建链接成功,发起链接的主机进入ESTABLISHED状态(表示创建链接成功);
第三次握手完成;
TCP链接成功创建;
看一下流程图:
让我引用《计算机网络》一书上的说法:
为了防止已经失效的链接请求报文段忽然又传到服务端,于是产生错误;
对于这句话的解读:
对于从客户端发送到服务端的请求链接的报文,由于网络上的延迟,或者阻塞等缘由,致使链接已经释放后再被服务端接收到,此时服务端再给客户端发送报文表示链接创建成功;
这时候发送给服务端的报文有多是已经失效的报文了,而服务器在接收到报文后,就创建起链接,这样会致使不少无效链接的创建,大大的浪费了资源;
而客户端接收到服务端发送的确认报文时,客户端须要再发送报文确认是否要创建链接,而不是发送一次报文就创建起链接了;
那么事实上真的是这样吗?
上面这种说法实际上是偏向于抽象的说法,而实际上还得从TCP的可靠性传输提及:
为了实现可靠性传输,TCP通讯的双方都必需要先维护一个初始化的序列号,标识当前发送TCP数据的序号,用于接收到TCP数据的主机判断哪些数据是收到的,哪些是没收到的;
而三次握手的过程是为了告诉对方本身数据的起始
序列号
,和指望下次收到数据的确认序列号
,而一次握手,两次握手都是不能实现这种操做的,若是是四次握手呢? 能够,可是没有必要,由于会浪费资源;
三次握手的目的就是为了同步双方的序列号,用于可靠性传输的保障,这个下面会讲到;
具体以下所示:
挥手的意思,对于人类来讲就是告别的含义,挥挥手不带走一片云彩。
而对于TCP链接来讲, TCP链接断开时,要断开的两台主机会互相发送报文给对方,每发送一次报文就叫一次挥手;
假设有两台已经创建起链接的主机,分别为主机A和主机B,主机A主动请求断开链接;
第一次挥手:主机A会发送一个报文段(序列号seq=p,标志位FIN=1),而后进入FIN-WAIT-1
状态,发送了标志位FIN=1以后,表示我要关闭这个链接,后续将不会再有数据传输过来,可是这时候主机A仍是能够收到主机B发送过来的数据;
第二次挥手:主机B接受到主机A发送的FIN报文段,会发送一个确认报文段,标志位ACK=1,确认序列号ack=p+1,而后就进入CLOSE-WAIT
状态,这个过程表示接受到关闭方发送的关闭请求了。
这时候主机B就处于半关闭状态,虽然主机A不会再发数据过来,可是主机B还可能会发数据给主机A;
主机A接收到确认报文后,会进入FIN-WAIT-2
状态,而后等待主机B发送链接释放报文;
第三次挥手:当主机B的数据都发送完毕后,就会想主机A发送释放链接的报文,主机B发送FIN报文段,标志位FIN=1,序列号seq=q,确认序列号p+1,标志位ACK=1,而后就进入LAST-ACK
状态;
第四次挥手:主机A接收到关闭报文段后,会发出确认报文段,标志位ACK=1,确认序列号ack=q+1,序列号seq=u+1,而后进入TIME-WAIT
状态,此时TCP链接尚未释放,会等待2∗MSL(最长报文段寿命)的时间后,才会进入CLOSED
状态;
主机B接收到确认报文以后,才会进入关闭状态,至此,TCP链接结束,在此看来,接收关闭的主机TCP链接的关闭时间会更快一些。
为何要等待2∗MSL以后,才会进入关闭状态呢?
MSL的全称为:Maximum Segment Lifetime
,翻译过来的意思是:最长报文端寿命,表示报文在网络上存在的最长时间,超过这个时间报文将被丢弃;
这个值是由当前主机设置,TCP容许设置为不一样的值;
主要的做用有两个:
(1)保证客户端发送的最后一个ACK报文可以到达服务器,由于这个ACK报文可能丢失
总结,虽然进行了4次挥手,可是作的操做不止四次;
挥手只是一个告知的消息,发送方和接收方都得作出响应操做;
请看流程图:
上面咱们分析了TCP的四次挥手,那么是否有这样的疑问,为何TCP链接断开要进行四次挥手?
在分析为何要四次挥手以前,咱们先来了解一下TCP链接的特性,TCP链接是全双工通讯的;
全双工通讯的意思是TCP链接的双方均可以同时进行发送和接收消息,这个上面有讲过;
由于TCP链接的双方都能同时进行发送和接收消息,那么要关闭链接的话,链接的双方都要关闭,不能只关闭一方;
假设主机A和主机B正在进行TCP链接通讯,这时候主机A想断开链接,那么创建起链接的双方都要关闭发送和接收通道,这仅仅发送一次报文的没法实现的,主机A关闭本身的发送通道和接收通道,得进行两个报文的发送,那么主机B同理,也是得发送两次报文来关闭本身的发送和接收通道;
那么到这里你是否就会有疑问?
我发送一次报文,就关闭本身的发送通道和接收通道不就好了,这样TCP断开链接只须要两次挥手就能够了,不是吗?
固然理论上是能够这样作的,只要发送一次报文,就关闭本身的发送和接收通道,可是实际状况为何不是这样作的呢,且听我细细道来;
假如要断开链接的主机A,发送一次TCP报文,告诉主机B我要断开链接,而后就关闭本身的读写通道;
那么这里会致使什么问题呢?
(1)关闭了写的通道,表示我没有数据要发送给主机B了,若是在网络正常不丢包的状况下,是能够直接关闭写的通道的,可是若是发送的这个数据丢包了,主机B没有接收到这个报文,那么主机B就认为当前的链接有效,就继续从主机A读取数据,这样会致使传输出现问题;
(2)关闭了读的通道,表示我不读取主机B发送过来的数据了,可是这时候并不肯定主机B是否还有没有数据要发送过来,若是有数据要发送过来,而主机A却关闭了读的通道了,那么也会致使传输出现问题;
既然无法发送一次TCP报文来关闭读写通道,那么要怎么作才能比较合理,比较符合TCP可靠传输的特色呢?
那么接下来就要来说讲前人智慧的结晶了;
假如让你来设计一个关闭TCP链接的方案,你会怎么设计呢?
首先咱们先来看一下TCP链接关闭的要求:
(1)TCP链接的主机双方都要关闭读写通道;
(2)TCP链接的主机双方都要保证数据的正常传输;
(3)要符合TCP可靠传输的特色;
那么咱们知道了上面的要求,接下来来看看怎么设计比较好呢?
既然不能一次性关闭读和写的通道,那么咱们来分开进行关闭;
假设有两台正在进行TCP链接的主机,分别为主机A和主机B,主机A要求要断开链接;
(1)首先主机A先发送一次报文,告诉主机B,我要关闭写的通道了,表示我没有数据要发送给主机B了;
(2)这时候主机B有两种状况,一种状况是正常收到了主机A发送的要关闭写的通道的报文,另外一种状况是没有收到主机A发送的要关闭写的通道的报文;
第一种状况:当主机B收到报文时,知道了主机A不发送数据过来了,那么主机B就关闭读的通道,那么这时候须要通知一下主机A,表示我已经收到你的报文了,否则的话主机A并不知道主机B到底收没收到;
第二种状况:主机B没有收到报文,这时候主机A怎么确认主机B是否收到了报文了,固然得要主机B告诉主机A才行,可是主机B没有收到报文,就不会告诉主机A是否已经收到报文了,那么主机A在发送了报文一段时间后,若是没有收到主机B的回应,那么主机A就得再发送一次报文给主机B;
综合上面两种状况,咱们知道,主机B收到报文后,必需要告诉主机A,我已经收到报文了,能够不用再发送报文给我了;
那么到这里主机A能够正常关闭写的通道,主机B能够正常关闭读的通道;
主机A关闭了写的通道后,尚未关闭读的通道,而主机B关闭了读的通道,尚未关闭写的通道;
接下来咱们继续分析:
(1)主机B在发送完全部数据后,发送报文告诉主机A,表示我这边数据已经传输完毕,要关闭写的通道了;
(2)这时候主机A也面临着上面的这种状况,一种是收到,一种是没收到,我这里就再也不多说了;
最后的状况也是主机B发送报文给主机A,表示要关闭写的通道,而主机A接收到报文后,必需要发送报文给主机B,表示我已经收到了,这个和上面的状况同样,这里就再也不多说;
至此,TCP链接的双方要正常的关闭,就必需要发送四次报文,也就是经典的四次挥手
;
我这里只是讲了大概的原理,实际状况远比这要复杂多,这其中还涉及到状态机的变化,这个能够看看上面四次挥手的分析👆;
TCP是基于IP层的协议来进行传输数据的,而IP层的协议是不靠谱的传输协议,那么TCP协议是怎么基于不靠谱的IP层协议来作到可靠传输的呢?
接下来让咱们来看看他的真面目吧!
什么是校验和?
TCP校验和(Checksum)是一个端到端的校验和,由发送端计算出来,而后接收端进行验证,用于校验数据在发送前和发送后时候一致,若是接受方检测到不一致了,则会丢弃该数据;
每个数据包都有序号,用于数据有序的传输,TCP在接收到数据后,会根据序号进行排序,而后再把数据交给应用层处理;
而主机接受到TCP数据后,必需要发送一个确认应答的TCP数据,用于表示我已经收到传过来的数据了,序号是多少,而且告诉发送的主机下一次发送时,起始序号是多少;
如图所示:
在进行TCP传输时,每次接受到数据后,都会给发送端返回一个确认应答,用于确认接收端已经接收到数据了;
可是实际上网络是很不稳定的,数据发送了,接收端不必定就会接收到数据,或者接收端接收到了数据,可是确认报文发送端不必定能接收到;
主要有两种状况:
(1)发送端发送了数据,可是因为延迟的问题,致使接收端迟迟没有接收到数据;
(2)接收端接收到了数据,可是也是因为网络问题,致使发送端没有接收到确认报文;
那么这上面两种状况,TCP是怎么来解决的呢?
TCP引入了一种叫超时重传的机制,能够理解为,当数据发送了以后,通过一段时间,没有收到响应,那么就再从新发送一次数据给对方;
这里的超时重传机制是根据是否收到确认报文为准的,也就是说上面两种状况均可以归类为一种状况,就是发送端是否能够接收到确认报文;
而报文的超时时间是动态计算的,由于网络的复杂性,若是超时时间设置为固定的,那么有可能会形成超时时间过长或者太短的问题,而超时时间的计算是由一个严谨的算法来计算的,这里就不过多深刻了;
感兴趣的请参考:超时重传的时间计算
(1)窗口
从传输的角度来看,咱们确定但愿传输速度越快越好,那么我是否能够把全部数据都一次性发给对方呢?
很明显是不能够的,若是数据小的话可能不会有问题,可是若是数据量很大的状况下呢,本身的发送缓存和对方的接收缓存是否能够放得下这么多数据呢,这种作法是不严谨的,而且很容易出错,这并不符合TCP可靠性传输的特性;
那么咱们怎么来判断要传的数据的大小呢?
原理很简单,就是由对方来告诉你,接收的一方来告诉发送方我能够接收多大的数据而不会出错;
那么TCP是怎么实现的呢?
经过TCP的窗口,这个在上面的TCP报文那里有讲过👆;
(2)传输效率
试想有一种状况,当接收方的缓存已经满了,而应用程序每次都只从TCP缓存中取出一个字节的数据,而后接收方就告诉发送方,我如今能够接收一个字节的数据了,这样会不会有问题呢?
一次传输一个字节的数据,虽然不会有什么问题,可是对于传输的效率来讲,效率过低了,一次传输只传输了一个字节的数据,会形成资源的浪费;
那么要怎么解决这种问题呢?
经过让接收方多等待一端时间,等到接收缓存中的剩余空间较多时,才发送确认报文,并带上窗口信息来告诉发送方,能够发送对应窗口大小的数据了,这样就能够避免传输效率的问题;
网络的传输也是须要进行控制的,试想若是在网络能接收传输的程度下,咱们无论不顾的往网络中传输超过其能接受的程度,那么就会形成网络的阻塞;
好比一条高速公路,其能容纳的车流量也是有限的,若是是正常的状况下,高速公路是能够正常运行,不会堵车的,可是若是在节假日放假的状况下,一大堆车会涌向高速公路,而这时候超过了高速公路所能容纳的流量,就会形成堵车,而网络的状况也是如此;
拥塞控制和流量控制不同,拥塞控制是做用于网络,用于控制网络中传输的效率,而流量控制是做用于接收方;
常见的拥塞控制有:
(1)慢开始,拥塞避免(2)快重传,快恢复
拥塞控制的算法有哪些呢?
(1)慢开始算法
发送方维持一个叫作拥塞窗口cwnd(congestion window)的状态变量,而这个窗口的大小是根据网络的拥塞程度来动态变化的;
拥塞窗口的大小取决于网络的拥塞程度,而且会根据拥塞程度来进行动态的变化,发送方的发送窗口等于拥塞窗口的大小,而还要考虑到接收方的流量控制窗口,那么可能会出现发送窗口小于拥塞窗口的状况;
而慢开始算法的实现原理就是,一开始向网络发送一个很小的数据包,用于探测网络的拥塞程度,然后再动态的加倍增长,直到达到网络所能接受的程度而不形成网络拥塞;
(2)拥塞避免算法
拥塞避免算法的原理就是,每次在发送数据包的时候,只增长很小的窗口容量,好比cwnd加1,而不是加倍的增长;
而上面的慢开始的算法若是每次都进行加倍的增长,就会很容易出现网络拥塞的状况,这样就没法计算的很精准的拥塞窗口容量;
那么咱们能够把慢开始算法和拥塞避免算法结合,若是慢开始算法加倍后,出现了网络拥塞,那么就把以前加倍的去掉,而后再采用拥塞避免算法,逐渐的递增,来试探网络的拥塞窗口大小;
拥塞的程度判断,通常都是经过有没有接收到确认报文,固然可能其余状况也会致使没有接受到确认报文,可是其余状况都是统一当成拥塞的状况处理了;
拥塞避免算法并非能够百分百的避免网络拥塞,而是会下降出现网络拥塞出现的几率;
(3)快重传算法
快重传算法的实现原理是,要求接收方在接收到一个失序的数据后,就马上发出重复确认报文,而不是要等到发送数据的时候捎带确认报文,这样可使发送方及早的知道有哪些报文没有接收到;
(4)快恢复算法
快恢复算法的实现原理的是,当进行了快重传算法时,发送方接收到了重复的确认报文,而这时候就认为网络尚未处于拥塞的状况,那么就将当前传输速度减半后,不走慢开始算法;
由于网络没有处于拥塞的状况,那么就执行拥塞避免算法,使cwnd缓慢增大,让网络传输速度快速恢复;
综上所诉,总结为以下流程图:
ssthresh:慢开始门限
cwnd:拥塞窗口
至于链接管理,那就是著名的三次握手和四次挥手机制,这个上面已经讲的很详细了👆,这里就不重复讲了;
UDP包头格式没有TCP那么复杂,由于UDP传输是不可靠的,就没有TCP那么多机制;
和TCP相比,UDP的包头就很简单了;
发送数据前不须要创建链接,那么发送结束以后也不须要释放链接,速度较快;
UDP传输尽最大的努力传输,可是并不保证可靠性传输,不须要维持链接的状态;
发送方的UDP将应用程序交付下来的报文,直接添加首部后,就交给IP层了;
对于应用程序交付下来的报文,不进行拆分,也不合并,应用程序传啥,那么UDP就传啥;
即便网络出现拥塞了,也不进行拥塞控制,对于一些须要实时性的应用程序,并不须要考虑网络的拥塞;
好比直播,或者游戏等软件,能够容许网络丢包,可是对于传输的实时性要求较高;
支持一对一,一对多,多对一,多对多等通讯;
首部只有8个字节,比TCP的20个字节的首部要短;
(1)网页或者APP的访问
访问网页和手机 APP 都是基于 HTTP 协议的。HTTP 协议是基于 TCP 的,创建链接都须要屡次交互,对于不稳定的网络环境来说,创建一次链接须要的时间会比较长,然而既然是移动中,TCP 可能还会断了重连,也是很耗时的。
而QUIC(全称 Quick UDP Internet Connections,快速 UDP 互联网链接)是Google基于UDP改进的一种通讯协议,旨在减小数据传输及建立连线时的延迟时间,双向控制带宽,以免网络拥塞;
(2)流媒体的协议
多用于直播方面的视频传输协议,好比上面提到的QUIC协议;
(3)实时游戏
对于游戏来讲,实时性的操做很是重要,而对于UDP来讲,实时性正是其特色;
(4)物联网
TCP对于物联网来讲,维护的成本较高,而UDP对于物联网的实时性来讲,是很是适合的; 好比CoAP协议,就是基于 UDP 协议的;
TCP报文段首部格式详解
传输控制协议
socket是什么?套接字是什么
TCP流量控制、拥塞控制
快速UDP网络链接
简单理解Socket
tcp udp流程图
两张动图-完全明白TCP的三次握手与四次挥手
TCP 为何三次握手而不是两次握手(正解版)
《计算机网络》
极客时间-趣谈网络协议-第10讲