1.应用层
很多时候这一层的协议是程序员自定义的应用层协议(相当于一种约定,约定数据如何进行传输)。
eg:
实现登录的场景:
此时前端就需要与后端约定请求(假设约定使用ajax请求)中的一些参数(比如type,url,data等),响应为用户是否登录成功(返回true/false)。这个过程就是自定义协议,是可以根据实际情况灵活变通。
2.传输层
传输层主要是UDP和TCP协议,这里也只介绍这两种。
2.1 UDP
UDP是无连接,不可靠传输,面向数据报,全双工的传输层协议。格式如下:
UDP报头中包含四个信息:源端口号,目的端口号,UDP报文长度,UDP校验和,且每个部分都是两个字节(但是单位可能不同)。
(1)源/目的端口号:
两个字节大小:0~65535(0一般不用),1~1023:“知名端口号”(上古时期知名的,但是现在可能已经有很多不用了),剩下的随便进行自定义。
(2)UDP报文长度:
也是两个字节大小:0~65535(单位为字节),也就是64KB(其实64KB-8字节,因为报头占8字节,但由于与64KB差距甚微,所以就近似取的64KB)大小,这代表UDP所能承载的数据报的最大长度为64KB,超过就需要考虑使用TCP了。
(3)UDP校验和:
其本质就是字符串,体积比原始数据要小,由原始数据生成,是用来校验原始数据的正确性的。校验和相同,则原始数据相同;校验和不同,则原始数据一定不同。
1)为什么会有校验和?
网络环境错综复杂,数据在传输过程中就可能出现错误,因此就需要一个东西来校验数据的正确性,校验和由此而生。
2)校验和计算方法:
a.循环冗余算法(CRC):把当前要计算校验和的数据,每个字节都进行累加,把得到的结果保存在两个字节中,如果中间某个数据不一样的话,那么第二次计算校验和(接收方计算)的时候就会和第一次(发送方计算)的不一样。
b.md5算法(更多采用):
有三个特性:
定长:无论原始数据有多长,经过此算法计算出来的校验和都是定长的(32位/16位)
不可逆:原始数据经过md5算法可以很轻松得到校验和,但是想通过校验和解析原始数据是困难的(数据长度较少是可以实现的,一但比较长就会很困难了)
分散:即使大部分原始数据都是相同,只要有一个不一样,得到的校验和都会相差甚大。
2.2 TCP
TCP是有连接,可靠传输(要想进行TCP协议,得先保证当前网络环境是通畅的,如果出现重大网络故障,那么TCP协议也就进行不下去了,可靠传输也就无从谈起了),面向字节流,全双工的传输层协议,也是优先考虑使用的传输层协议。
2.2.1 TCP格式:
(1)源/目的端口号:
同UDP相同,不在介绍。
(2)4位首部长度:
4位:4bit(0~15,单位为4字节),但首部长度是不定长的,最短20字节(不包含选项),最长60字节(包含选项)。
(3)16位校验和:
同UDP相同,不在介绍。
(4)标志位:
ACK(确认应答报文段),SYN(同步报文段),FIN(结束报文段),RST(复位报文段),PSH,URG
为1代表目前TCP报文为对应字段报文,为0代表为普通报文段。
2.2.2 TCP十大机制
(1)确认应答:
发送方把数据发送给接收方过后,接收方返回一个应答报文(ACK),发送方接收这个ACK之后就知道自己是否数据发送成功;是TCP进行可靠传输的核心机制;使用是将TCP中标志位处ACK设为1就变成应答报文。
为什么要有ACK?
网络环境错综复杂,数据传输可能由于各种原因(传输距离过远...)导致数据“先发后至”/“后发先至”,此时就有可能造成一些歧义。
比如如下场景:
我现在约一个心仪的女生出来吃饭,但是她可能不喜欢我(我单方面追求)。
上述情况如果加入TCP(标志位:ACK为1)的话,能完成的操作:
确保应答报文和发出去的数据能够对上号,不出现歧义。
确保出现“先发后至”的情况,应用程序也能按照正确的顺序来执行。
还是上述例子:
这里的序号并不是按照“一条两条”的方式来编写的,而是按照字节的形式来编写的。
一般TCP进行通信时序列号一般不是从1开始的。
这个的最后一个字节号是根据数据长度大小推算出来的(开始序列号为1,数据长度为1000字节,那么最后序列号为1000),确认应答中下一个是1001/2001,即向A索要1001后的/2001后的数据。
(2)超时重传
此操作大幅度提升数据能过传送成功的概率。
上面所说的确认应答是一个比较理想的情况,但如果在网络传输过程中出现丢包咋办?
发送方势必就无法收到ACK了,使用超时重传机制,针对确认应答进行补充。
丢包的两种情况:(发送方是无法区分的)
a.数据包丢了
b.返回的ACK丢了
此时该种情况站在B的角度接收到了两条一样的数据,是否有bug?
假设考虑上述情况出现在转账的场景下,本只想要转500,结果现在转了1000,出现重大bug(与钱打交道需要注意)。但TCP帮助解决了这个问题,TCP中有一个“接收缓冲区”,这个缓冲区可以进行去重,如果当前数据缓冲区中有就会直接丢弃掉当前传来的重复的数据,并且该缓冲区还可以按照数据发送的顺序来让应用程序依次处理这个数据。
超时重传等待时间:
a.等待时间是可以进行配置的,不同系统,环境不一定一样,也可以通过修改一些内核的参数来引起时间的变化。
b.等待的时间是动态变化的,每经历一次超时重传,等待时间就是变长(变长的隐喻含义就是此时对应能够接收ACK的态度是悲观的,可能已经出现严重网络故障),但不是无限变长,超过一定限度,再次发送如果还是没有接收到ACK(认为在怎么重传也没有用了),此时就会断开连接了。
(3)连接管理:
包含建立连接(三次握手)和断开连接(四次挥手)。
建立连接(三次握手):
这里的握手是指给接收方发送一个不携带任何业务数据的数据包,引起接收方的注意(要向你发送数据要准备好),从而触发后续的操作。握手这个过程并不是TCP独有的,甚至不是网络通信独有了,计算机的很多操作和日常生活中的很多时候,都涉及到握手。
eg:给手机充电,普通通电/快速充电在手机上所显示的页面不一样,这个过程就涉及到握手。
三次握手如上图所示。
其中SYN表示同步报文段(是一个不携带任何业务数据的数据报,作用是告诉B,A准备要向你发送数据了),使用是需要将标志位SYN设为1。
A和B要想建立连接,A就会主动先向B发送一个SYN(握手),实际开发中,主动发起握手的一端就称为客户端,另一端就称为服务端。通过上述三次握手的操作,A与B就完成建立连接,互相保存了双方的信息。
这里的三次握手其实是四次握手,中间的SYN和ACK是在相同时机触发的,所以可以合并为一个数据包,所以就是三次握手。
三次握手的核心作用:
a.确保当前网络环境是通畅的。
b.确保发送方和接收方接收能力和发送能力都是正常的。
c.让通信双方在握手过程中,针对一些重要的参数进行协商(eg:TCP中的确认序列号从几开始,每次协都会协商出一个和上一次差异比较的值)
断开连接(四次挥手):
建立连接:客户端发起,而断开连接:客户端/服务器发起
下图是四次挥手的流程图:
FIN:结束报文段 (使用是需将标志位FIN设为1)。在上图中是A发起断开连接的,且中间的ACK和FIN不能合并成一个数据包,ACK是内核态触发的(B一收到FIN就会返回),而FIN是用户态触发,需要程序员手动调用close方法或者进程结束。
还需要注意:
A在发起断开连接请求后,并不会立马处于CLOSED状态,而是先处于TIME_WAIT状态,这样的处理是为了A能返回B一个ACK。如果A发起断开连接后就处于CLOSED状态那么B在发送FIN后就会接收不到A返回来的ACK,就会触发超时重传,重新把刚才的给传一遍,此时重传的FIN就没人来进行处理了,B永远接收不到ACK。让其处于TIME_WAIT状态让这个FIN(有可能是重传的,也可能是最初的)才有意义。
(4)滑动窗口
上述三个机制都是确保TCP可靠传输的,可靠传输必然会使传输速率降低,滑动窗口是用来提高TCP的传输速率的。触发条件:传输数据量比较大且频繁,就会进入滑动窗口模式,按照快速重传方式处理。
有滑动窗口的存在数据能够进行批量传输(有上限,批量传输的数据量称为窗口大小,其越大,传输速率越快),不会立即返回ack,直接发送下一个数据,直至窗口满了。
上述图描述一个滑动窗口的滑动过程,中间返回一个ack(下一个发送4001,说明这之前的数据已经接收并处理成功)
有滑动窗口之前:
发送一个数据包,接收到一个ACK。
有滑动窗口之后:
不会立即返回ACK,窗口占满后,需要等待,等待B返回ACK才能继续发送数据。
上述滑动窗口中,确认应答是可以正常工作的。
但是如果出现丢包怎么办?
有两种情况:
a.数据包丢了
上述过程中,1001~2000的数据包丢失了,但是并不会影响后续数据的发送,B没有接收到该段数据,就会持续发送一个ACK(下一个是1001),当A重复接收几次重复的应答报文以后就会重传刚才丢失的数据包。哪个丢了就重传哪一个,没丢的不需要重传,这个过程就是快速重传。
b.ACK丢了
此种丢包情况并不会影响,不需要额外进行重传操作。上述过程中ACK(下一个是2001)丢失了,但是A接收到ACK(下一个是3001),此ACK说明在序列号3001之前的数据都已经传输过去了。
(5)流量控制
背景:通过滑动窗口确实会提高传输速率,就窗口越大,速率越快,但是窗口是越大越好吗?答案显而易见,肯定不是这样,如果传输速率过快就会导致接收方处理不过来数据,然后丢包,发送方还得重传,这样速率感觉又下降了。
所以就引入流量控制,通过接收方来一定程度地约束发送方的发送速率,也就是发送方的发送速率不应该超过接收方的处理速率。
具体做法:
接收方每次接收到一个数据,就会把接收缓冲区的剩余空间大小通过ack返回给发送方,发送方通过这个信息,来调整下一轮的发送速率。
数据到达接收方,数据并不是立即就会被应用程序处理,而是先放在接收缓冲区中等待应用程序来进行处理数据,一旦数据被处理了,该数据就会从缓冲区中删除。接收方的消费能力就是通过缓冲区的剩余空间大小来衡量的,剩余空间越大,消费能力越强,处理能力越强;反之就越弱。(生产者-消费者模型的体现)
下图是一个流量控制的过程:
网络环境不太好的情况下,就可能导致窗口探测包丢失,所以这个包发送方时不时就会发送一个。
TCP格式中窗口大小为16位,也就是64KB但实际大小不是这么多,TCP中还有一个选项叫做窗口扩展因子,通过它,窗口可以变得更大。
(6)拥塞控制
拥塞控制是用来衡量/考虑中间节点的情况,在转发过程中,任意一个节点处理能力达到上限,都可能影响数据的可靠传输。
eg:A向B发送数据,中间会经历许多节点。
首先让A先以较小的速率传输数据,如果数据传输很顺利,没有丢包,再让数据传输速率慢慢增大,随着窗口的不断增大,达到一定程度,可能中间节点就会出现问题了,此时这个节点,就可能进行丢包,发现丢包,汉就需要把窗口给变小,此时如果还是发现丢包的话,就再缩小窗口大小,直至不丢包,在这个过程中发送方不断调整窗口的大小,逐渐达到动态平衡,就可以寻找到发送方的最大传输速率(是取流量控制和拥塞控制下窗口的较小值)
下图演示拥塞控制下发送方传输速率的变化:
刚开始慢开始,窗口慢慢增大,然后开始指数增长,达到一个窗口阈值(将要达到窗口的最大值,此时就不能够进行指数增长了),然后进行线性增长,直到丢包(最大窗口);一旦丢包就会从头开始传输,这种“一落千丈”的情景还是不太好,我们可以做一下改进:不让传输速率直接降为0,而是具有一定的初始速率,这样避免开始慢增长的过程,就能直接进行指数增长的过程。
(7)延时应答
本质也是为了提高传输速率。
接收方接收到数据之后不会立马返回ACK,而是会等待一段时间,在这段时间内,接收方的应用程序可能又会从缓冲区中读一定数量的数据,此时缓冲区的剩余大小会变多。
(8)稍带应答
其实就是在延时应答的基础上进一步提高传输速率。
网络通信中往往是“一问一答”的通信模型。
接收方接收到request后不会立即返回ACK,在这个等待过程中,接收方的应用程序把response已经计算好了,就会把response同刚才的ACK一起返回了(本来要返回两个数据包,现在合并成一个数据包)
(9)面向字节流
面向字节流的这个机制可能会出现粘包问题(包:应用层数据包):如果多个应用层数据包同时传输过去就可能出现此问题。
eg:
在接收缓冲区中,这三个应用层数据包中的数据是以字节的形式紧紧挨在一起的,接收方的应用程序读取的时候读取的字节数是不确定的,但最终的目的是为了得到完成的应用层数据包。
接收方不知道缓冲区中的数据从哪到哪是一个完整的应用层数据包,但是UDP就没有粘包问题,因为是面向数据包的,缓冲区中是一个一个的数据包,应用程序可以得知从哪到哪是一个完整的数据报。
如何解决粘包问题?
a.引入分隔符
eg:使用\n作为分隔符
接收方的应用程序每次就会一直读到\n为止。
b.引入长度
eg:
接收方应用程序读取的时候就先读取两个字节,得到数据的长度,然后再根据长度读取对应字节的个数。
(10)异常情况
a.进程崩溃
进程崩溃,异常终止了,文件描述表也就释放了,此时就会触发FIN,对方收到之后,就会自然返回FIN和ACK,这边再进行返回ACK(正常的四次挥手断开连接过程)。TCP的连接是可以独立于进程存在的。
b.主机关机(正常)
在进行关机的时候,就会先触发强制终止所有的进程的操作。此时就会触发FIN,对方返回FIN和ACK:
如果此时电脑还没有完全关机,对端返回的FIN和ACK到了,此时系统还是可以返回ACK的,就是正常的四次挥手断开连接的流程。
如果此时电脑已经关机了,那么此时系统不会返回ACK,对端多次进行重传操作,发现没有ACK返回,也就断开连接了。
c.掉电(非正常)
此时是一瞬间的事情,还来不及结束进程(还没有触发FIN),主机就直接停机了。
站在对端角度,对端不一样知发生了什么事情。
情况1:接收方掉电,那么发送方发送数据之后,就会一直等待ACK,然后会触发超时重传,触发TCP连接重置功能发起复位报文段(RST),如果RST发送过去还是没有接收到ACK此时就会断开连接了。
情况2:发送方掉电,对端还在等待数据的达到,但明显此时数据就不能发送过去,对端等了半天没有消息,此时无法区分是没有发信息,还是已经挂了。在TCP中提供了心跳包机制:如何识别某个机器是否挂了,就可以使用心跳的机制来判断。
也就是接收方会周期性发送一个不携带业务数据的数据报,并且期望发送方返回一个应答,但是如果发送方没有应答并且重复进行多次之后,还是没有接收到应答,此时就认为发送方已经挂了,就可以单方面地直接断开连接了。
d.网线断开
eg:
A给B发送数据一旦网线断开,此时A触发超时重传和TCP连接重置(RST),如果没有应答就会断开连接;此时B就会触发心跳包机制,周期性给A发送一个特殊的不携带业务数据的数据包,期望得到A的应答,如果重复多次后都没有得到应答,就会单方面断开连接了。
2.3 TCP和UDP的区别:
TCP:适用于绝大多数场景(除了一些对性能要求极高,数据正确性不太高,不太敏感的场景)
UDP:更适用于可靠性不敏感,性能敏感,数据正确性不太高的场景;还支持天然广播,TCP不支持。
eg:局域网内部主机之间的通信传输层协议就是使用的UDP,因为同一机房内部,路由器/交换器的负载程度也不是很高,所以出现丢包的概率很小。