本章主要将介绍传输层的UDP与TCP协议.
UDP: 无连接,不可靠传输,面向数据报,全双工
TCP: 有连接,可靠传输,面向字节流,全双工
图解UDP与TCP之间的传输过程区别:(面向字节流与数据报的区别)
教科书的图,正确的UDP协议结构是一串连续的字符串:
源端口号: 也就是发送方的地址.(包括ip地址与主机端口号)
目的端口号: 接收方的地址,(包括ip地址与主机端口号)
通过ip地址确认是哪个主机,通过主机端口号来确认是哪个应用程序.
UDP长度: 也就是数据报的长度(单位是字节)
由于"UDP的长度"给的大小是两个字节,就会导致出现一个UDP数据报的数据长度最大不能超过64kb,在互联网还没发展的时代,64kb确实够用了,但是随着互联网的发展,一份数据报只有64kb已经出现了弊端,
为什么不升级长度:
UDP已经内置到各个操作系统内核中了.
如果要对长度进行升级,就要升级全世界的所有主机,如果主机不升级,UDP则不会在这台主机工作.
升级操作必须要考虑到"兼容性"
对UDP局限性的改进方案:
1. 在应用层,将一份数据拆分成多个部分,再使用多个数据报来传输,接收这一段数据之后,再把数据进行组合.
这个方案带来的问题:
网络通信有一个特点:"后发先至".
在网络环境正常的情况下,发送的顺序和接收的顺序是一样的.
在网络出现抖动时,很容易让接收顺序和发送顺序不一致.
可以使用编号来控制主次顺序,但是整队就得需要耗费额外的开销,消耗时间.
2. 将UDP替换成TCP
TCP没有长度的限制.
校验和:
在网络传输中,数据不一定是完全准确的,网络信号的本质是光信号/电信号来表示0 1数据.传输过程中是非常容易受到干扰导致"比特反转"(使得数据出现错误.),尤其是无线的情况下.
A发送数据的时候,会根据传输的数据计算出一个校验和(相当于数据的摘要),发送的时候,数据连同校验和一起发送.
B接收数据的时候,会根据收到的数据重新计算校验和,对比自己计算出来的校验和和发来的校验和是否是一样的,如果一样,数据则没有问题,如果不同,数据则是在发送过程中出错,
校验和使用的算法是一种"CRC"算法(循环冗余算法)
对CPC算法的简单介绍:
对数据的每一个字节,都进行往上相加,如果超出了范围(2字节),溢出的部分则舍弃.当所以的字节相加完,得到的结果就会校验和.
如果校验和不同,可以认为数据在传输过程中一定出现了错误.
如果校验和相同,不能确认数据100%正确.(有可能正好的两个数据字节叠加的校验和出现相同的情况,虽是小概率事假,但依然可能发生).
TCP协议特点: 有连接,可靠传输,面向字节流,全双工
源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
32位序号/32位确认号: 数据编号与确认应答机制
4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节)所以TCP头部最大长度是15* 4=60
6位标志位:
o URG:紧急指针是否有效oACK:确认号是否有效
o PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
o RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段o SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
oFIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文
16位窗口大小: 滑动窗口
16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和个光包含TCP首部,也包含TCP数据部分。
16位紧急指针:标识哪部分数据是紧急数据;
TCP协议中,客户端每一次发出去的数据,服务器都会给予一次确认应答响应.
以收发短信为例:
但是网络是具有"后发先至"的特点.在多份数据传输的情况下,出现网络抖动就会发生响应错误的情况.
这样就会收到的响应出现结果错误的情况.
因此TCP的确认应答机制为每一条信息与响应都进行了编号:
这样就不会导致信息出现错乱的情况.
如何进行编号:
TCP是面向字节流的,在进行编号的时候,不是按照"条"的方式来编号的.而是按照字节来编号.
在图中,A主机发送了1000个字节的数据,这一个TCP数据报,长度为1000,序号则为1.所以确认应答的序号则为1001
确认应答机制保证了TCP协议的传输可靠性,使得客户端与服务器之间能知道消息是否发送出去了.
如何区分当前报文是普通报文还是一个应答报文呢?
TCP协议中,有几个标志位:
其中ACK就是用来表示当前报文是否是一个应答报文的:
当ACK为0时,则表示当前报文是一个普通报文.
当ACK为1时,则表示当前报文是一个应答报文.
由于网络的环境是十分错综复杂的,当网络不稳定或者网络拥堵的时候,就会导致出现丢包的情况.
虽然已经有了确认应答机制,确认应答只能保证客户端与服务器做到连接的功能.TCP协议在面对网络不稳定或者丢包情况下,引入了超时重传机制.确保TCP协议的可靠性得到保证.
丢包情况下:无论是普通报文还是应答报文,都是可能无差别地丢包的.
如果是业务数据丢了:发送方会在等待一定的时间之后,没有收到ACK就会重传数据.
如果是应答报文丢了:即业务数据已经到达了服务器,但是服务器反馈给客户端的ACK丢包了,发送方在等待了一定的时间没有收到ACK,就会触发超时重传,在这个情况下,服务器就会收到两份一样的数据,这时,服务器会根据数据的序号来进行数据去重(服务器发现收到一份之前的数据就会自动丢弃,确保应用层读到的数据是不重复的)
如果丢包的数据只丢了一半(或者传输过程中发生了数据变动)呢?
客户端在发送数据时会计算出这个数据的校验和,数据接收方在接受数据时也会计算出该数据的校验和.
数据接收方会按照规则计算出该数据的校验和,并且对客户端计算出的校验和进行比较,只要校验和出现了差别(数据发生了变化),那就会自动触发丢包!!进一步继续超时重传.
超时重传的连续丢包情况
对于重传数据,并不是说重传的数据一定就会到达,也可能出现丢包的情况.
丢包操作,有一个超时时间(具体的大小是可以在操作系统内核中配置的)
连续丢包下:
第一次传输丢包超时时间为T1,第二次传输丢包超时时间为T2…
T2>T1(随着时间的推移,时间会越来越大)
其实连续两次都没发出去,说明单次丢包的概率已经十分地大了,一般而言,连续重传的丢包的次数很多,这就意味着网络可能出现了严重的故障了.
对于连续重传多次还是失败的情况下,TCP也只能放弃并尝试重新连接了.如果重新连接失败,那就彻底断开连接了.
上文中我们已经得知了TCP协议是有连接的,所以TCP需要建立连接与断开连接:
建立连接:“三次握手”
TCP确认连接的三次过程:
客户端向服务器发送尝试建立连接的同步报文段SYN(也就是TCP协议中SYN为1时,该报文为同步报文)
SYN:synchronize,同步的意思
服务器接受到客户端尝试建立连接的请求之后,就会发送一段ACK+SYN的报文(该报文ACK为1且SYN为1),客户端接受到报文也就得知了服务器已经与我进行了连接.
客户端接收到报文后向服务器发送确认报文ACK,服务器接收到ACK也就得知了客户端已经与我建立了连接.
以打游戏交流为例:
TCP三次握手的意义:
“投石问路”,在正式通信之前,确认通信链路是否通顺,验证通信双方的发送能力与接收能力是否正常.
三次握手的过程还会让通信双方协商一些重要的参数.(例如MSS)
断开连接:“四次挥手”
TCP断开连接的四次过程:
客户端向服务器发送FIN为1的断开连接报文.
服务器立马向B发送ACK报文(确认服务器已经收到)
用户调用socket.close(),服务器会向客户端发送FIN为1的断开连接报文
客户端收到报文,向服务器发送ACK报文.
值得注意的是:为什么TCP三次握手中,中间两次进行了合并,而四次握手却不一定能合并.
因为服务器B返回ACK是操作系统内核的行为,只要操作系统内核收到了FIN,就会立即返回ACK,而服务器B发送FIN是用户态的行为,只有用户调用了socket.close()方法,才会触发FIN,
因此B发送FIN和ACK之间有时间间隔,所以不能进行合并.
也可能导致合并的情况:延时应答与捎带应答机制有可能导致四次挥手中间的ACK与FIN合并.W
详细版TCP协议建议连接断开连接过程:
对TCP客户端和服务器各个状态的解析:
LISTEN状态:服务器已经启动完毕,已经绑定端口号成功.(开机完毕)
ESTABLISHED:连接建立完毕
CLOSE_WAIT:被动接收了FIN的一方,就会进入了CLOSE_WAIT(已经接收了FIN并且发送了ACK),CLSED_WAIT的意思就是wait close,也就是在等待代码中调用close方法发送FIN.
TIME_WAIT:主动发起FIN的一方,会进入TIME_WAIT(收到FIN并且返回ACK)
为什么不直接进入CLOSED:因为如果最后返回的ACK 丢包了,而客户端已经CLOSED了,那就无法触发超时重传,导致连接无法释放,而进入TIME_WAIT状态,就会有时间来等待重传,一定时间后没触发重传就可认为连接已经断开.
TIME_WAIT等待的时间一般是2MSL(操作系统的一个配置参数,MSL表示两个主机之间,数据从一边传输到另一边所花费的最大时间(30s/60s/120s))
由于TCP保证可靠性的确认应答机制和超时重传机制导致了TCP的效率较慢,因此引入了滑动窗口来提高.
通过滑动窗口,我们不必在等待一条消息的响应再发送下一条数据,我们可以发送一批数据,等待一批ACK的方式来提高传输速率,也就是说如图,我们在发送前4000个字节的数据时,是一并发送的,不需要等待ACK.
收到ACK后,我们会继续滑动对应大小的窗口.
同时操作系统会为了维护这个滑动窗口,开辟了一个"发送缓冲区"来记录那些数据还没有应答,只有应答了的数据才会被发送缓冲区删除.
这个窗口越大,网络的吞吐率就越高.
对于滑动窗口的丢包问题:
情况一: 数据报抵达,但是ACK丢失.
丢包作为小概率事件,由于ACK是成批发送,所以只要这批数据的后一个ACK能够送达,则认为前面的数据已经传输完毕了.(后一个ACK会涵盖前一个ACK)
如果是最后一个ACK丢失了,则会重传这个数据(滑动窗口最差的情况下,就退化为普通的超时重传)
情况二:数据报丢失.
数据报直接丢失,主机A并不会停止发送数据,但是每一条数据返回的都是丢失数据的ACK,主机A收到3次同样的ACK后,就会触发重传,重传成功后,由于滑动窗口并没有停下,因此下一条ACK就会返回该过程中传送了的最后一条数据的ACK.
此处遵循的规则就是:哪一条数据丢失了,那就重新传输哪一条,对于已经传输到的数据,则不会重复传输.
由于滑动窗口的大小越大,发送速率就越快.所以也容易导致出现服务器的接收能力不足,而客户端的发送速率过快,最好的情况便是客户端的发送速率与服务器的接收速率相当.
流量控制的目的的就是限制滑动窗口的大小.
流量窗口就是通过接收缓冲区的剩余空间大小决定下一次发送数据的滑动窗口大小.在TCP报文中,有一个"16位窗口大小",服务器在ACK响应中会在报文中带上这个信息,客户端可以通过这个信息来调整滑动窗口的大小.
具体流程:
在一次的数据传输过程中,中间可能经历了多个中间设备的传输,我们其实是无法准确衡量中间节点的传输速率的,因为我们无法得知:
中间的设备有几个
中间设备的各个参数
在两次不同的数据传输过程中,经历的中间设备是否一样
因此中间设备的传输速率是难以得到准备数值的.
拥塞控制采取的方式其实是一种"探险家"的原理,每一次都探测出一个最合适的窗口大小.
简单流程:
刚开始按照小的窗口来发送数据.
如果不出现丢包情况,则认为网络比较畅通,并且逐渐放大窗口的大小.(刚开始以指数级别,达到阈值后,以线性级别提高)
放大到一定的程度后,传输速率够快以后.就会出现网络拥堵,进一步产生丢包,当出现了丢包之后,就会减小发送的窗口.
在这个过程中反复横跳.以达到一个动态的平衡.
延时应答其实也是为了提高TCP传输的效率机制,目的是限制流量控制对TCP滑动窗口的限制.
在流量控制中,服务器会通过ACK来告知发送方接受缓冲区的大小.来进一步控制滑动窗口的大小应该为多少合适.
通过延时回复ACK,使得应用程序在这段时间不断地消费接收缓冲区,进而提高接收缓冲区的大小,进而提高滑动窗口的大小.
在延时应答机制下,ACK不一定是要与发送的数据报一一对应的,例如(2001则会包含了1001)
基于延时应答的策略下提高传输效率.
延时应答会延缓ACK发送的时间,而在某一瞬间可能服务器响应了一段数据给客户端,这个时候就可能ACK与响应数据合并为一条报文来发送给客户端.
一般而言,ACK是由内核直接发送的,而响应数据是应用程序发送的,如果这两个数据并不是一个时机发送的,就不能合并,必须在时间上达到重合才能合并.
在延时应答与捎带应答机制下,TCP的四次挥手中间服务器发送的ACK与FIN是有可能一起发送的,所以四次挥手也可能成为三次.
什么是粘包问题:
TCP面向字节流发送数据,这个时候接收缓冲区就无法区分数据是哪个报文,导致出现前一个包包含后一个包的情况出现.(即区分不了数据来自几个应用层数据报,也区分不了数据是哪一个应用层数据报)
我们已经在上文已经了解到TCP协议是面向字节流的,并且每一次传输的数据都会进入接收缓冲区来给服务器进行读取数据.就会出现两种情况:
如果一个TCP连接中,里面只有一个应用层数据包,就不会出现粘包问题了.
如果一个TCP连接中,传输了多个应用层数据报,这个时候就容易区分不清,从哪到哪是一个完整的应用层数据报了.
粘包问题的解决方案:
数据之间使用分隔符
每一个数据都约定长度
分为三种情况:
主机正常关机
主机关机会杀死所有的用户进程,进而释放PCB,进一步释放文件描述符表上对应的文件资源.(即调用close()).
触发FIN,开启四次挥手流程
程序奔溃
程序奔溃也会释放PCB,进而释放文件描述符表.
虽然程序奔溃导致进程没了,但还是会正常的四次挥手(TCP连接也是内核负责的,内核会继续完成后续的挥手过程)
主机掉电或者网线断开
主机掉电或者网络断开的情况下,就来不及进行四次挥手了.
两种情况:
如果是接收方掉电,在客户端尝试发送数据发现没有ACK,就会尝试重传,连续重传多次后依然没有收到ACK,就会尝试重新建立连接,这个时候失败过后,客户端就会认为网络出现了严重的事故,则直接放弃.
如果是发送方断电,服务器就收不到数据了,这个时候服务器是判断不了是发送方没发数据还是发送方挂了.
这个时候为了判断对方是否存活,则周期性会发送一个"心跳包"来判断对方是否存活
即接收方给对面发一个特殊的报文(ping),对方返回一个特殊的报文(pong),如果对方没有恢复,则认为对方已经挂了.
如果要求可靠性,则优先使用TCP
如果发送的数据较大.优先使用TCP
如果对性能要求高,但对可靠性要求不大,则建议使用UDP
如果需要进行广播,则优先使用UDP
传输层中并不只有TCP和UDP协议,如果说TCP是可靠性很强的协议,那UDP就是效率很高的协议,在传输层中有许多保证了一定的可靠性也保证一定效率的协议,如KCP等等.