TCP协议
什么是TCP协议?
TCP,即Transmission Control Protocol 传输控制协议
TCP协议端格式

源/目的端口号:表示数据是从哪个进程来,到哪个进程去
32位序号/32位确认序号:后面会详细展开
4位首部长度:表示该TCP头部有多少个32位bit(有多少个4个字节) (固定部分已经有20字节,所以这里不是以字节为单位,而是以4个字节为单位) 固定部分已经为20,选项部分最多是40
6个标志位:
URG/ACK/PSH/RST/SYN/FIN(在后面讲解TCP原理时会详细介绍(这是TCP最核心的六个标志位)
16位校验和:发送端填充,CRC校验 接收端校验不通过,则认为数据有问题,此处的校验和不光包含TCP首部,也包含TCP数据部分
16位紧急指针:标识哪部分数据是紧急数据
TCP的核心机制
TCP的核心机制是可靠性
网络通信非常复杂,此处的可靠性不是a给b发一个消息,b100%能收到,而是a给b发了消息之后,尽可能让b收到(100%不能做到,再厉害的技术,也顶不住挖掘机的一铲子)
核心机制一:确认应答
保证可靠性的一个关键前提,是发送方知道自己的数据是否被对方收到,需要对方返回一个"应答报文"(acknowledge,ack) 发送方知道应答报文,就可以确认对方收到了
但这会有一个明显缺陷:当连续发送多个数据的时候,会返回多个应答报文,有可能后面的数据先返回了报文(无法确定前面的信心是否收到了) 这是后发先至的典型情况
那么网络上为什么会出现后发先至呢?
这是因为转发数据,每个路由器/交换机相当于"十字路口",不同数据选择的路线可能不同,那么应答报文到达的时间也就可能不同
针对这一问题TCP解决方案:给传输的数据进行编号


当ack为1,表示这是一个应答报文(确认序号只在应答报文中生效)
TCP是面向字节流的,其实在编号的时候,不是按照一条,两条来编号的,而是按照"字节"来编号的 每个字节都分配一个编号,编号是连续递增的
序号字段填写载荷部分的第一个字节的序号,确认序号,把收到的数据载荷的最后一个字节序号+1填到确认序号中
引入序号之后,接收方就可以根据序号对数据进行排序
TCP在接受方会安排一个"接受缓冲区"(内存,操作系统内核里) 通过网卡读到的数据,先放到接受缓冲区中,后续代码调用read,也是从接收缓冲区来读的(生产者消费者模型) 根据序号来排序,确保序号小的在前面,确保前面的数据已经到了,read才能解除阻塞,如果后面的数据先到,read继续阻塞,不会读取数据
核心机制二:超时重传
这一机制是针对丢包情况做出处理
当达到等待时间的上限,还没有收到ack,a就认为传输过程中发生丢包了,两种情况:1.a->b发的数据丢失了 2.b->a返回的ack丢了
为什么会丢包呢?(丢包是无法避免的客观现象,重传是有效对抗丢包的手段)
如果数据报经过某个路由器/交换机转发的时候,该路由器/交换机已经非常繁忙了,导致当前需要转发的数据量超过路由器/交换机的上限 槽糕的情况是数据报太多了,处理不过来了,接受缓冲区都满了,只能丢弃了(网络上的数据包都有时效性)
即使某个数据报等了很长时间成功转发了,但由于时间过长也是无效的,不如重新传
为此TCP引入超时时间来判断是否丢包
TCP中,判定超时的时间阈值不是固定数值,而是动态改变的
假设当前a->b发送数据,丢包的超时阈值为T,当a给b传输发生超时之后,就会延长这个时间阈值,会继续延长这个时间(但这个延长时间不是无限制的 当超时次数达到一定程度/等待时间达到一定程度,就认为网络出现严重故障,放弃这次传输)
随着重传的进行,数据到达对方的概率越来越高,重传如果还不成功,这也就意味着当前丢包的概率很大,网络大概率出现严重故障,此时如果继续重传也就没有意义了(重传频率没必要很高了)
发送方不知道数据丢了/ack丢了,做法都是重传


当b收到两份一样的操作,TCP会在内部进行去重操作,接受缓冲区就可以根据序号,在接受缓冲区找,如果存在就直接丢弃,如果不存在就放入缓冲区(去重和排序时同时进行的)
确认应答和超时重传是TCP协议中最核心的两个机制==>这保证了TCP能够进行可靠传输
核心机制三:连接管理(安全机制)
建立连接:三次握手 断开连接:四次握手
建立连接 TCP通过"三次握手"方式来完成,握手操作,没有实际的业务,只是打个招呼(下图就是一个简单的三次握手的示意图)

这里我们注意到syn,这是TCP6个标志位中的一个,请求建立连接,我们把携带SYN标识的称为同步报文段 各自让对方存一下自己的关键信息,并且各自给对方返回一个ack,告知自己收到了
synchronized 有多种含义:在多线程中理解为互斥 在这里理解为同步 TCP的同步指的是"数据上的同步",A告诉B,接下来我要和你建立联系
三次握手,一定是客户端主动发起syn(客户端和服务器都是角色,同一个程序在不同场景下功能可能不同)
为什么要进行三次挥手?
1.三次挥手相当于"投石问路" 先初步的探一探网络的通信链路是否通畅(网络的通常是可靠传输的前提条件)
2.验证通信双方的发送能力和接受能力是否正常
3.三次握手的过程,可以协商一些关键信息
(三次握手也有确认应答和超时重传)
建立连接的操作,如果只握两次手是不够的,握四次可以,但没有必要
TCP要协商一个非常关键的信息,通信过程中,序号从几开始,初始序号一般不是从0开始的,并且两次连接初始序号都是不同的(往往差别比较大) 通过序号来区分连接
四次挥手 断开连接

这里的FIN:通信双方,各自给对方发FIN,表示我要把你存储的信息删除了
这里我们有疑问了:为什么三次挥手建立连接的时候ack和syn可以合并,而四次挥手断开连接却不能将ACK和FIN合并
这是因为四次挥手ack和fin的交互的时机是不同的
ack是内核控制返回的,内核收到FIN,第一时间返回ACK,和代码无关,FIN是代码中调用socket.close才会触发的(进程结束,也能触发)
而FIN的时机和ACK的时机可能不同
三次握手:一定是客户端主动发起SYN()(第一次握手,一定是客户端开头的)
四次挥手:客户端和服务器都可以主动发起FIN(看谁先调用close)(从实践角度来看,一般都是客户端主动发起的)
谁是主动发起FIN的一方,就会进入到TIME_WAIT
谁是被动发起FIN的一方,就会进入到CLOSE_WAIT
CLOSE_WAIT在正常开发中是看不到的,TIME_WAIT是为了给最后一个ack丢包兜底(如果在收到fin之后直接释放,那么如果ack丢包再次重传也无济于事)
那么TIME_WAIT等多久会比较合适呢?
2*MSL(MSL是指网络上任何两个节点传输过程中消耗的最大时间 -->通常这个时间会配置成60s 不同系统可能不同)
超时重传的时间阈值是ms级别的
下图是具体的过程

下图是TCP状态转换的一个汇总

核心机制四:滑动窗口
刚才我们讨论了确认了应答策略,对于每个发送的数据段,都要给一个ACK确认应答.收到ACK之后再发送下一个数据段,这样有一个比较大的缺点,就是性能比较差,尤其是数据往返的时间较长的时候
我们的解决方案是批量传输,用一份时间等多组ACK(把多组ACK的时间叠成一份)

窗口大小:不需要等待,能够连续发送的最大数据量
下一组怎么发呢?
1.等这一组的ack都回来,再发第二组
2.收到一个ack,就发下一条
窗口越大,批量发送的数据就越多,效率就越高 但是窗口也不能无限大,太大也会影响到可靠性
滑动窗口是在可靠传输的基础上,提高效率
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉
滑动窗口中,也会丢包
情况一:数据包已经到达,ACK别丢了

情况二:数据包直接丢了

快速重传:谁丢了重传谁,整个重传的速度很快(滑动窗口下,超时重传的变种操作)
ps:超时重传和快速重传并不矛盾,只是针对不同情况下的重传机制
超时重传:传输的数据量少,没有构成滑动窗口批量传输的形式
快速重传:传输的数据量多,形成滑动窗口(滑动窗口是在可靠性基础上提高效率的机制)
核心机制五:流量控制(安全机制)
滑动窗口,窗口越大,效率就越高,但是不能无限大,太大了会影响到可靠性,接收方的处理能力也是有上限的
流量控制可以让接受方,根据自己处理数据的速度,反馈给发送方,限制发送方的发送速度
我们可以在TCP协议段格式中看到16位窗口大小
那么是否滑动窗口中,最大数值就是64kb呢?
不是的,TCP首部40字节选项中有一个特殊的属性(窗口拓展因子) M
实际上窗口大小是窗口字段的值左移M位
接受方接受缓冲区的剩余空间大小填入到这个属性中(窗口大小) 发送方会根据这个数据来重新设定发送的滑动窗口大小(滑动窗口的大小是动态变化的)


接受缓冲区满了,如果继续发送就要丢包了 发送方发送探测包只是为了触发ack,主动询问一下,接受方咋样了 发送方发现窗口大小为0就会暂停发送
核心机制六:拥塞机制
流量控制是根据接收方处理能力,进行限制的, 拥塞机制是根据传输链路的转发能力,进行限制的(根据接受缓冲区空余空间来定量衡量)
拥塞机制和流量控制都能限制发送方的窗口大小,谁小谁说了算
拥塞窗口:1.发送开始的时候,定义拥塞窗口大小为1; 2.每次收到一个ACK应答,拥塞窗口加1
3.每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小作比较,取较小的值为实际发送的窗口
这样的增长速度,是指数级别的, 慢启动(初始时慢,但增长速度非常快)
为了不增长那么快,引入了一个叫做慢启动的阈值 当拥塞窗口超过这个阈值时,不再按照指数的方式增长,而是按照线性方式增长

少量的丢包,只是触发超时重传 ;大量的丢包,我们认为网络堵塞
核心机制七:延时应答
默认情况下,接受方都是在收到数据包第一瞬间,就返回ack,但是可以通过延时返回ack的方式来提高效率
接收缓冲区,剩余空间大小

等一会的过程中,应用程序就会消耗掉缓冲区的一部分数据了,这样下次传输的数据会更多
但是这也不是100%能提高效率,还是看应用程序;并且不是所有的包都可以延迟应答(有数量限制和时间限制)
实际上,这种是综合的,当传输的数据密集时,按照第一个 当数据稀疏时就按照第二个
核心机制八:捎带应答
TCP已经有延时应答了,基于延时应答,引入"捎带应答",返回业务数据的时候,顺便把上次的ack给带回去

如果没有延时应答,返回ack和响应的时机就是不同时机 引入了延时应答,恰好这个时候要返回响应数据,此时就可以把ack也代入到响应数据中,一起返回
ack:ack设为1,窗口大小设为接受缓冲区的剩余值,确认序号,设定为合适的值(都是报头里设置的)
设置这些内容不影响响应数据 把两个包合成一个,就能起到提高效率的作用
核心机制九:面向字节流
这里要强调的是他背后的粘包问题,粘的是"应用层数据包",通过字节流传输.很容易混淆包与包之间的边界,从而使接收方无法区分从哪里到哪里是一个完整的包
这个问题,在TCP层次上是无解的,需要我们站在应用层解决,定义好应用层协议,明确包之间的边界:
1.约定好包与包之间的分割符(包的结束标记)
2.约定好包的长度(比如约定每个数据包开头的四个字节,表示数据包一共多长)
这个解决方案,在HTTP中两种方案都有体现:1.get请求 没有body,使用空行作为结束标记
2.post请求,有body的时候,通过content_length决定body多长
核心机制十:异常情况处理
TCP在通信过程中存在特殊情况
1.某个进程崩溃
进程崩溃和主动退出,没有本质区别,进程释放,回收文件描述符表中的每个资源=>调用socket的close (FIN 四次挥手,进程虽然没了,但TCP的连接信息还存在,此时的四次挥手还是可以正常进行的)
TCP连接释放的时机更晚
2.主机关机了
正常流程的关机,本质上还是会杀死所有的用户进程(和1类似)
关机需要一定时间,如果在这段时间内,四次挥手挥完了,就和正常的一样了,如果没有挥完呢?最终还是会释放掉连接(多次重传仍然无回应,认为对端出现严重问题,就会主动放弃连接
3.主机掉电了
拔电源(不要随便拔,可能会出现问题 台式机这样的情况,非常伤硬盘)

(接收方突然掉电)A突然掉电了,B后续发来的数据都没有ACK了,对于B来说会触发超时重传 但超时重传并不能解决问题,重传达到一定次数,就会触发"重置TCP连接" B会主动发送一个复位报文(从头开始RST)

发生方突然掉电了,B突然发A无动静了,此时B无法区分A是挂了还是暂时休息一会,B只会继续等,但不会无线等,一段时间后就会给A传输一个特殊的报文"心跳包",不携带业务数据,只是为了触发ack
心跳包:1.周期性的 2.如果没有反应的话,认为挂了
4.网线断开了

站在A的视角就是和"接收方掉电"是一样的 站在B的时间,就和"发送方掉电"是一样的情况
补充:
URG紧急指针 TCP正常情况,就按照序号发送和接受紧急指针就相当于插队
PSH:催促标志位 发送方给接收方发的数据中带有这个标志位,接受方就会尽快的将这个数据read到应用程序中
选项: RFC标准文档 不同kind下,选项中的长度,各个部分的含义都是啥
基于TCP应用层协议
HTTP/HTTPS/SSH/TeInet/FTP/SMTP
UDP协议
什么是UDP协议?
UDP协议是传输层的一个重点协议(在网络编程套接字的学习中我们已经有了一定的了解,这里不再过多赘述)
UDP 协议端格式


我们可以知道,UDP数据报的长度表示的范围是64KB,那我们如何传输一个大的数据呢?
法一:直接换成TCP(TCP无数据报长度的限制)
法二:校验和(校验和是两个字节的数据)
通常情况下校验和是验证数据是否发生修改的手段,而UDP校验和不是为了防别人,而是防止出现传输过程中的"比特翻转"
在UDP发送之前,先计算一个校验和,把数据和校验和一起发送给对端,接收方收到之后,重新计算一下校验和,和收到的校验和进行对比(不一致的话就丢弃)
UDP的校验和使用了CRC方式来进行检验(循环冗余校验) 把每个字节(除了校验和位置的部分之外)都当做整数,进行累加,溢出也没关系,继续加最终得到结果crc校验和
传输到对端,数据出现错误了,对端再次计算的校验和就会和第一个校验和不一样,此时,就会丢弃数据
(认为,两个原始数据相同,使用相同的校验和算法,得到的校验和也是相同的; 反之,如果两个校验和相同,原始数据时不一定相同的(但工程上概率极低,可以忽略不计))
UDP特点
UDP传输的过程类似于通信
无连接:知道对端的ip和端口号就直接进行传输,不需要建立连接
不可靠:没有任何安全机制,发送端发送数据报以后,如果因为网络故障无法发送,UDP协议层也不会给应用层返回任何错误信息
面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并
全双工:UDP只有接收缓冲区,没有发送缓冲区 UDP的Socket既能读,也能写(全双工)
基于UDP的应用层协议
NFS:网络文件系统
TFTP:简单文件传输协议
DHCP:动态主机配置协议
BOOTP:启动协议(用于无盘设备启动)
DNS:域名解析协议