传输层
虽然除了应用层其他四次都是由操作系统内核实现,但是传输层协议的学习有助于当我们代码出现bug更加精准的找bug定位问题,修改问题!
我们知道传输层协议很多,但是我们主要来学习TCP和UDP这两个协议!
端口号
范围: 2个字节大小; 0-65535之间的整数
知名端口号:把0-1024这些端口号划分为一些具体的作用
很多网络服务器是非常常用的
未来方便管理,及将这些服务分配了一些专门的端口号
这里并不是强制要求,而是建议!
例如:
80 http服务器
443 https服务器
22 ssh
23 ftp
…
我们自己部署http端口也可以绑定其他的端口,就像java中的tomcat就没有将http绑定在80端口,而是绑定在了8080端口!
UDP
学习一个协议很多时候就是研究他的报文格式
x
这里的报文格式排版并不之前,只是因为美观所以这样,实际上的报文格式如下所示:
就是我们之前分装的数据格式!
源端口
源端口就是保存该主机程序的端口号
目的端口
目的端口就是接收方主机程序服务器的端口号
报文长度
这里的报文长度范围是2个字节大小,也是就0-65535,单位是字节!
也就是最大的报文长度64k一次传输的UDP传输层协议报文只能保存64k数据!
校验和
校验和就是检验接收方接收到的数据是否正确!
如何校验呢?
数据内容参与校验!
例如:
你要去买饮料,寝室的人都叫你带,而且买的饮料都不一样,这时,你如何知道有没有买正确呢?我们可以根据人数就可以确定数量是否正确,然后更加每瓶饮料的首个字,保存下来,然后对照买的饮料看看是否一样!
通过这样的方法,就可以校验是否正确!
而这里的内容校验也是类似,可以保证绝大多数时间我们接收的数据都是正确的!
TCP面试重点
我们看到TCP协议报文格式比较复杂!
源端口:保存请求方服务程序端口号
目的端口:保存响应方服务器端口号
32位序号:因为TCP是面向字节流传输,TCP对传输的数据按字节顺序进行编号.
例如:我要传输200个字节的数据,然后数据的确认序号为100,说明我是从字节序号为100的数据开始传输传输200长度字节!
32位确认序号:就是发送方期待响应方发送已经接收到的字节序号的下一个字节序号(告诉发送方下一次要发送的数据字节开始顺序)
例如:我们发送方从100字节序号发送200个字节数据,接收方接收后,就应该返回301的确认序号给发送方!
4位数据偏移(首部长度):也就是tcp的报文头大小这里的单位是4字节,也就是15*4字节= 60字节,协议报头可以有60字节大小
6位保留位:用于后期扩展,保留今后使用,但目前应都位0
6位标志位:
1.紧急URG:当URG位为1时,紧急指针字段有效,表示这个协议报中存在紧急数据需要优先处理
2.确认ACK:当ACK标志位为1时,确认号字段有效,TCP规定在连接建立之后ACK标志位要置为1
3.推送PSH:当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1
4.复位RST:当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
5.同步SYN:在连接建立时用来同步序号.当SYN=1,ACK=0时表示发送连接请求报文,当响应方同意建立连接后发送SYN=1,ACK=1给请求方!
6.终止FIN:用来释放连接,当FIN=1时,表示数据以全部发送请求释放连接!
16位窗口:通知接收方,我发送的文本,需要留有多大的空间接收
16位校验和:检验接收的首部和数据是否出错!
16位紧急指针:指出本协议报文紧急数据的字节数
选项:可变长度不像udp报头固定了8字节,tcp留有扩展的余地!
填充:用于存放完整应用层的数据载荷
我们通过之前对TCP的学习已经对TCP协议特点有所了解!
面向字节流
全双工
可靠传输
有连接
上面的这些特点在我们的套接字编程中TCP中都有所体现,除了可靠传输!
那么TCP是如何保证数据传输的可靠性呢?
TCP原理
TCP协议需要保证传输数据的安全可靠性,还有数据传输的传输效 率!
所以TCP的设计原则:在保证可靠传输的基础下尽可能提高传输效率!
下面是TCP设计的一些原理策略!
确认应答
我们刚刚学习32位序号和确认序号时,已经知道TCP由于面向字节流的特点,对传输的数据按照字节顺序进行编号!然后返回确认序号!
如果不对数据进行编号会怎样呢?
当小明发了两条消息给李华时!
这时小明收到"好呀好呀"不知道是啥意思了,因为他发送了两条消息,这是上号还是学习呀,就很迷!
如果我们有确认序号就帮我们解决了这个问题!
小明接收到李华发送的消息,有了确认序号2,表示李华已经接收到了第一条数据,并且给了回应!希望小明发送第二条数据!这就是序号和确认序号的作用!
TCP对每个数据都进行了编号!
每次接收方接收到了数据,就会发生一个ACK确认已经之前的数据已经全部接收到,可以继续发送数据了!
确认应答策略保证了数据传输的可靠性!
超时重传
超时重传相当于对确认应答进行了补充!
我们知道我们网络传输的环境十分复杂,有可能会存在数据丢失丢包的情况,我们此时如何保证数据传输的可靠性呢?
网络正常:
当网络正常没有掉包时,刘备通过张飞发送的"好啊好啊"ACK可以知道张飞已经接收到消息,我们的消息发送成功!
但我们没有接收到ACK时!
当我们没有收到接收方传来的ACK时,我们也不知道是我们的数据丢包了,还是对方的ACK丢包了,这时就无法知道接收方,是否已经接收到了数据!
此时发送方等待了会,没有接收到ACK时会将消息再次发送,这就是超时重传!
可能有人就会说了,那如果是ACK丢了的话,如果我们再次发送一条消息,那消息不就重复了呀?
我们的ACK是在操作系统内核发送的,也就是是说,如果接收方接收到了数据,首先到了接收方操作系统socket的接收缓存队列(阻塞队列)中,然后会记录下接收数据的数据编号,然后将ACK发送个发送方!
如果我们数据丢包了,接收方的接收数缓冲区就没有该数据,会将该数据编号保存,发送一个ACK确认序号!
ACK丢了,发送方再次发送数据到接收方,先到了操作系统内核,然后呢保存到接收缓冲区,发现已经有该数据了,就直接丢弃,就不会再给接收方这个数据了!
这就保证了应用程序调用socketapi拿到的数据不重复!
当网络只是发生抖动,那么超时重传就能保证传输继续!
但是当网络遭受重创,直接卡了网线!
那么无论如何重传也无济于事,那这是发送方就不会一直重传下去了!
并且每次重传的时间间隔会越来越大!
最后停止重传!
这里的每次重传的时间间隔并没有准确的值,也可以自行设置!
连接管理(面试重点)
我们知道TCP是有连接的,所以传输数据之前必须双方进行连接!
那么如何进行连接呢?
3次握手
3次握手就是客户端和服务器建立连接的交互过程
这里的握手只是一个比喻,一次握手就好比一次交互过程,这里进行了3次握手也就是客户端和服务器建立连接需要进行3次交互才能实现!
3次握手就类似于打电话刚接通的过程:
经过这3次交互过程我们就可以知道双方的通话环境是否ok!
第二次握手,可以知道客户端的发送能力和服务器的接收能力没有问题!当进行最后一次交互握手过程时,就可以确认通信双方的可以进行通信!
上图就是客户端和服务器进行3次握手的过程!
TCP的状态:
LISTEN状态,表示服务器可以进行连接,等待连接状态,就好比手机开机,网络良好,等待电话!
ESTABLISHED:表示通信双方已连接成功,就好比已经接通了电话,可以进行信息交流了!
3次握手有啥用?和可靠传输有什么关系?
3次握手相当于投石问路,检查一下通信双方的网络状态,是否满足可靠传输的基础,如果网络状态不好,就没有进行数据传输的必要了!经历了3次握手就可以验证双方的发送能力和接收能力,就好比打电话双方打通后需要验证双方的麦克风和喇叭!
让双方能够协商一些重要信息
为啥是3次握手,不能是4次?或者2次嘛?
上图就是4次握手,可以4次但是没有必要,我们可以将第二次ACK和第3次SYN放在一个数据包中传输,没必要分开进行分封装分用,就好比你去一家店铺买2件衣服,商家肯定打包成一个包裹!
这里分开传输效率低!不如和在一起!
2次握手显然不行,因为进行次握手后,客户端知道了自己的发送能力和接收能力良好,但是服务器不清楚自己的发送能力和接收能力是否良好,进行3次握手双方才能确认了双方通信状态,进行接下来的数据传输!
3次握手就好比2个人谈恋爱是一歌双向奔赴的过程,所以其中的交互缺一不可,不然就不能确立男女朋友关系!
4次挥手
4次挥手客户端和服务器断开连接的过程!我们在进行3次连接后,在操作系统内核中保存了一些连接的信息,就是我们之前学过的五元组!断开连接的过程就是将保存的信息删除!
也就是相当于情侣之间分手的过程!
当俩个人确认关系后,那么双方会在心里留一块位置给对方,起码知道对象叫啥名,不然谈啥恋爱对吧,然后4次挥手就把心里的位置给腾开,把之前双方的信息给销毁!
CLOSE_WAIT:四次挥手进行了2次后的状态,这个状态就是等待代码中调用socketapi中的close方法进行接下来的挥手,如果一个服务器中出现了大量的该状态,大概率出现了bug!
TIME_WAIT:谁发起的FIN谁就进入了该状态,表面上看A发送了ACK就没撒事了,但是有可能出现最后一个ACK丢包问题,所以TIME_WAIT状态会等待B的超时重传!要保证,B重传时,A还在连接!
一般A的等待时间是2*MSL,MSL是网络间,任意两点传输的最长时间!
我们此时又在想:3次挥手中间的ACK和SYN合在了一起,提高了传输效率,那么这里为啥不能是3次挥手呢?
3次握手中间两次可以合并,都是由操作系统内核完成!
而4次挥手有时候不能合并!
不能合并的原因B发送ACK和FIN的时机不同,ACK是由操作系统内核发送,而FIN是代码中的socket.close方法才会触发,有时两者的时间间隔太大无法合并一起!
滑动窗口
上述的机制都是针对TCP的可靠的传输设计的!
而在可靠传输的基础上还要保证传输效率!
滑动窗口机制就是提高TCP网络协议的传输效率!
我们可以看到,如果TCP每次传输一次数据就要等待一个ACK确认序号后再进行传输数据,显然这样每次等待ACK这就使得TCP的传输效率很慢,而滑动窗口就解决了这个问题!
我们可以一次发送多组数据,每组数据都会有一个ACK我们同一发送完一组数据后,再一次等待ACK保证传输的可靠性同时又保证了传输效率!
这里的一组数据的数据条数就是窗口大小,在窗口大小的范围内,我们无效等待ACK可以继续发送数据!
滑动,就是我们接收到ACK后,我们的窗口位置就可以往后滑动,又可以进行数据发送!
这张图就很好的诠释了滑动窗口!
这里一组数据有4条数据,就是窗口大小为4!
当我们已经接收到了ACK2001时,我们就知道1001-200数据已经发送成功,窗口向下滑动!
一起发这么多ACK,如果出现丢包问题,该如何解决呢?
ACK包丢失,数据到达!
这种情况,我们并不需要处理,就如上图,虽然ACK1001丢失了,但是后面的2001接收到了,我们知道,接收到了ACK2001说明前2000个字节数据传输成功,也就是ACK2001涵盖了ACK1001的功能!后面的4001丢失也是如此,我们已经接收到了5001,就保证了1-4000数据已经发送成功!
数据丢失
数据丢失,就需要进行处理,可以看到当1001-2000数据丢失后,虽然这一组数据的发送并没有结束,但是每次的ACK1001确认序号都是一样的,服务器向客户端索要1001-2000数据,在进行这一组数据的发送后,客户端会将数据重发!
我们知道主机B会将数据保存到接收缓冲区,在没有接收到1001-2000的数据,接收缓冲区会留有一块空间!
直到接收到1001-2000的数据将其补上!
这种机制叫做高数重发机制(也叫快重传)
流量控制
流量控制是在滑动窗口机制上对其补充!保证了传输的可靠性!
我们了解到在主机A和主机B进行通信时,主机A的数据先保存到主机B操作系统中的接收缓冲区当中!
我们又说过,接收缓冲区是一个阻塞队列,那么当主机A的传输数据过快时,那么数据就会在主机B中的接收缓冲区阻塞,如果主机A继续发送数据就会导致丢包问题!
因此TCP根据接收方的处理能力,来决定发送速度,这个机制就叫做流量控制!
TCP如何得知接收方的处理能力呢?
接收方通过将接收缓冲区剩余空间大小放在TCP报文中的16位窗口大小中,然后通过ACK报文告知发送方!
窗口大小越大说明,接收方的处理能力越强!
当接收缓冲区将要满时,发送方会减小发送速率,避免产生丢包!
如果接收缓冲区满了,那接收方就会回将0保存在16位窗口中!发送方收到ACK后就会停止数据传输,会过会时间定期发送一个窗口探测报文,接收方会将窗口大小返回给发送方!
我们知道16位窗口大小只能保存最大数据为65535字节,那么一个TCP数据报超过65535字节呢?
我们TCP报文中还有一个40字节的选项,这里面包含了一个窗口扩大因子M,实际的窗口大小可以是16位窗口大小+M左移因子!
拥塞控制
这也是滑动窗口的延伸,限制滑动窗口的发送速率保证可靠性!
拥塞控制是衡量发送方到接收方,这整个线路之间的拥堵状况(处理能力)这里和流量控制一样限制了发送方的传输速率!
我们知道两个设备进行通信,中间链路情况十分复杂,中间有很多设备,而每个设备的信息处理能力又不一样!如果中间一个设备处理的很慢,那其他设备处理的再快也无济于事!
这就导致,整个链路的传输速率也下降,这也促使发送方降低发速率!
发送方如何得知中间链路的阻塞状态呢?
这里无法直接得出,我们需要通过实验得出一个结论,找到这个最适合的发送能力,既要保证传输速率快,又要避免丢包!
发送方一开始取的拥塞窗口大小非常小,取纵轴的1个单位(1个单位的大小是1个字节,10个字节,具体要看操作系统代码如何实现)
一开始发送方通过指数增长的方式,增加传输速率,到达了一个合适的值后,就降低增长速率!
到达阈值后就是指数增长就会变成线性增长,使速率无限接近最大速率,直到产生丢包
丢包后,说明网络拥塞,自接将速率减少到最开始的初始值,避免丢包过多,又反复进行上述过程,当第二次指数增长时,会将阈值减小到当前出现丢包窗口的一半(一开始到达24就产生了丢包,那第二次就会将阈值减少到12),然后进行线性增长,直到找到一个最合适的值!
这个过程可能有些许复杂,可以联系俩个人谈恋爱!
一开始在一起时,热恋期好感度指数增长,到了后面,新鲜感没了,没有增长的这么快了,最后吵架闹分手,就好感度一重新开始,所谓小别胜新欢,然后又热恋,显然这次热恋期没第一次长,然后种种最后爱情的镜头还是财米油盐!
这里的拥塞窗口和刚刚的流量控制都是对发送方发送速率的约束,那么最后发送方发送的速率是取决于那个呢?
发送速率 = Min(拥塞窗口,流量控制);
延时应答
演示应答是流量控制的的延伸
流量控制是告诉发送方不要发送的太快!
延时应答是在这个基础上,尽可能然窗口更大一些!
刚刚的流量控制,当接收方的接收缓冲区满后,发送方就会停止发送数据,而是定期发送窗口探测报文,为了得知当前接收方接收缓冲区的大小,然后接收方就会发送返回一个窗口大小的报文!
而这里的延时应答就是将这个窗口大小的报文延时传送给发送方!
这样延时处理后,给接收方些许时间处理数据,使得窗口大小变大,然后下次发送方就可以提高发送速率!
捎带应答
这里又是延时应答的延伸!
因为延时应答的存在导致接收方接收到数据后ACK可能不是及时传输,那么当ACK延时后,当再次发送时,发现和另一条发送数据的发放时间一样,那么就可以将ACK和另一条数据一起封装分用,进行打包发送,提高发送效率!
就是将ACK和数据一起打包传输返回给主机A!
客户端和服务器之间的通信方式有多种!
一问一答:客户端一个请求返回一个响应(网页)
一问多答:下载文件
多问一答:上传文件
多问多答 :直播
这里的捎带应答策略针对的是一问一答方式
这里捎带应答ACK和数据报丢了咋整,和正常的丢包程序一样处理!
面向字节流
TCP是面向字节流读取数据的,也就导致了一个粘报问题!
所有的面向字节流都会存在粘包问题!
粘包问题:
当主机A发送3条数据给主机B时,3条数据就来到了主机B的接收缓冲区,然后应用程序通过socket中的read方法读取数据,面向字节流读取,这时应用程序也无法区分数据包,就出现了粘包问题!
如何解决呢?
显然我们的应用层数据拿到的数据时进行了分用的,也就是说并没有TCP协议中的字节确认序号,所以这里要通过对应用层协议的改进才能解决粘包问题!
在设计应用层协议时就要对数据通过标记位或者分隔符进行分割!
在应用层协议设计好就处理了粘包问题!
如果我们是基于某些库和框架来完成的网络通信,一般来说粘包问题已经被库和框架解决了,如果你是自己实现的库和框架使用TCP协议那就需要考虑粘包问题!