目录
负责数据能够从发送端传输接收端
一、TCP协议
Transmission Control Prottocol 传输层控制协议
即要对数据的传输进行一个详细的控制
TCP协议段落格式
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去
- 4位TCP报头数据:表示该TCP头部有多少个32位bit(有多少个4字节);所TCP头部最大长度为15*4=60
- 6位标志位:URG:紧急指针是否有效;ACK:确认号是否有效;PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走;RST:对方要求重新建立连接(携带RST标识--复位报文段);SYN:请求建立连接(携带SYN标识--同步报文段);FIN:通知对方本端要关闭了(携带FIN标识--结束报文段)
- 16位校验和:发送端填充,CRC校验。接收端校验不通过则认为数据有问题(此处的校验和不仅包含TCP首部,也包含TCP数据部分)
- 16位紧急指针:标识哪部分数据是紧急数据
原理
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率
这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能地提高传输效率。
既要保证可靠性,又要尽可能地提高性能。
- 可靠性:校验和、序列号、确认应答、超时重发、连接管理、流量控制、拥塞控制
- 提高性能:滑动窗口、快速重发、延迟应答、捎带应答
- 其他:定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)
1、确认应答机制
确认应答,是实现可靠传输的最核心机制,是TCP最核心的机制,支持了TCP的可靠传输
面试题:TCP是如何保证可靠传输的?
以确认应答为核心,借助其他机制辅助最终完成可靠传输
TCP将每个字节的数据进行了编号,即序列号。
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发送
2、超时重传机制
(等待一定时间,再重新传输)
确认应答是一种理想的情况,但是若网络传输过程中出现了丢包咋办?
网络传输中造成丢包的原因:
o 网络拥塞(路由器会直接把其中的大部分数据包直接丢掉)
o 传输介质问题(线路损坏、信号干扰)
o 路由器故障
o 网络延迟
o 安全防火墙和过滤器(会误判或有意丢弃特定类型的数据包)
o 软件错误或配置问题
o 链路故障
o 数据包冲突(多个设备同时尝试发送数据到同一个目标)
发送方就势必无法收到ACK了,收不到ACK也可以分为2种情况:
这样使用使用超时重传机制,对确认应答机制进行补充。
但是超时重传机制会导致主机B收到很多重复数据,那么TCP协议就需要能够识别出那些包是重复的包,并丢弃掉。
TCP有一个“接收缓冲区”,就是一个内存空间,会保存当前已经收到的 数据,以及数据的序列号
接收方如果发现发送方此次发来的数据是已经在接收缓冲区存在的,接收方就会直接把这个后来的数据给丢弃掉,确保应用程序进行read时读到的只有一条数据。
这个接收缓冲区不仅可以去重,还可以进行重新排序,确保发送的顺序和应用程序读取的顺序一样
另外还要对超时时间进行合理的确定。
最理想的情况下,找到一个最小的时间,保证“确认应答能在这个时间内返回”。
这个时间的长短随着网络环境的不同是有差异的:设得太长会影响整体的重传效率;设得太短会频繁发送重复的包。TCP为了保证无论在任何情况下都能比较高性能的通信,因此会动态计算这个最大超时时间
Linux中(BSD Unix和 Windows)也是如此,超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。若重发一次仍然得不到应答则等待2*500ms后再进行重传;若仍然得不到应答则再等待4*500ms进行重传(以此类推,以指数形式递增)。累计到一定的重传次数,TCP认为网络或者主机 出现异常,则强制关闭连接。
3、连接管理机制
(如何建立连接断开连接—三次握手四次挥手)
TCP的初心是为了实现“可靠传输”,进行确认应答机制和超时重传机制的前提就是当前网络环境是基本可用的、通畅的,否则可靠传输无从谈起。
网络传输单位:段segment、包packet、报datagram、帧frame
在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接
三次握手
第一握,SYN(同步序列编号)
客户端向服务端发送一个同步报文段请求建立连接,客户端选择一个初始序列号
第二握,ACK-SYN(确认和同步)
服务器收到客户端的建立连接请求后,返回ACK(标志位设置为1表示收到请求),并选择自己的初始序列号
第三握,ACK(确认)
客户端收到服务器的SYN=ACK后,返回ACK(标志位设置为1表示收到请求),连接建立完成
三次握手作用:
- 建立连接
- 投石问路,确认当前网络是否通常
- 确认双方的接收和发送能力是否正常
- 通信双方协商关键参数(序列号,双方都设置了初始化序列号为数据传输做准备,通过使用序列号来确保数据包的唯一性和顺序性,避免了前朝的剑斩本朝的剑的情况)
四次挥手
前面在建立连接进行三次握手时我们将发起连接请求的一方作为客户端,但是在断开连接这里是客户端服务端两端都可发起断开连接请求的
- 客户端向服务端发送FIN数据包(发起断开连接请求),并进入FIN_WAIT状态
- 服务端收到FIN,向客户端发送ACK数据包(确认收到FIN报文),并进入CLOSE_WAIT状态
- 服务端处理完所有数据传输后,向服务端发送FIN数据包(完成数据传输并准备关闭连接)并进入LAST_ACK状态
- 客户端发送ACK数据包(确认关闭连接)
(1)不能合并为三次挥手的原因
TCP断开连接的四次挥手过程中,服务端ACK和FIN的触发机制是不同的:
- ACK报文主要用于确认接收到的FIN报文,ACK是内核响应的,服务端收到FIN就会立即返回ACK
- FIN报文则标志着服务端完成数据传输并准备关闭连接,是应用程序执行到close()触发的
那是否意味着,若服务端代码没写(没执行到)close(),第二个FIN就一直发不出去?
这也是有可能的。
- 正常的四次挥手:正常流程断开连接
- 不正常的挥手(没有挥完四次):异常的流程断开
(2)延时应答机制—实现合并
此机制拖延了服务端ACK的回应时间,一旦ACK滞后那就有机会和FIN合并。
(3)TIME_WAIT的作用
哪一方主动断开连接,哪一方就会进入TIME_WAIT,存在意义:防止最后一个ACK数据包丢失
在客户端没有TIME_WAIT状态下,若最后一个ACK丢失,服务端就会触发超时重传FIN,但此时客户端已彻底释放连接,B将永远无法收到ACK并释放连接。
(4)操作系统关闭连接
TCP断开连接的过程可能因为各种原因(如网络问题、进程异常终止等)而未能完全执行四次挥手。在这些情况下,操作系统可能会代为发起四次挥手,以确保连接的优雅关闭。
4、滑动窗口机制
前三个机制都是保证TCP的可靠性,TCP前提就是可靠性,要在可靠性的基础上,再提高传输效率
前面的确认应答策略,对每一个发送的数据段都要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样保证了TCP传输的可靠性,但是性能差,尤其是数据往返的时间较长的时候,因为多出了等待ACK的时间,这样单位时间内能传输的数据就少了
既然这样一发一收的方式性能差,那么我们这样一次发送多条数据,就可以大大的提高性能(尤其是将多个段的等待时间重叠在一起),减少了等待确认应答的时间
窗口大小:无需等待确认应答时间而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(4个段)
- 发送前四个段的时候,不需要等待任何ACK直接发送
- 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,以此类推(不是等待4个ACK都回来了才往下发,而是每回来一个就可以继续往下发了)
- 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉
- 窗口越大,则网络的吞吐量就越高
那么如果出现了丢包,如何进行重传?这里分两种情况讨论
情况一:数据包已经抵达,ACK被丢了
确认序号:当前序号之前的数据已经确认收到了,下一个你应该从确认序号这里继续发送
这种情况下不需要任何重传,部分ACK丢了不要紧,因为可以通过后续的ACK进行确认
比如要返回确认序号1001ACK包丢了,但是返回确认序号2001的ACK包成功,则表示确认序号2001之前的数据都已确认收到,这里就涵盖了1001的情况
情况二:数据包直接丢了
- 当1001~2000报文段丢失之后,接收端会一直返回确认序号1001的ACK,一直索要这个报文段
- 在每次索要的同时也在接收发送端后面发送的报文段,只是先存放在操作系统内核的接收缓冲区中
- 发送端主机连续三次收到了同样一个“1001”这样的应答,就会将对应的数据1001-2000重新发送
- 接收端收到了1001~2000报文段,再次返回的ACK就是7001了,传输继续正常进行
这种机制被称为“高速重发控制”(快重传)
5、流量控制
接收端处理数据的速度是有限的。如果发送端发的太快(窗口设置太大),导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应
流量控制机制:根据接收端的处理能力来决定发送端的发送速度
- 接收端将自己的接收缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK端通知发送端
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端
- 发送端接收到这个窗口大小后,就会减慢自己的发送速度
- 如果接收端缓冲区满了,就会将窗口设置为0,并通知给发送端
- 此时发送端不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端
在前面的TCP协议段部格式中就可以看到16位窗口大小
这个“窗口大小”字段就用于存放窗口大小信息,那么16位数字最大表示65535,那么TCP窗口大小就是65535字节吗?实际上TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段左移M位
6、拥塞机制
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引发问题的。
TCP引入了慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据
拥塞窗口:发送开始时定义拥塞窗口大小为1;每次收到一个ACK应答,拥塞窗口+1;每次发送数据包时,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口
像上面这样的拥塞窗口增长速度,是指数级别的。“慢启动”只是指初始时慢,但是增长速度非常快
为了不增长的那么快,因此不能使用拥塞窗口单纯的加倍,为此引入一个叫做慢启动的阈值:当拥塞窗口超过这个阈值时,不再按照指数方式增长,而是按照线性方式增长
- 当TCP启动时,慢启动阈值等于窗口最大值
- 在每次超时重发时,慢启动阈值会变成原来的一半,同时拥塞窗口置回1
少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络堵塞
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生堵塞,吞吐量会立刻下降
拥塞控制,归根结底是TCP想尽办法可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案
7、延迟应答
如果接收数据的主机立刻返回给ACK应答,这时候返回的窗口可能比较小,但其实延迟应答下使窗口再大些,接收端也能处理得过来
窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不堵塞的情况下尽量提高效率
但也不是所有的包都可以延迟应答的:
- 数量限制:每隔N个包就应答一次
- 时间限制:超过最大延迟时间就应答一次
具体的数量和超时时间,依操作系统的不同也有差异,一般N取2,超时时间取200ms
8、捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是“一发一收”的。
意味着客户端给服务器说了“How are you?",服务器也会给客户端回一个”Fine,thank you."
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine,thank you"一起会给客户端
9、面向字节流
10、缓冲区
由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:
- 写100个字节的数据时,可以调用一次write写100个字节,也可以调用100次write每次写一个字节
- 读100个字节的数据时,也完全不需要考虑写时是怎么写的,既可以一次read100个字节,也可以一次性一次read一个字节,重复一百次
11、大小限制
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区
调用write时,数据会先写入发送缓冲区
如果发送的字节数太长,会被拆分成多个TCP的数据包发出;如果发送的字节数太短就会先在缓冲区里等待,等到缓冲区长度差不多了或者其他合适的时机发出去
接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区
然后应用程序可以调用read从接收缓冲区拿数据
12、全双工
TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这么一个连接,既可以读数据,也可以写数据
粘包问题
粘包问题不是TCP独有的,而是面向字节流的机制都有类似的情况
此处的包指的是应用层的数据包,如果同时有多个应用层数据包传输过去,就容易出现粘包问题
在TCP的协议头中没有如同UDP一样的“报文长度”这样的字段,只是有一个序号这样的字段。站在传输层的角度,TCP是 一个一个报文过来的,按照序号排好序放在缓冲区中。站在应用层的角度,看到的是一串连续的字节数据
- 接收缓冲区中,这三个应用层数据包的数据就是以字节的形式紧挨在一起的
- 接收方的应用程序读取数据时,可以一次读一个字节,也可以一次读多个字节,最终的目的都是得到完整的应用层数据包。可是接收方B并不知道缓冲区的数据从哪里到哪里是一个完整的应用层数据包
相比之下,UDP这样面向数据报的通信方式就没有上述问题
在UDP的接收缓冲区中,相当于是一个个DatagramPacket对象,应用程序读时就能明确哪里到哪里是一个完整的数据
而避免粘包问题的核心思路就是:通过定义好应用层协议,明确应用层数据包之间的边界
1、引入分隔符
对于变长的包,还可以在包和包之间使用使用明确的分隔符(应用层协议是程序员自己来定的,只要保证分隔符不和正文冲突即可),比如\n,这样应用程序读取数据时就可以一直读取数据直到读到\n为止
2、引入长度
对于定长的包,保证每次都按固定大小读取;
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置。
TCP异常情况
1、进程终止
进程终止会释放文件描述符,仍然可以发送FIN,正常进行四次挥手,和正常关闭没有什么区别
TCP的连接可以独立于进程存在
2、主机关机
和进程终止的情况相同
在进行关机时,就是会先触发强制终止进程操作,触发FIN,进行四次挥手
但这个不仅是进程没了,整个系统也可能关闭
如果在系统关闭之前,对端返回的ACK和FIN到了,此时系统还是可以返回ACK,进行正常的四次挥手
但若系统已经关闭了,ACK和FIN迟到了,无法进行后续ACK的响应。站在对端的角度,对端以为是自己的FIN丢包了,重传FIN,重传几次都没有响应,自然就会放弃此连接(把持有的对端的信息就删了)
3、机器掉电
接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset。即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放
这个是一瞬间发生的,来不及终止进程主机就直接停机了,则对端仍是认为来连接还在的
(1)如果对端是发送端(接收方掉电),发送的数据就会一直维持ACK,触发超时重传,进行TCP连接重置功能,发起“复位报文段”。如果复位报文段发过去之后也没有效果,此时就会释放连接了
(2)如果对端是接收端(发送方掉电),对端一直在等待数据到达,无法区分是对端没发消息,还是对方挂了。
TCP自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放心(心跳包机制)
另外,应用层的某些协议,也有这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态。例如QQ,在QQ断线之后,也会定期尝试重新连接
4、网线断开
和刚才的主机掉电非常类似
当前假设,是A正在给B发送数据,一旦网线断开,A就相当于:触发超时重传 -> 连接重置 ->单方面释放连接;B就会触发心跳包机制 ->发现对端没响应 ->单方面释放连接
基于TCP应用层协议
HTTP、HTTPS、SSH、Telnet、FTP、SMTP、自己写TCP程序时自定义的应用层协议
二、UDP协议
User Datagram Protocol,用户数据报协议
是一种简单的面向无连接的传输层协议,它使用数据包的形式传输数据。
UDP协议端格式
UDP数据包的格式包括以下字段:
- Source Port(源端口):占16位,指明发送方的端口号
- Destination Port(目标端口):占16位,指明接收方的端口号
- Length(长度):占16位,指明UDP数据包的总长度,包括头部和数据部分
- Checksum(校验和):占16位,用于检测数据包是否在传输过程中发生了损坏
- Data(数据):变长字段,包含实际要传输的数据内容
报头(Header):包括源端口、目标端口、长度和校验和字段(用于控制和校验数据包的传输)
载荷(Playload):报头之后的部分(承载实际要传输的数据内容)
校验和
校验和(Checksum)是一种用于在数据传输过程中检测错误的技最,通常用于验证数据在传输过程中是否发生了损坏或篡改。
校验和的计算方法是将数据按照一定规则进行处理,生成一个校验值。
发送端将该校验值附加到数据包中,接收端在接收数据包后同样应用相同的计算方法得出校验值,并与发送端传输的校验值进行比较,以检测数据包的完整性。若二者相同则认为数据在传输过程中没有发生损坏或篡改
校验和计算方法
1)累加和校验
这个是UDP中的校验和计算方法
机制:将数据每个字节的值都进行累加,并将累加的结果保存在一个两个字节的变量中。如果累加过程中发生溢出,溢出的部分会被丢弃,不会影响最终的累加和结果。
局限性:简单但没有对溢出进行处理,对数据的完整性检查能力较弱,容易受到数据篡改的影响
2)CRC算法(循环冗余算法)
机制:选择一个固定的多项式作为生成多项式,将要传输的数据看作一个二进制数,并在其后附加一定数量的0,使得整个数据能够被CRC多项式整除;通过多项式除法运算得到余数作为校验码附加到数据后面一起传输;接收端使用相同的CRC多项式对接收到的数据进行除法运算,如果余数不为0,则表示数据出现错误。
局限性:不同的数据可能会生成相同的校验码,尤其是在数据量较小时,冲突的概率会更高;无法纠错;检测结果受限于多项式选取;无法检测所有错误
3)md5算法
特点:
1)定长:无论原始数据多长计算得到的md5都是固定长度
2)分散:给定两个原始数据哪怕绝大部分内容会一样,只要其中一个字节不同得到的md5值就会差异很大(非常适合hash算法)
哈希表是要把 一个key通过hash函数转换成数组下标,而且希望hash函数能够做到尽量分散,这样产生hash的冲突的概率才会低
3)不可逆:给定原始数据生成md5很容易,但是md5还原原始数据是几乎不可能的(计算机的算力有限)(可应用于密码学场景中)
UDP的特点
1)无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接
2)不可靠:没有任何安全机制,发送端发送数据以后,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息
3)面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分也不会合并
用UDP传输100个字节的数据:若发送端一次发送100个字节,那么接收端也必须一次接收100个字节,而不能循环接收10次每次接收10个字节
4)只有接收缓冲区,没有发送缓冲区:发送的数据会直接交给内核,由内核将数据传给网络协议层协议进行后续的传输工作;虽然有接收缓冲区,但不能保证收到的UDP报的顺序和发送UDP报的顺序一致,若缓冲区满了,再达到的UDP数据就会丢弃
5)全双工:UDP的socket既能读,也能写
6)大小受限:UDP协议头部有一个16位的最大长度,一个UDP能传输的数据的最大长度位64K(包含UDP头部)
基于UDP的应用层协议
- NFC:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
- 自己写UDP程序时自定义的应用层协议