目录
一、UDP协议
二、TCP协议
TCP报文结构
TCP十大核心机制
确认应答
超时重传
接收缓冲区
连接管理
建立连接
断开连接
一、UDP协议
学习一个网络协议首先要学习报文结构。
对于UDP协议来说,应用层数据到达UDP后就会给应用层数据加上UDP报头。
(UDP数据报 = 报头 + 载荷)
这是UDP报头格式:
16位源端口号和16位目的端口号就是发送方的端口号与接收方的端口号;
16位UDP长度:整个UDP数据报占多少长度,16bit是两个字节,能存储的数据范围是0~65535也就是 64kb,所以整个UDP数据报最多占64kb,减掉报头占的八个字节得出载荷(数据)可以占64kb-8字节的容量。
正因如此使用UDP开发程序就会受到很大制约,每次传输的数据报不能超过64kb
16位检验和(checksum):数据在传输过程中是可能出错的,比如比特翻转/丢包,比特翻转是指你传输的数据本来是0结果到了对端变成了1,就需要有一些办法对传输的数据进行校验:(1)能够发现是否出错 (2)能够发现哪位出了错并且进行纠正。第一种办法代价较小,第二种办法消耗的时间空间更多,以上两种办法本质上都是引入冗余信息。
在UDP中校验只能做到第一种的程度。
校验和:通过引入冗余信息来校验传输的数据是否正确,拿着数据的一部分进行计算得到结果,拿着结果去比较,如果传输过程中数据发生变化那么结果也会发生变化,比对时接收方发现校验和不一致就会将这组数据丢弃。
UDP中使用CRC算法计算校验和,CRC的计算方式简单粗暴:设定两个字节的变量,把数据的每个字节取出来在这上面累加,如果结果溢出超出两个字节就把溢出部分舍弃。
这里计算校验和不是为了进行算术运算/得到准确的值,而是为了让每个字节都参与进来影响结果,不关心具体数值多少,只关心接收方从网络接收的校验和与自己算的是否相同。
UDP现在的主要场景是对于性能要求高但对于可靠性要求不高的场景,比如分布式系统中多个服务器之间进行通信,网络结构简单&带宽充裕。
二、TCP协议
TCP的特性:有连接、可靠传输、面向字节流、全双工;可靠传输是TCP最核心的机制,而这样的机制在代码是无法直观看到的。
TCP报文结构
首先介绍TCP的报头结构:
先介绍比较容易理解的
1、源端口/目的端口:发送方的端口号与接收方的端口号
2、4位头部长度:是指报头的长度
3、选项(optional):英文翻译过来是可选的,也就是可以加也可以不加。头部长度是4bit也就是能表示的数据范围是0~15,此处头部长度单位是4字节,再乘4之后能表示的数据范围就是0~60,最大数据长度就是60字节,去掉固定的20字节得出选项最多40字节;结论:如果选项没有,tcp报头最大长度是20字节,如果选项有,报头最大长度就是60字节。
4、六位保留:UDP的报文长度使用两个字节来表示太小了,所以在TCP报文里提前申请好一块空间先不用,但是以后说不定能用上,这个空间就叫保留位。
万一未来某一天TCP需要扩展一些新功能就用这个保留位来表示。
5、TCP报文中的六个标志位(SAFURP):TCP的灵魂,文章后面会依次提到,具体展开看是
6、16位检验和:此处检验和与UDP中的作用是一样的
TCP十大核心机制
确认应答
对于TCP协议来说要解决一个很严重的问题——可靠传输,所谓可靠传输,不是发送方的数据百分百传输到接收方,而是尽可能的让发送方知道数据是否传输到接收方。
比如说班级里发送了一条通知,同学们都要回复一条收到,这条收到就是用来应答班级发出的请求,这样用于应答的数据报文称为应答报文,也就是标志位中ACK。
上述单纯的应答在批量发送数据时就会产生麻烦,在网络传输中经常会发生一个情况——后发先至,按照上述的栗子也就是班级群先发了一条明天放假的通知,又发了一条明天有没有同学想来做志愿者的通知,你按照顺序回复收到和不想,而发送出去的应答报文则是不想先到,收到后到,就变成了你不想放假你想做志愿者;
后发先至是网络中客观存在不可以改变的。解决这个问题的办法就是:给传输的数据进行编号,应答的数据编号与传输的数据编号能够对应起来,即使出现后发先至也不影响对于传输数据的理解。
7、32位序号/32位确认序号:给应答报文使用(ACK为1才有效),根据确认序号就可以知道应答的哪条报文。
实际TCP序号不是按照一条两条的方式来进行编号,而是按照字节来编号,每个字节都会分配一个序号;假如说TCP载荷中存储了1000字节的数据,每个字节都有编号而且顺序是递增的,假如说第一个字节的编号是1,那么第1000字节的编号就是1000,此时TCP报头中存储的编号是载荷中第一个字节的编号,此处存储的也就是1。(由于递增的特点,知道第一个字节的序号就可以知道后面的所有序号)
确认序号的设计也很有特点,确认序号是载荷中最后一个字节的编号+1;此处可以理解成:A向B发送了载荷1000字节编号1~1000的数据包,B应答给A一个确认序号是1001的应答报文,对于B来说<1001的字节都已经收到了,对于A来说可以继续发送1001~2000的数据了。
(发送方发送的数据包只有序号字段有效,确认字段无效;接收方返回的ACK只有确认字段有效)
TCP最核心的功能就是可靠传输,可靠传输主要依靠确认应答机制,通过应答报文告诉发送方数据的传输情况,如果当数据的传输没那么顺利就会出现丢包的情况,TCP的另一个核心机制——超时重传,来解决丢包问题。
(丢包:数据传输过程中被丢弃了无法传送至对端,也是客观存在的随机现象;丢包出现的原因:每个路由器/交换机的转发能力存在上限,如果要转发的数据量超出自身的极限,多出的部分就会被丢弃)
超时重传
用来应对网络出现丢包情况的策略
正常情况下,TCP通过ACK确认应答报文来判断数据是否顺利到达,如果在传输过程中出现丢包情况,接收方就收不到数据也就不会给发送方发送确认应答。正常发送方发送完数据要等待一段时间才能收到ACK,如果等待时间超过了一定的阈值(超时),发送方就会认为发生了丢包的情况,此时就会把数据进行重传。
这是数据丢了的情况下,那么ACK丢了呢?
这种情况下接收方正常接收数据返回ACK但是返回的ACK在传输过程中发生了丢包,此时发送方还是迟迟等不到ACK,等待时间超过了阈值后还是会进行超时重传,而此时接收方就会接收到两份一样的数据,这样的情况是非常不科学的:
TCP接收方会针对发来的数据按照序号进行去重,TCP层次上对于重复传输/重复接收无所谓,只要应用层读到的数据是不重复的就行,无论重传多少次内核都会保证应用层读到的数据是一份,此时可以确保应用程序不会因为重传而产生bug,核心的机制就是——去重。
接收缓冲区
接收方的内核中存在一个数据结构——接收缓冲区(类似于阻塞队列)
这个过程是:A通过socket api操控内核给B的内核发送数据,B内核中进行去重后才会发给用户态。通过该图帮助理解
B拿到数据后层层分用(accept->clientSocket->InputStream->read),当进行到TCP这一层时就会有一个阻塞队列数据结构,将数据放到阻塞队列中,到read的时候就会将重复数据从内存缓冲区删掉。
向内存缓冲区放数据的过程中会根据数据的序号在内存缓冲区中进行判断,如果存在/曾经存在过就会被直接丢弃。
此处也是生产者消费者模型,A是生产者,B是消费者,中间的内存缓冲区就起到了阻塞队列的作用。
接收缓冲区除了去重还有一个很重要的功能,针对收到的数据排序。
网络传输过程中会出现后发先至,很多时候我们不希望的这样的情况发生想让我们发送出去的数据有序的到达接收方。虽然在传输过程中是后发先至的,在内存缓冲区针对数据的序号进行排序,小号在前面大号在后面,数据与数据间的序号是连续的。
丢包本身就是一个小概率事件,假设丢包的概率是10%,那么重传的数据丢包的概率就是1%,如果连续几次重传都丢包说明丢包的概率相当大了,此时应该是网络出现了问题。
超时重传的时间是多少呢?
这个时间不是固定数值会动态变化,随着重传的轮次增加会逐渐变长;假设第一次等待时间是50ms触发超时重传,那么下一次就会等待100ms,如果还是没收到ACK就会等待更长的时间。
如果网络出现严重问题,重传多次多次还是无法收到ACK,达到一定次数阈值就会尝试重置连接,
触发一个重置报文RST尝试重置连接,重置就是通信双方清空之前TCP传输的中间状态重新开始传输,网络出现严重故障时RST报文也是无法进行重新连接的,这样只能断开连接。
正常重传后接收方会返回ACK确认应答,发送方收到ACK即可进行正常传输。
超时重传是确认应答的重要补充,TCP的可靠传输主要靠确认应答与超时重传的机制。
连接管理
建立连接:三次握手。 断开连接:四次挥手;
可以理解为两个机器一见面先进行打招呼,打招呼的过程中不进行实际上的数据交互是为进行打招呼而传输一些数据,握手与挥手过程中传输的网络数据包不携带任何与业务有关的数据
建立连接
通过三次交互让通信双方保存对方的信息。实现过程是:客户端给服务器发送一个SYN同步报文,服务器收到后返回ACK+SYN,客户端收到后再次返回ACK,此时成功建立连接:
客户端给服务器发送的数据报载荷是空着的只有报头,报头中的SYN标志位为1。
上述流程其实是四次交互,客户端给服务器发送同步报文为第一次交互,服务器收到同步报文后会先返回一个ACK确认应答,再服务器进行一系列操作后才会返回SYN,这是两次交互,而将这两个标志位合并为一个数据报发送可以提高效率,也就是在一个数据报中SYN和ACK标志位都为1。
三次握手的时候相当于让对方保存好自己的信息,保存好对方的信息(ip和端口)才算建立完成。
三次握手的意义及解决的问题
(1)三次握手相当于投石问路,在正式传输数据之前先确认一下通信链路是否畅通,也相当于保证TCP可靠传输的一种方式
(2)通过三次握手确认通信双方的接收和发送的功能是正常的。
在交互过程中,步骤一执行完毕后B就会知道:A的发送功能是正常,B的接收功能是正常的;步骤二执行完后:A会知道A的发送功能/接收功能都是正常的,B的发送功能是正常的;步骤三执行完后:B会知道B的发送功能是正常的。此时正常的测试功能就完成了,可以进行交互了。
(3)协商一些必要的参数:通过双方共同商量一些参数;TCP通信时使用的序号就是协商出来的,往往不是从0/1开始,而是通信双方一起协商出一个数字,第一次连接和第二次连接协商出的数字往往差别很大
TCP考虑到一个情况:“前朝的剑斩本朝的官”,也就是通信双方在进行若干次通信后断开再进行连接后,可能再这若干次通信过程中某个数据报走丢了造成发送方超出了等待时间进行了重传,而当这个数据报到达接收方时已经物是人非,现在已经是一个全新的连接,服务器收到这个数据报后会进行丢弃。
如何区分当前收到的数据报是否是以前的连接产生的数据报?
通过序号就能分辨出来,每次建立连接都是一个全新的起始序号,如果收到一个数据报的序号与起始序号差别很大就可以当作是以前连接产生的数据报。
三次握手过程中TCP的状态:
Listen:服务器出现的状态,当服务器绑定端口成功后就会进入Listen状态,随时可以有客户端连接过来。
Established:建立完成,可以随时进行后续通信了。
断开连接
四次挥手中,双方都可以作为提出断开连接的一方先发送FIN结束报文
断开连接:通信双方将保存的对方的信息删除。
假设这里先发出FIN结束报文的是客户端,客户端通过socket.close或者关闭进程等方式都可以发送结束报文。
四次挥手中间的两次挥手是否能合并?分情况
当服务器收到FIN后立即返回ACK,而返回FIN需要等服务器调用到close方法后才会返回,这两个操作可能会隔很久。
特殊情况下可以合并,TCP的机制——延时应答(不是立即回复ACK,而是等一会和FIN一起发送)
四次挥手期间的状态:
CLOSE—WAIT:被动一方进入的状态,等待应用程序调用close方法,如果程序出现问题close没有被调用或者没有close方法就会导致机器上出现大量CLOSE_WAIT.
TIME—WAIT:存在意义是为了应对最后一个ACK丢包的情况:
刚才的情景中客户端向服务器发起FIN后,服务器先返回ack表示应答后在应用程序调用close返回FIN,此时服务器没有真正的删除对方的信息,而是要等待收到客户端返回的ACK才会正式删除对方的信息断开连接;如果最后一个ACK出现丢包的情况那么服务器就会重传FIN报文,客户端在处于TIME_WAIT期间就是在等待服务器发起的重传,如果超出等待时间还没等到重传的报文就会默认ACK正常送达,此时TIME_WAIT就可以释放了。
CLOSE_WAIT是被动发起的一方的状态
TIME—WAIT是主动发起的一方的状态