1)咱们主机机房的内部,机房内部的主机可以进行相互通信,机房内部的网络环境比较简单,带宽也是比较高的,丢包的概率就比较小,并且像咱们机房内部的主机的通信,也是对效率要求比较高;2)在应用层的基于UDP复刻TCP的机制;
3.TCP和UDP有什么区别?
1)TCP有连接,UDP没有连接
2)TCP可靠,UDP不可靠
3)TCP面向字节流,UDP面向数据包
3.1)UDP是面向报文的,发送方的UDP对应用层交下来的报文,不合并,不拆分,只是在其上面加上首部后就交给了下面的网络层,也就是说无论应用层交给UDP多长的报文,它统统发送,一次发送一个,而对接收方,接到后直接去除首部,交给上面的应用层就完成任务了,因此,它需要应用层控制报文的大小;
3.1)TCP是面向字节流的,不论应用层发送的报文长度如何,到了传输层后,TCP总是把收到的报文看作一串字节流,并且把每一个字节都进行编号,TCP会根据当前网络的拥塞程度和对方接收缓存的大小,决定现在应当发送多长的报文段,TCP关心的是:必须保证每一个字节都正确无误的传输给对方,并不关心传输了多少个报文段,和每个报文段有多少个字节。这就表明了TCP是面向字节流的
4)UDP 不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能;一份数据可以同时发送给多台主机,UDP本身是支持广播的,但是TCP本身并不会支持,所以只能通过多个连接,通过轮询的方式来给每台主机来发送数据
5)TCP对于数据传输没有长度限制,UDP对于数据传输有限制,只能是64K;
6)特别注重速度,就是用UDP,因为等待ACK要花费更多的时间,拥塞控制与流量控制都是在限制TCP的传输效率,UDP不用等ACK,所以效率会更高
注意应用层描述的主体是应用程序之间,但是传输层描述的是两台主机之间,一个主机可以包含多个应用程序
数据链路层是负责设备之间的传送和识别的,数据在传输的时候经过多个设备进行数据传输,而数据链路层就是负责相邻数据传输和识别设备中的
物理层是将数据转化成信号,再将信号转化成数据的
TCP保证可靠性:
1)校验和是保存在TCP首部的一个数据,TCP的发送端和接收端会采用相同的算法,根据发送方的数据计算出一个16位的校验和,并且校验和会和数据一起发送给接收端,TCP/IP/UDP的校验和都是相同的
2)TCP采用的是动态时间的超时重传机制,如果第一次发送的消息丢了,那么发送端会在1000ms内再发送一条消息,那么它会在更长时间内传输一条消息,重复多次之后,发现消息还没有正常的发送,那么TCP会认为对方主机存在异常,会强制断开连接......
3)TCP之所以要进行三次握手是因为,TCP双方都是全双工的,所谓全双工是指,TCP任何一端既是发送方,又是接收方
面试题三:像王者荣耀,吃鸡使用的是什么协议?
都不一定,既要保证TCP的一些特性,又要保证效率,其实都不一定,有的协议可能会更好的兼顾到可靠性和效率,付出的代价可能是浪费更多的机器资源;传输层协议并不仅仅限于TCP/UDP协议,知乎上的一位大神,自己开发了KCP协议,效率变得更高了,这样就更适用于即时性更高的游戏;例如说超时重传不以500ms为单位,而是以更小的单位进行重传;
1)我们数据传输的效率,不仅仅取决于接收方(滑动窗口的大小)和接收方(接收方缓冲区处理数据的能力),还和传输路径以及中间链路连接有关;
6)拥塞控制
1)流量控制是根据接收方的剩余缓冲区大小来进行确定滑动窗口的大小,但是拥塞控制是把中间的链路看成了一个整体,看看中间数据链路的拥堵情况,根据他们的传输数据快慢以及效率来进行控制窗口大小;
2)虽然TCP有了滑动窗口这个大杀器,可以相当可靠的发送大量的数据,但是如果在刚开始的阶段就发送大量的数据可能会引发问题,网络上有很多的计算机,可能当时的网络状态很拥堵,若不清楚网络状态,贸然发送大量的数据,很可能会导致网络系统瘫痪
3)本质上是在另一个角度上来控制发送方的窗口大小,他是站在一个宏观角度来看待问题,他是把中间的链路都看成了一个整体,只看结果,是一个不断尝试的过程;因为我们的A和B的中间节点,有多少个,咱们也不知道,就很难对这些设备一一进行衡量
4)先用一个较小的窗口来传输数据,看看是否丢包,如果不丢包,说明网络通畅;如果丢包,说明网络拥堵;当我网络通畅的时候,就加大传输速率;如果当前网络不丢包的情况下,就立即降低发送速率,通过这样的方式,就可以直接试出来当前最合适的窗口大小;
5)当我们A开始的时候会以一个较小的窗口来发送数据,如果说数据很流畅的就到达了,我们就逐渐增大窗口大小,如果加大到一定程度之后,出现了丢包,就意味着通信链路出现拥堵了,这个时候我们在减小窗口;
6)我们通过反复的增大和减小过程,逐渐就摸到了一个合适的范围,拥塞窗口就在这个范围内不断地发生变化,达到动态平衡;
流量控制针对的是接收方一个元素针对的,拥塞控制针对的是整体路径进行限制,它们会动态变化的;
真实的窗口大小取流量控制的窗口和拥塞控制的窗口他们的较小值;
![]()
![]()
1)我们最期望的理想滑动窗口效果,就是说在阈值和丢包窗口之间,在这个窗口之间就可以保证既不进行丢包,传输速率也是比较快的;
2)咱们拥堵的值是啥,就是完全取决于这一次传输是否丢包,如果进行丢包了,那么就说明视为拥堵,也不一定,反正会出现多次重传;
3)咱们就直接可以通过拥堵的值来进行确定更新下一次阈值的这一个过程,不是阈值影响到了拥堵的值,而是我们拥堵的值影响到了阈值,下一次拥堵的值是如何进行改变的?拥堵的值就是完全取决于咱们网络上面的一个传输数据的效率,这个值还是和网络整体环境是有关系的,所以拥堵的值也是在实时变化的,因为我们一开始的网络传输的时候拥堵值就是不确定的,所以下一次的阈值也是不确定的;
1)刚开始启动的时候,窗口比较小,此时通过指数增长,就可以在很少的轮次(发送的次数)上把窗口大小给顶上去,刚开始的窗口比较小,慢慢达到增长到即将丢包的最大窗口,我们实际情况既希望速度快,又希望不丢包,我们只能增加滑动窗口接近丢包的极限,又没真丢;
初始情况下我们给定的窗口太小了,可能合适的值是一个更大的值,通过上述过程,我们就可能接近一个更合适的值,指数增长到一定程度就会变成线性增长,滑动窗口已经快打到极限了,此时如果指数级增长,可能来个2倍,就直接超过了最大范围,线性增长也是增长,增长到一定程度,就会出现丢包的问题;
2)一旦发生了丢包的情况,此时发送方就立即会让窗口变,回归到刚才的初始情况下的窗口大小,继续重复刚才的指数增长+线性增长,一大块就减没了;
4)从这个位置开始,如果指数级增长直接翻倍成2倍就直接超过最大值,超出上一次位置,我们就用一个新的阈值来衡量我们线性增长的一个过程;
1)第二次滑动窗口从0开始增长的阈值就是第一次滑动窗口达到的最大值的一半;
2)这个阈值就决定了,咱们窗口大小啥时候从指数增长为线性,这个阈值也不是永远固定不变的,每一次出现丢包,阈值就变更新为当前出现丢包的窗口大小的一半,我们就想要通过阈值来进行描述一下线性增长的过程;
3)我们通过拥塞窗口来进行控制滑动窗口的大小;
4)如果达到阈值,就会从原来的指数增长变成线性增长,增长速度变慢,因为阈值已经快接近丢包的范围了,传输轮次是传输的次数,当传输伦次达到13次的时候 ,就会出现网络拥堵,然后窗口的大小又会变成最小值,阈值会发生变化;
5)正式的窗口取流量控制窗口和拥塞控制窗口的较小值,我们是希望滑动窗口的大小是出现在阈值和网络拥塞窗口(丢包窗口)之间,这样就做到了尽量少丢包,况且传输效率还特别高
7)这就类比于两个人谈恋爱,在一开始的时候两人的感情呈现指数级增长,之后呈现线性,稳步上升,两个在一起时间长了,一吵完架,感情迅速降到级点,对彼此很失望;
8)一段时间之后,两个人都彼此发现自己有不对的情况,感情又迅速升温,最终还是会线性增长,但是这一次增涨到极点的过程还是比较短的,至少比第一次短;
爱情不是风花雪夜,而是柴米油盐;
7)延时应答:琢磨窗口大小,相当于是流量控制窗口的一个延申(16位窗口变大)
7.1)咱们的流量控制是踩了一下刹车,想让发送方,发送的不要太快,延时应答,就是在这个基础上面,能够让窗口变得更大一些,想让咱们的发送方多发送一些数据;
让窗口大小保证在可靠的基础上,效率再高一些,窗口争取再大一些,流量控制就是说,窗口大小是接收缓冲区的剩余空间大小,会随着时间的推移接收方的应用程序会不断地进行消耗缓冲区里面接收的数据;窗口越大,网络吞吐量就越大,传输效率就会越高,我们的目标是在保证网络环境不拥堵的情况下,尽量提高传输效率;
7.2)在A发数据的时候,本应会返回一个ACK,但是这时缓冲区的大小可能会发生变化,可能会减少很多,这时B就对数据的需求量更大了,所以我们可以让ACK等一会再发出去,在B向A发ACK的这个过程中,B可能会消费的更多,缓冲区的剩余就更多,这时下次A再发的数据就可能会让缓冲区填不满,所以说咱们就等一会再来发送ACK,延时应答让最后传输的ACK中的16为窗口大小就会变得更大;
2)咱们再进行举一个入水和出水的例子来进行理解一下:
1)我们在进行注水的同时也在出水,我们每一次在进行注入一波水的时候,我们就会进行询问水池中的剩余空间有多少;
2)此时我们采取的策略就是不立即进行回答,而是稍微晚一会再进行回答,我们稍微迟一点进行回答,就是为了在迟一点的这个延时时间里面,就会排出更多的水
3)如果立即进行回答,那么我们可能会回答一个剩余20吨水,但是我们稍微等一会回答,也就是说延时一会回答,就可以说剩余5吨,我们在借助延时的这个时间里面,又出了15吨水,所以说我们在下一次进水的时候就会进更多的水;
4)所以说我们的这个操作就实在有限的情况下,又尽可能地提升了一点运行传输速度,既想保证可靠性,也想保证效率,发送给多的数据;
数据链路层是负责相邻设备之间的数据帧的传送和识别的,因为数据在传输的时候需要多个设备进行数据传输,数据链路层就是负责相邻设备之间的数据传输和识别的
咱们如何来进行延时应答呢?
1)每隔N个包就来进行应答一次
2)超过最大延时时间就进行应答一次
8)捎带应答:捎带应答本身是一个概率性的机制,当前ACK的延长时间正好要比接下来发业务数据的时间要长一些,取决于代码具体的实现,内核中只要收到数据就会立即返回ACK,这是应用程序进行的响应,捎带应答本身就是延时应答的延申;
咱们用浏览器上网打开网页用的就是一问一答的模型,一个请求就是一个响应
1)内核返回的ACK和应用程序,用户代码返回的响应,是有一定的时间间隔的因为这两个东西是不同时机进行触发的,两种数据包本来进行返回的时间间隔是不同的,所以说在本质上就不应该进行合并;
2)但是有了延时应答的存在,导致我们的ACK是不会立即返回的,而是要稍微等一会返回,让我们的接收方的应用程序多消耗一下接收缓冲区里面的数据,才可以进行返回的16位窗口大小变得更大些;
3)ACK正好在这里等一会的过程中,服务器要返回业务上面的请求了,正好可以把这个ACK和response合成一个包,因为在网络通信过程中,对于数据涉及到大量的封装和分用,收到之后还要进行解析,时间成本也高,效率也低;
3)之前我们说过,四次挥手是有可能变成三次挥手的,但是具体的概率是不知道的
捎带应答是建立在延时应答的基础上的 ,他是有可能把中间的ACK和FIN合并到一起的,例如在服务器收到请求并返回响应,这个过程中消耗50ms,但是延时应答ACK最多等待20ms,这个时候就无法触发捎带应答了;
4)所以说它本质上是一个概率上的机制,但是此时假设延时应答ack最多是等60ms,第50ms的时候,此时处理完数据,代码执行逻辑完成响应,这个时候ACK就可以和FIN一起过去了,也就同时触发了延时应答和捎带应答;
![]()
![]()
这是延时应答的一种情况
假设左边是A,右边是B
1)正常来说A再给B发送业务数据的时候,B的操作系统内核会立即返回一个ACK,但是由于延时应答的问题,咱们的ACK要等一会发,等一会是为了让B消耗了更多的业务数据,让B的接收缓冲区变得更大,让返回的ACK里面的16位窗口大小变得更大些;
2)咱们的业务数据,用户操作代码执行逻辑实现的,但是在等待过程中,业务数据处理完成了,那么这个ACK就会和响应报文一起发送给对方,这就是演示应答的基础上发生了捎带应答;
9)面向字节流,可能会出现粘包问题,粘包问题,主要指的是粘应用层的数据报,咱们的TCP数据报中的载荷里面放的就是应用层数据;
咱们不仅仅是TCP存在粘包问题,而是说所有的面向字节流的机制都是存在着粘包问题的,比如说读文件;
1)TCP粘包主要是粘的是应用层的数据包,在咱们的TCP缓冲区里面,若干个应用层数据包粘在一起了,分不出谁是谁了
2)本质上就是说,指数据在进行传输的时候,在一条消息中读到了另一条消息的部分数据,这种现象就叫做粘包,咱们的TCP是面向字节流的传输协议,流是没有明确的开始和结尾边界的,所以会出现粘包问题;
1)接收方收到这些数据之后,进行分用,先把TCP数据进行解析,解析过后,就会把这些数据部分放到接收缓冲区里面;
2)应用程序通过read方法从接收缓冲区中读数据的时候,就不知道从哪里到哪里是一个完整的应用层数据包了;应用层取的就是一个个完整的字节,应用程序此时只能看到接收缓冲区中的一个一个的字节;无法区分当前应用层有多少个应用层数据包,以及从哪里到哪里是一个完整的应用层数据报;他不是TCP特有的问题,而是面向字节流共有的问题;
3)TCP的报头没有长度的,当前我们的接收缓冲区,可以视为是分用后的数据(当前已经把TCP报头给去掉了,缓冲区里面是没有TCP报头的),或者SocketAPI拿不到,从应用程序角度来看就是一个一个的字节
那么如何解决这个粘包问题呢?应该通过再设定一个合理应用层协议来进行解决
1)不知道两个应用层数据之间的界限
2)我们解决方法的关键是要在应用层协议这里,加入包之间的界限
3)粘包问题虽然是在TCP这里面被提及,但是他从本质上来说还是一个应用层的问题,咱们的粘包问题本身对于TCP协议没有任何影响,但是这个粘包问题导致了说基于TCP的应用层协议会出现一些麻烦,我们应用程序就不知道一个应用层数据到另一个应用层数据之间的界限了,会对我们的应用层产生影响;
1)给应用层数据设置结束符;分隔符,这个时候我们只需要按照;来进行区分,我们就可以区分出当前从哪里到哪里是一个完整的应用层数据包了;
2)显示的进行设定数据包的长度
![]()
但是在UDP协议中,就不会涉及到这样的问题;
采用了链式结构来记录每一个到达的UDP,UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
我们的发送方和接收方固定发送数据的大小,当我们的字符长度不够的时候可以用空字符来继续拧补充,有了固定大小之后就知道每一条消息的信息边界了,这样就没有粘包问题了
10 TCP中的一些异常问题
1)进程终止:在我们的任务资源管理器里面,咱们的程序通过TCP建立的连接,QQ的客户端和服务器建立好连接了,在我们的进程毫无防备的情况下,咱们突然偷袭一个进程,突然关闭它,还有可能是一种正常情况,不管进程中是怎么终止的,抛出异常终止;
1.1)咱们的TCP连接,是通过Socket文件来进行建立连接的,Socket本质上来说就是进程打开的一个文件,文件其实就是存在于进程中的PCB的文件描述符表,每打开一个文件,就在文件描述符表里面增加一个表项(包括Socket),我们每一次关闭一个文件,就在PCB的文件描述符表里面减少一个表项;
1.2)我们如果直接杀死进程,进程没了,PCB也就没了,进程里面的文件描述符表也就没了,此处的文件就相当于是自动关闭了,因此这个过程其实和手动调用socket.close()的效果是一样的,都会触发四次挥手,这样我们来进行偷袭进程,和正常情况下进行四次挥手是没有任何区别的;
1.3)本质上都会对应释放PCB,也会释放对应的文件描述符,一样会触发四次挥手,进程终止并不意味着连接就终止,进程终止本质上是调用了socket.close()而已,和正常流程一样
2)机器重启:这里面的机器重启或者关机,是按照操作系统的正常流程来进行关机,正常流程的关机,本质上是让操作系统杀死所有的进程,然后再进行关机,和第一个步骤一样
机器重启的时候,其实也是在先杀进程,其实本质上就是关闭Socket,仍然是先触发四次挥手;
3)机器掉电/网线断开:这是属于一种突发情况,机器来不及进行任何动作的,不知道要断开连接,直接拔电源(偷袭成功),就比如说咱们的台式机直接拔电源,咱们的操作系统不会有任何反应时间,更不会有任何处理措施
1)掉电的是接收方,此时另外发送方这边还会继续传输数据,发送数据,显然发送方不会再收到ACK,于是发送方就会进行超时重传操作逻辑,重传几次之后,接收方也不会进行反应,发送方就会认为这个连接已经出现故障了,尝试就会进行重置连接(三次握手),重新连接,复位报文段,这个时候A就会主动放弃链接,然后发送方就主动释放与服务器曾经的连接 信息,但是此时连接还是连不上,这时发送方就会将连接对应的资源回收掉;
2)掉电的是发送方:此时另外一方正在尝试接收数据,但此时不会收到任何数据,此时的接收方如何知道发送方是挂了呢,还是说发送放休息一会暂时没有发呢?接收方好像只能干等;
3)这是接收方采取的策略,时不时的向发送方就会发送一个PING包(心跳包)(很小的报文),不会带有实际的数据,此时他也期待对方可以给它返回一个PING(pong包)(探测报文),只是想让他返回一个ACK,如果反复发送几次后对方也不应答,不再返回ACK,接收方就会认为发送方已经挂了,通过探测报文检查发送方是不是有回应,通过心跳包来进行检测发送方,就回收五元组;
在可靠性的基础上,保证效率;
TCP报头结构
正式因为咱们的TCP报头中拥有选项这个部分,这个选项部分可以有,也可以没有,所以咱们的4位首部长度来进行标识报头大小
1)32位确认序号: 用作对另一方发送来的TCP报文段的响应,其值是收到的TCP报文段的序号值加1,假设主机A和主机B进行TCP通信,那么A发送出的TCP报文段不仅携带自己的序号,而且包含对B发送来的TCP报文段的确认号。反之,B发送出的TCP报文段也同时携带自己的序号和对A发送来的报文段的确认号;
2)32位序号:一次TCP通信从TCP连接建立到断开过程中某一个传输方向上的字节流的每个字节的编号
3)4位首部长度:标识该TCP头部4个比特位,因为4位(1111)最大能标识15,(1表示四个单位)所以TCP头部最长是60字节。其实上就是划分了header和payload之间的分界线
选项上面的20个字节是一定有的,所以选项范围最多是0-40个字节
4)16位窗口大小(流量窗口):滑动窗口大小,用来描述TCP一次发多少组数据包;
5)16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。注意,这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。
6)16位紧急指针:TCP的紧急指针是发送端向接收端发送紧急数据的方法, 标记紧急数据在数据字段中的位置
7)选项:TCP头部的最后一个选项字段(options)是可变长的可选信息。
8)6位保留:先占个位置,暂时不用,但是保不齐以后会使用,未来的升级有点空间,就比如说咱们刚才讲的UDP,它的长度已经不够使用了,我们这个时候再去改UDP的数据长度范围,显然是不现实的,所以我们要使用TCP,设计一开始的设计协议的时候留点空间,比如说以后想要给TCP扩展一些空间,就可以使用这部份空间
9)6个标志位,是TCP包头的核心字段
URG标志,表示紧急指针(urgent pointer)是否有效,表示本报文中发送的数据是否包含紧急数据,后面的紧急指针字段URG=1才有效
ACK标志,表示确认号是否有效。我们称携带ACK标识的TCP报文段为确认报文段。
PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间(如果应用程序不将接收到的数据读走,它们就会一直停留在TCP接收缓冲区中)
RST标志,表示要求对方重新建立连接,我们称携带RST标志的TCP报文段为复位报文段,如果收到RST=1的报文,说明与主机的连接出现严重错误(如主机崩溃),必须释放连接,然后重新建立连接
SYN标志,表示请求建立一个连接,我们称携带SYN标志的TCP报文段为同步报文段
建立一个新连接。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段,当 SYN=1,ACK=1时,表示对方同意建立连接
SYN=1,说明这是一个请求建立连接或同意建立连接的报文
FIN标志,表示通知对方本端要关闭连接了。我们称携带FIN标志的TCP报文段为结束报文段, 断开一个连接,表示通知告知对方本段要关闭连接了,标记数据是否发送完毕,当FIN=1,表示告诉对方“我的数据已经发送完毕,你可以释放连接了”,代FIN标志的TCP报文段称为结束报文段