1.应用层协议
应用层协议一般是程序员自己定义的数据传输的格式,然后调用传输层的api进行真正的网络通信。
这里的自己定义格式就是"自定义传输格式",例如要使用QQ发送一条信息就要由程序员进行约定传输层格式。
1.自定义协议的过程主要约定两件事
通信的信息是什么
通信的信息是根据需求而确定的
2.通信的数据格式是什么
格式由程序员自行定义,可能是客户端程序员定义,也可能是服务器程序员定义。
例如现在1234将hello这条信息发送给5678在2025年7月23日9:17
1)行文本方式
1234.5678.2025-7-23.9:17
行文本方式的可读性较差
2)XML
<request>
< from>1234</from>
<to>5678</to>
<time>2025-7-23.9:17</time>
<message>hello</message>
</request>
XML方法虽然解决了可读性问题,但是因为有重复的标签,会消耗较多的网络带宽。
3)JSON
{
"from"1234,
"to"5678,
"time"2025-7-23.9:17,
"message"hello,
}
JSON是一种现在较为流行的方法,既继承了可读性的优点,又继承了不是特别消耗网络带宽
除了自定义应用层协议,还有一些现成的应用层协议可以直接使用,例如HTTP
2.传输层协议
虽然不用在传输层实现代码,但是我们要调用传输层提供的API,学习网络协议其实就是学协议的格式。
TCP
TCP的核心特点一:确认应答
TCP的可靠传输,能否做到百分百的数据达到对方呢?答案是不能的,所以发送的数据能够知道对方是否收到,就可以认为是可靠传输,所以,如果对方收到了数据证明传输成功,但如果对方没有收到,而是发生了丢包等情况,就要采取其他补救措施,第一条措施就是确认应答。
确认应答的机制就是发送方发送了数据之后,接收方接受,一旦接收到了,就给发送方返回一个应答报文,告诉发送方"我收到了"
TCP的核心特点二:超时重传
确认应答的关键是数据顺利到达,通过ack告知发送方,但是实际传输中很可能丢包,所以确认应答丢包的场景,我们采用了超时重传的方法。
超时重传中的重传,顾名思义,就是重新发送一份数据,而其中的超时是指最大等待时间的超时时间,在我们发送数据后 需要收到应答报文ack,只要超过超时时间还没有收到ack此时就直接重发。
超时重传的超时时间不是一直不变的,会随着超时重传的进行,如果还是没有收到ack,仍然要继续重传,此时等待的时间就会逐渐变长。
重传的次数也是存在上限的,如果达到了上限,重传还没有成功,TCP连接就会被重置,单方面地断开连接。重置涉及复位报文,也就是6个标志位中的RST,触发了这个报文,就意味着连接不要了。释放连接,就相当于删除掉之前保存的对方的信息。
没有接收到ack有两种情况
第一种是数据包直接丢失
这种情况使用超时重传是合理的,我们直接将数据重新发送一遍。
第二种是返回的确认应答报文ack丢失
这种情况下,接收方已经收到了一份数据,如果我们再发送一份数据,是否会引起冲突?实际上,TCP协议已经处理了上述情况。TCP在接收到数据的时候,会在操作系统内核中维护一个接收缓冲区这个接收缓冲区其实就是一个内存空间,如果在接收到了同一个数据,此时在接收缓冲区中就会根据数据的序号来进行去重操作。
TCP也会针对收到的数据在接收缓冲区中进行重新排序,因为数据传输可能会出现后发先至的情况。
TCP的核心特点三:连接管理
TCP的有连接,其中的连接是虚拟的、抽象的连接,通信双方各自保存对方的关键信息,如IP端口等。而连接其中最关键的就是连接如何建立和连接如何断开连接。
连接如何建立(三次握手)
TCP中的握手,其实是传输一个打招呼的数据包,这个数据包不携带任何的业务数据(业务数据就是没有应用层载荷,这个数据包只有报头,没有正文)。
三次握手大致过程
1)客户端给服务器发起一个syn(同步报文段)表示我要跟你建立连接
2)服务器给客户端返回一个ack(表示收到我会跟你建立连接)
3)服务器给客户端返回一个syn(表示我要跟你建立连接)
4)客户端给服务器返回一个ack(表示我会跟你建立连接)
客户端和服务器的交互明明有四次,但为何是三次握手,中间的一次返回客户端的ack和syn可以合并为一次返回。
三次握手的意义
1.三次握手相当于投石问路 验证通信链路是否通畅
2.三次握手可以视为一种保证可靠传输的手段(辅助手段)
3.三次握手也是在验证通信双方的发送能力和接收能力是否正常
4.三次握手和可靠传输是有关系的,只不过是前提条件和辅助操作。真正传输数据的时候靠的是确认应答和超时重传
5.三次握手连接中还有一个需要协商的数据,那就是起始序号,TCP连接中需要给每个载荷部分进行字节编号,在三次握手中就需要协商出序号的起始序号。
传输的第一个数据包的第一个字节的编号不是从0或者1开始编排,而是由3次握手阶段协商出这样的数字作为起始编号,每次建立连接协商出的起始序号都是不同的。这样的设定为了避免出现网络传输中的特殊情况,前一次连接中传输的数据包因为一些原因无法到达。等到这次连接结束时,还是没有送到,而是在下一次连接中到达,那么此时这个数据就应该被丢弃。
连接如何断开(四次挥手)
断开连接的四次挥手可能是客户端主动发起,也可能是服务器主动发起的,而三次挥手一定是客户端先发起的,先发起的一方定义为客户端。
四次挥手的大致过程
1)客户端首先给服务器发送一个FIN(结束报文)(我要和你断开连接 请你把我删了)
2)服务器返回客户端一个ack(表示我会和你断开连接)
3)服务器再返回一个FIN给客户端(表示我也要和你断开连接请你把我删了)
4)客户端返回服务器ack(表示收到我也会和你断开连接)
不能合并的情况
这里中间两次返回为什么不能合成一个报文,这是因为触发FIN需要进程结束或者调用close代码,而内核负责的收到FIN立即返回ack服务器中,服务器返回的FIN需要代码手动实现close,而调用close钱可能有其他的代码逻辑需要实现,这就导致了ack和fin返回是有时间差的,这就导致了不能同时返回。
可以合并的情况
也有可以合并的时候。TCP中有一种机制叫做延时应答,应答ack的时机会往后拖延一段时间,但不会特别久如果收到FIN触发的close之间间隔本身不是特别长,再加上上一个ack延时应答了,此时这个ack就可以和FIN一起返回。
TCP的核心特点四:滑动窗口
前面3个机制实现的是TCP的可靠性,而滑动窗口则是提高效率。
滑动窗口核心机制
客户端每次发送都需要等待ack,我们可以批量发送数据再等待ack这样等待的一份时间中,就是在等待四组ack的到达,此时就把批量发送多少数据不需要等待的区间称为窗口大小,在收到一个ack后 就立即往后发一组数据,每次收到一个ack窗口都会往后平移格子,这样的过程就像是"滑动"的过程。
滑动窗口的提升效率再怎么提高也不会超过UDP无可靠传输机制协议。
滑动窗口的机制下出现丢包
1.数据包已经到达ack丢了
如果只是ack丢包,在滑动窗口机制下不需要做任何处理。这是因为ack的确认序号的设定规则可以表示,该序号前的所有数据已经收到。
2.数据包直接丢了
如果中间有个数据包丢了,在下一次发送方发送数据来之后,接收方返回的ack是发送方丢失的上一次的ack1,而之后发送方再次发送数据给给接收方,接收方返回的ack就都是ack1。此时接收方就会意识到出现了丢包的情况,就会重发丢包的数据,而一旦接收方接收到了丢失的数据包,此时他就会发现在自己的接收缓冲区里已经有了之后的数据,接下来只要从最后一个接收到的数据包索要即可,这样的机制叫做"快速重传"。
滑动窗口,快速重传和确认应答,超时重传的关系
如果使用TCP传输较大量的数据的时候,自然就会使用滑动窗口,重传机制就使用快速重传。
如果使用TC传输较小量的数据时就使用确认应答,重传就使用超时重传。
TCP的核心特点五:流量控制
流量控制是搭配滑动窗口使用的。
滑动窗口的窗口大小越大,传输的速度就越快,但是也不能无限大,会对可靠性有影响,流量控制就是根据接收方的处理能力,干预发送方的发送速度(窗口大小)。
发送方发送数据给接收方,接收方接收时并不是直接调用数据,而是先把数据存在一个名叫接收缓冲区的地方。可以把接收缓冲区想象成阻塞队列,如果队列里面没有数据,应用程序就在read时就会阻塞。而所谓的接收方的处理能力就是接收方应用程序调用read的速度,在结束缓冲区后,会根据序号号进行排序,排序完后才能被接收方进行read。
那么要如何根据接收缓冲区来限制发送方的速度?这需要知道接收缓冲区的剩余空间大小,以这个指标反向制约发送方的发送速度。在接收方返回ack报文的时候,在TCP报头中把集中缓冲区剩余空间大小数值放到ack报头中,发送给发送方,等到发送方收到ack就知道接收方的处理速度了。
如果接收方的接收缓冲区已经满了,那么接收方也不会给发送方返回ack,等到接收方的接收缓冲区中有空余的位置了,发送方又怎么能知道何时有位置。当窗口大小为0时,发送方只是不发业务数据,但是会周期性的发一个名叫数据探测包的特殊的数据包,当接收方收到了数据探测包,同时接收缓冲区有空闲时,就会返回一个窗口更新包,发送方接收到之后就会得知接收缓冲区不满了,此时就会继续发送。
TCP的核心特点六:拥塞控制
拥塞控制也是用来限制滑动窗口的发送速率的
发送方的速率不仅要考虑接收方的速率,还要考虑传输路径整个过程中所有中间节点的情况,通过实验的方法来确定最终的发送速率,先用较慢的速率传输。如果没发生丢包,就增大发送速率如果丢包 就增大窗口大小 增加速度。
发送方的发送窗口大小同时取决于流量控制和拥塞控制,这两者是根据这两个因素中较小的那个因素来决定窗口的大小。
窗口大小的控制也是非常的巧妙,在初始状况下,窗口大小非常小是因为刚开始网络的通畅程度是未知的,在慢启动后,如果不丢包就会按照指数的方式增长,短时间内快速增加发送速率,当指数增长到一定程度(达到阈值)指数增长就会变成线性增长,当出现丢包时,就要重新计算阈值,从阈值开始作为新的拥塞窗口继续线性增长。
TCP的核心特点七:延时应答
为了提高传输效率,不立即返回ack, 而是稍微等一等,这是因为为了给接收方留出一些时间,好能够多读取一些数据,让接收缓冲区的剩余空间大一点。
延时应答也有最大的延时时间,超过最大延时线就应答一次,当然也不能超过超时重传的最大超时时间。
核心机制八:捎带应答
捎带应答也是提升传输效率的一种手段,在客户端服务器中 往往是一问一答的模型。
返回ack的时机是在收到请求之后立刻返回,而响应则是要根据服务器计算后返回,但是TCP会延时应答,一推迟就正好赶上服务器返回响应了,就可以直接把ack报文和响应数据做成一个TCP数据包返回客户端。
普通的响应报文,ack这一位是0,窗口大小也是无效的,此时就可以把ack填入到这里面。
捎带应答既会取决于延时应答,又和程序的处理逻辑有关。捎带应答不会百分百触发,但是TCP会尽可能这么做。
核心机制九:面向字节流
粘包问题

接收方的应用程序read的时候,就有可能有很多种情况,粘包的是应用层的数据包,TCP的字节流的特征,收到多个TCP数据包时,会把所有载荷给混到一起,放进结束缓冲区里。包的边界比较模糊,就好像粘上了一样。
解决年报问题,从应用层入手,合理地设计应用层协议,让包之间的边界能够比较清晰。我们可以通过特殊的分隔符来为包编辑格式作为包的边界。
1.可以使用特殊的分隔符来作为包的边界区分。例如使用\n。
2.在应用层数据包开头的地方,通过固定的长度约定整个应用层数据包的长度。
粘包问题只针对字节流的传输,对于文件操作(使用文件储存多个结构化数据也是可能涉及粘包的),对于UDP来说,不存在粘包问题,这是因为UDP的接收缓冲区和TCP的不太一样。
核心机制十:异常情况
1.进程崩溃
进程崩溃意味着对应的文件描述符就被关闭了,是调用close或者干掉进程。只要进程退出,都会释放PCB释放文件描述符表。但是TCP的连接不会因为进程结束而立刻结束,会保留一会儿,也会正常触发4次回手,所以进程崩溃其实是正常的流程。
2.主机关机(正常流程关机)
主机关机要看关机的速度是否快。
如果关机的速度快 那么4次挥手是可以正常完成的。
如果关机的速度慢,在主机发送完一个FIN之后就关机了,接收方收到fin并返回ack后,因为主机关机无法返回ack,此时接收方在重传了几次之后就会自动将对方的联系断开。
3.主机掉电
不同于主机关机,主机掉电是直接拔电源 来不及发起FIN。
1)掉电的一方是接收方 对方是发送方
对方继续发送数据,没有ack就超时重传,仍然没有ack就继续超时重传,等到一定程度掉电一方仍然没有ack返回,发送方就会发送一个复位报文,就是表示要重置连接,相当于要放弃当前连接。
2)掉电的一方是发送方 对方是接收方
接收方的感受是,发送方突然就停下了,接收方会继续逐次等待 等待发送方发来的新的数据。
接收方会周期性的和发送方交换心跳包,心跳包就是验证双方是否有应答的一个特殊的数据包,如果对方有应答,就可以认为对方是正常工作的。如果心跳包也没有应答,就可以认为对方挂了。
4.网线断开
网线断开实际上是和主机掉电是一样的,一个是发送方,一个是接收方。对于发送方来说,没有ack就超时重传达到一定程度,放弃连接。对于接收方来说,周期性触发心跳包,发现如果没有返回就放弃连接。
TCP格式

4位首部长度:这里的4位首部长度代表的是TCB报头的长度,但是是要乘以4字节,所以TCP报头的最大长度是60字节,其中的选项是0~40字节。
16位校验和:和UDP的校验和一样,也是验证数据传输过程中是否出错。
保留6位:UDP想升级非常费劲,就导致UDP的报文长度始终受64KB的限制,现在的保留位就是不使用,而是先占个位置,说不定以后会使用,这样就提供了非常大的扩展空间。
16位窗口大小:窗口大小是滑动窗口中的窗口区间,但是窗口大小并非是只有64 KB, 而是可以扩充的。在选项中有一个窗口扩展因子,可以进行选择。
16位紧急指针:紧急指针意味着后面有一些数据要先传输,紧急指针的值表示从当前位置往后多少个字节位置部分要进行插队。
六个标志位:
ACK:应答报文
PST:复位报文,单方面的放弃连接
SYN:同步报文
FIN:结束报文
URG:表示紧急指针有效
PSH:催促接收方尽快把缓冲区的数据交给应用程序
TCP中的状态
LISTEN:服务器存在的状态 服务器new Ser socket就会进入此状态,表示在监听这个端口,端口上过来,服务器就能直接连接了(手机开机信号良好)
ESTABLISHED:客户端和服务器连接建立好了,客户端和服务器之间就可以直接进行通信了(电话已经接通 可以说话了)
CLOSE_WAIT:被动断开连接的一方进入的状态,收到对方发来的fin的时候就会返回ack同时进入此状态。
TIME_WAIT:主动发起断开连接的一方进入的状态,我方发送FIN,对方返回ack,对方发送FIN,我方返回ack,同时进入此状态,按理说我方返回ack相当于4次挥手结束,已经断开连接了,可以释放了。但是为什么还要等?这是因为应对最后一个ack丢包的情况当发送最后一个ack后,客户端直接释放连接,但是如果最后一个ack没有送到服务器,就会触发超时重传,重新发送一个FIN,而此时客户端与服务器之间已经断开连接了,这样重传的FIN就无人处理了。
UDP
UDP格式


UDP长度:由于UDP长度就是2字节,一个UDP报头长度最大就是64kb,但是由于64KB上限扩充需要的代价太大,所以当64 KB上限到达时就会换成TCP协议来解决UDP报头不够长的问题。
CRC校验和(循环冗余校验):验证UDP数据报是否在传输中出错。
网络传输本质上是传输电信号、光信号等,而这些信号电磁波在传输过程中也会受到自然因素的影响,所以传输过程中自然也会出错,这时 我们就需要使用校验和来甄别数据是否出错。
在发送方构造UDP数据包之后,会把数据包的每个字节数据进行累加到一个16位整数上,从而得到一个累加和,等到接收方拿到数据包,接收方也会使用同样的方式来对数据包的每个字节的数据进行累加,之后将这两次累加和进行对比,看两次累加和是否相同,从而达到检验的效果。
如果出现恰好两个比特位发生翻转,导致翻转后算出来的校验和和翻转前算出来的碰巧一样,这种情况理论上会存在,但实践中概率非常小,可以忽略不计,如果对于准确性要求非常敏感的场景,当然也有其他的校验和算法,做一个更精准地甄别。
序号和确认序号:
网络中有先发后至的情况出现,就是提前发的消息比之后发的消息更慢到达对方,从而导致了信息的错乱。
而为了解决这种情况,我们引入了序号和确认序号,所谓的序号和确认序号,就是将数据进行标记,让数据都有属于自己的序号,从而区分每一条数据,而TCP是字节流,传输序号和确认序号都是针对字节进行排序的,针对每一个字节进行递增的排序,所以只需要知道TCP的第一个字节的编号,后面的每个编号就都能知道。
在同一个TCP连接内序号会持续累加,下一个数据包的序号,是在上一个数据包最后的一个字节序号基础上继续递增。
确认序号只在应答报文中生效,而所谓的应答报文,就是报头中那6个标志位中的ack为1就是应答报文,否则就是无效字段。
如何使用UDP实现可靠传输
我们可以参考TCP来修改应用层,例如确认应答、超时重传、滑动窗口、流量控制、拥塞控制等。