一、UDP
UDP协议端格式
大概是这个样子

这样更好理解

这里的长度指的是整个UDP数据报的长度,理论上也就只有64kb,这个长度放在现在是显然不够的。
有两种解决方案:
1.应用层代码进行拆包,把一个大的数据报拆成若干个小的数据报,然后用UDP传输。
问题:工作量大,逻辑复杂,维护成本较高。
2.使用TCP协议,TCP没有数据包长度的限制。
校验和
这里的校验和不同于HTTPS的数字签名,后者是防止黑客篡改,UDP里的校验和则是为了防止传输过程中的"比特反转"(由于外界干扰引起的导致信号发生变化的问题)
这里的思路是,在传输之前,先计算一个校验和,然后把数据包和校验和一起发送,接收方在拿到数据包后再次计算一个校验和,与接收的校验和进行比较,若一致,则数据正确,反之,则直接丢弃。
UDP使用了CRC方式来计算校验和,通过把除了校验和之外的属性都当做整数,全部进行累加(溢出也无所谓),直到得到结果为止。
当然,校验和相同,并不能说明传输的数据一定没问题。例如,有两个字节同时发生了比特翻转,前一个变大,后一个变小,计算结果虽然相同但数据已然发生变化。但这种情况发生的概率非常小,可以忽略不计。
二、TCP
TCP协议端格式

这里只说明与UDP区别较大的部分:
**选项:**这个位置是用来根据需求加入一些信息的,是可有可无的,也就是说,TCP的长度是可变的。
4位首部长度:4位,就是四个比特位,即取值范围是0 ~ 15,但它的单位是4字节,也就是说,当取15时,表示报头长度位60个字节。
**保留(6位):**这个位置是预留的,防止以后可能会添加一些属性之类的内容,这点相较于UDP,考虑的更长远。
6位标志位:
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
TCP核心机制
可靠传输
保证TCP可靠传输的关键机制:确认应答,超时重传
确认应答
网络上有一种现象叫做"后发先至":

可能你发的第二条信息会比第一条先到,在网络中,两个数据包可能走了不同的路由器路径。后发的包走了一条更畅通的"快车道",而先发的包堵在了一条拥挤的路径上。
为了应对这种情况,TCP会对传输的数据进行编号。

这里的发送方序号会放在报头的32位序号 里,而应答时会根据序号,把应答的序号放在32位确认序号里。当ACK为1时,表示这是应答报文
因为TCP是面向字节流的,所以每个字节都有一个编号,连续递增。序号字段 填写的是载荷第一个字节的序号,确认序号填写的是最后一个字节序号 + 1

TCP会在接收方这里安排"接受缓冲区",通过网卡读到的数据,先放到接收缓冲区里,根据序号来排列,序号小的在前面,大的在后面,先确保前面的数据到了,才解除阻塞,read开始读取,否则,read继续阻塞
超时重传
主要是针对丢包的情况进行处理
TCP引入超时时间,来判断是否丢包,会设置一个阈值T,当超时时间超过T,会把T变大一些,进行重传。当然,超时次数达到一定程度时,会认为出现严重故障,放弃这一次传输
丢包的情况有两种,第一种是发送方发送数据包时丢包了

这种情况下重传即可,但还有一种情况是接收方收到数据包但是发送ACK的时候丢包了

这种情况也不用担心,因为TCP会在接受缓冲区进行去重操作,当收到一个数据包时,根据序号进行判断,当缓冲区有该序号时,直接舍去即可
连接管理
建立连接
TCP通过"三次握手"建立连接

在建立连接时,客户端会发送一个同步报文段syn,其实就是把数据包的6位标志位中的syn置为1。双端会进行数据的同步,TCP的同步指的是数据上的同步,b会保存a的关键信息,这里的关键信息就是端口号和IP地址。
b依据确认应答机制发送一个ack,同时发送b的syn
a最后发送确认报文

LISTEN表示服务器启动,客户端随时可以连上来
这里的ESTABLISHED指的是已经连接了的,表示客户端与服务器连接建立成功
三次握手的作用
1.判断网络链路是否同通畅
2.验证通信双方的发送能力和接收能力是否正常
3.三次握手过程中,可以协商一些关键信息,比如序号从几开始
断开连接
"四次挥手"

中间的两步可以合并为一步吗?
ACK是由操作系统内核发送的,而FIN实际上是socket.close方法,取决于程序的逻辑,所以可能会比ACK慢一点,但是有一种场景,在延时应答情况下,ACK和FIN的发送是可以合并的。
但总体来看,分为4步比较合理。

谁是主动发起FIN的一方,就会进入到**TIME_WAIT,**它的意思是等一会再释放,什么意思呢?
因为发送端在发送ACK时可能会丢包(前面丢包超时重传就行),这里的情况比较特殊,如果不等直接释放的话,发生丢包,接收方重新发送FIN,此时因为发送方已经释放连接了,导致无人处理FIN,所以发送方要等一会释放连接。至于等待时间,一般就是2MSL,不同的操作系统时间不同。
谁是被动发起FIN的一方,就会进入到CLOSE_WAIT(调用socket.close)
滑动窗口
回顾前面的确认应答过程,必须每次发一个数据段,都要给一个ACK确认应答,收到ACK后才能发送下一个数据段,性能较低。于是采用一次发多条数据段的形式。

窗口大小指的是每次发送的数据最大值,上图所示的最大值是4000个字节
一个窗口内每发送一个数据段无需等待ACK,而是这一组发完之后,收到一个ACK之后继续发送第5个数据段,后面以此类推。

如果第二个ACK先到呢?那么滑动窗口直接移动到第二个数据段即可。因为如果已经收到2001的数据,那么小于2000的自然也就收到了。
滑动窗口是在可靠运输的基础上提高效率。
滑动窗口的丢包问题
数据包到达,但ACK丢了
这种情况不打紧,因为即使前面的ACK丢了,只要后面的ACK抵达,就表明前面的数据已经到达了,后一个ACK会涵盖前面的ACK的含义。
数据包丢了
当某⼀段报⽂段丢失之后,发送端会⼀直收到1001这样的ACK,就像是在提醒发送端"我想要的是1001"⼀样;
如果发送端主机连续三次收到了同样⼀个"1001"这样的应答,就会将对应的据1001-2000重新发送;
这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前就 已经收到了,被放到了接收端操作系统内核的接收缓冲区中;
这种机制被称为"⾼速重发控制"(也叫"快速重传")
前面提到的超时重传,是在数据量少,不足以构成滑动窗口批量传输的情况下使用的
流量控制
滑动窗口的窗口越大,效率就越高,但是不能无限大,因为接收方的处理能力是有上限的。
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度.这个机制就叫做流量控制(Flow Control)
在TCP的首部中,有一个16位窗口字段,它存放的是接收方缓冲区剩余空间的大小,把这个属性添加到ACK,发送方会根据这个数字来重新设定滑动窗口的大小
如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送⼀个窗口探 测数据段,使接收端把窗口大小告诉发送端

由于16位最多表示64kb,那么滑动窗口最多就是64kb?
在选项里有一个特殊的属性,叫做窗口扩展因子。举个例子,当它取2时,表示把64左移两位,就变成256kb,指数增速是非常快的,这样就不用担心上限的问题了。
拥塞控制
拥塞控制的大体思想是,先发送少量的数据,判断当前网络拥塞情况,然后逐渐加大数据量,找到平衡的范围。它和流量控制都是对窗口大小进行限制,在实际中,这两个值谁小,窗口大小就是谁。

刚开始慢启动,但启动速度是指数级别,在达到预先设定的阈值时变为线性增长。
然后增长到一定程度,开始出现丢包时,更新阈值为此时窗口数量的一半
旧版方法是重新进行慢启动,不过因为阈值发生变化,所以传输轮次会变少,这个版本已废弃
新版本则是在新阈值位置开始进行线性增长
延迟应答
服务器如果一收到数据就立刻ACK应答,这时候返回的窗口可能比较小。但实际上处理端可能处理的很快,如果接收端多等待一会再应答,返回的窗口可能会大一些,网络吞吐量就大一些,传输效率就会高一些。
那么所有的包都可以延迟应答么?肯定也不是
数量限制:每隔N个包就应答⼀次;时间限制:超过最大延迟时间就应答一次;具体的数量和超时间,依操作系统不同也有差异;⼀般N取2,超时时间取200ms;

捎带应答
在延迟应答的基础上,ACK可以往后延迟一些,响应数据此时正好要发送,那么干脆让数据包捎带一下ACK,把两个包合成一个,提高了传输效率

面向字节流
粘包问题
粘包问题中的包指的是应用层数据包,粘包问题指的是在TCP传输过程中由于是以字节流的形式进行传递的,导致应用层在面对这一连串的数据时,无法确定一个完整的数据包是从哪儿到哪儿。
如何解决粘包问题呢?
1.约定好包与包之间的分隔符,例如\n
2.约定包的长度,在包头位置约定一个字段来表示包的长度
异常情况的处理
1.进程崩溃
进程崩溃和主动退出没什么区别,进程崩溃后会调用socket.close,从而触发FIN,重复前面的四次挥手过程
2.主机关机了
其实和进程崩溃一样,也是杀死所有的进程
3.机器掉电/网线断开
接收端认为连接还在,⼀旦接收端有写⼊操作,接收端发现连接已经不在了,就 会进行reset.即使没有写⼊操作,TCP自己也内置了⼀个保活定时器,会定期询问对方是否还在.如果 对方不在,也会把连接释放