开发的几种格式,TCP的十个重要机制

发布于:2025-06-03 ⋅ 阅读:(27) ⋅ 点赞:(0)

自定义协议中,

我们有几种常见的数据格式:
1.xml

通过标签来组织数据

请求:

优势:

让数据的可读性变更好了

劣势:

标签非常繁琐,传输的时候也占用更多网络带宽(maven会使用xml来管理项目配置)

HTML是xml的变种

2.json(最流行的数据格式)

通过{}把所有的键值对包裹起来,

键值对之间用,分割;键和值之间用:分割

键固定是String类型,有时候大多可以把key的引号省略

值可以是数字,字符串,json,数组。。。

优势:

可读性比较好,比xml更简洁

劣势:

在网络传输中,会消耗额外的带宽(key也需要传输)

3.protobuffer

简称pb,使用二进制的方式组织数据,可以保证带宽占用最低(相当于把传递的信息按照二进制形式压缩)

优势:

占用带宽最低,传输效率最高,非常适合对于性能要求比较高的场景。

劣势:

可读性不好(二进制),影响开发效率(比传输效率更重要)。

应用层也有一些现成的应用层协议,最广泛使用的就是HTTP协议(超文本传输协议)。

超文本不仅仅是文本,还有图片,视频,音频,字体

传输层的重点协议


1.UDP协议:

协议报文格式

UDP报文长度:

能否对UDP升级?把2个字节变成4个字节?
不可,升级得通信双方一起升级(升级操作系统内核,UDP是在内核中实现的),这样的话全世界的设备都得升级。

UDP、TCP这样的报文格式是谁规定?

一些大佬规定的,约定出来的内容被整理出一份标准文档,称为RFC标准文档。

三流公司做产品,一流公司做标准。

64KB-8和64kb相差不大忽略不计。

校验和:

网络传输中,由于外部干扰,可能会出现数据传输出错的情况。

于是需要通过校验和这样的检查手段,来识别出出错的数据。

检验和本质上是一个字符串,体积比原始的数据更小,是通过原始数据生成的。

原始数据相同,得到的校验和就一定相同;

检验和相同,原始数据大概率相同。(存在不同的情况,不过可忽略)

如何基于校验和来完成数据校验呢?---》发现数据传输是否出错

1.发送方,把要发送的数据整理好(data1),通过一定的算法,计算出校验和checksum1

2.发送方把data1和checksum1一起通过网络发送出去

3.接收方收到数据,收到的数据称为data2(可能和data1不一样),以及接收到checksum1

4.接收方根据data2,按照相同的算法,重新计算校验和,得到校验和checksum2

5.对比checksum1和checksum2是否相同

如果不同,则认为data1和data2一定不相同;

如果相同,则认为data1和data2大概率相同。

计算校验和的算法:

1.CRC算法(循环冗余算法)

把当前要计算校验和的数据,每个字节都进行累加,把结果保存到一个  两个字节的变量中。

累加过程中如果溢出,没关系

如果中间某个数据出现传输错误,第二次的校验和就会与第一次的校验和不同

这个算法不是很靠谱:

两个不同的数据,得到相同的crs校验和的概率比较大。

比如 前一个字节恰好少1,后一个字节恰好多1(出现这种情况的概率也不大)

2.md5 算法

特点:

1.定长:无论原始的数据有多长,通过公式计算的md5,都是固定长度。

2.分散:给定的两个原始数据,只要有一个字节不同,得到的md5就会差异很大。

   所以md5也非常适合hash算法:

哈希表是要把一个key通过hash函数,转成数组下标。

希望hash函数能做到尽量分散,产生hash冲突的概率会降低。

3.不可逆

把原始数据计算md5可以,但是把md5还原出原始数据艰难不可行。

UDP的特点:通过代码

1.无连接,UDP协议本身不会存储对端的信息。在要发送数据的时候,显示指定要传输给谁。

2.不可靠

3.面向数据报

4.全双工

通过一个socket,可以send,也可以receive

基于UDP的应用层协议:优先考虑TCP


NFS:网络文件系统

TFTP:简单文件传输协议

DHCP:动态主机配置协议

BOOTP:启动协议

DNS:域名解析协议

自己的写udp程序时自定义的应用层协议。

2.TCP协议:

数据报 = 首部(报头header)+ 载荷

16位源端口号和16位目的端口    与  UDP相同

4位首部长度: 

TCP报头长度不固定

报头最短,是20字节(没有选项)

报头最长,是60字节(选项最多是40字节)

选项可有可无,4个字节为一个单位

保留位:方便以后扩展报头长度

检验和:和udp一样

TCP的相关特性:

1.有连接

2.可靠传输(TCP最核心的特性,初心)

    发送方不一定100%能将数据传输给接收方

    1)发送方发出去数据之后,能够知道接收方是否收到数据

    2)一旦对方没有接收到,可以通过一系列手段来补救

3.面向字节流

4.全双工(可写又可读)

TCP的原理:

1.确认应答:

数据传输的过程中可能会出现”后发先至“的情况:

TCP在此处要完成的工作:

1.确保应答报文和发出去的数据,能对上号,不会出现歧义

2.确保在出现后发先至的情况时,能够让应用程序按照正确的顺序来理解数据

解决:

序号是按照字节来编号的,TCP是面向字节流的。

TCP的初心是实现可靠传输------》可靠传输最核心的机制,就是确认应答

如何区分一个数据包是普通的数据,还是ack应答数据?

面试题:

TCP如何保证可靠传输?
:通过确认应答为核心,借助其它机制辅助,最终完成可靠传输。

2.超时重传

确认应答是以一种理想的情况,如果网络传输的过程中出现丢包,发送方无法收到ack。

于是使用超时重传的机制,针对确认应答,进行补充。

为什么会出现丢包?

比如高速公路(网络)

平时车流量不大时,收费站快速通过,很少堵车;

节假日,会经常堵车。

“收费站”就像是路由器/交换机,如果数据包太多,路由器/交换机就会“堵车”。

路由器面对堵车,不会把这些数据包保存,而是把其中大部分数据包直接丢掉。

真实网络环境中,不知道什么时候,不知道哪个节点,会丢什么包。

丢包是一个“随机”的事件,在tcp传输过程中,丢包存在两种情况:

1.传输的数据丢了

2.返回的ack丢了

出现上述两种情况,发送方都会进行“超时重传”,

比如丢包概率为10%,第一次丢了,再重传一次,那么此时丢包概率就为1%,大幅度提升数据能被成功传过去的概率。

为何UDP不会被TCP完全替代?

因为为了可靠性会付出代价:

1.传输效率的降低

2.复杂程度的加深

有些需要性能的场景下,UDP更好

在TCP和UDP协议之间,还有其它能够在可靠性和性能之间做到“权衡”,quic,kcp

那么发送方何时进行重传呢?

:看等待的时间

发送方在发送数据之后,会等待一段时间。如果在这个时间之内,ack来了,就不需要重传。

如果时间到了都还没来,需要重传。

1.初始的等待时间是可以配置的。不同系统不一样,也可以通过一些内核参数来引起时间变化

2.等待的时间会动态变化,每多经历一次超时,等待时间会变长。

:A-》B发送数据,第一次A等待ack时间为50ms,

假设A需要重传,重传之后,还是没有收到ack。

那么第二次等待的时间就会比第一次更长(不会无限重传,到一定时间就会触发tcp重置连接操作)

当我们站在B的视角,如果收到两条的一样的数据时,是否会产生bug?

比如:发的是一条数据,收到两条数据,问题会很大。

          付款的时候,一开始刷二维码,还没完成付款,然后又刷了一次,这样就相当于付了两次钱。

解决方案:

TCP会有一个“接收缓冲区”(一个内存空间),会保存当前已经收到的数据以及数据的序号。

接收方如果发现,当前发送方发来的数据,是已经在接收缓冲区存在的(收到过的重复数据),接收方就会把这个后来的数据给丢弃掉,确保应用程序read的时候,读到的只有一条数据。

这个缓冲区,不仅能做到去重,还能进行重新排序。确保发送的顺序和应用程序读取的顺序是一致的。

3.连接管理:建立连接(三次握手)和断开连接(四次挥手)

这里的握手,给对方传输一个简短的,没有业务数据的数据包。

通过这个数据包,来唤起对方的注意。

计算机其它操作也涉及到“握手”:

1.手机充电:不同的充电头,通过“握手”来判断是 普通充电/快速充电.

2.u盘:usb口支持3.0/2.0(速度),把3.0的u盘插入,通过”握手“,就用3.0的速度通信。

            不过把3.0的u盘缓慢插入,可能会“握手”成2.0(当我们插入一半的时候就会被握手成2.0,全部插入就会识别成3.0)

TCP的三次握手:建立连接

一般是客户端主动发起

TCP在建立连接的过程中,需要通信双方一共打“三次招呼”才能完成建立连接。

三次握手的作用:

1.确认当前网络是否是通畅的

:地铁每天都会跑一趟空车来检验是否顺畅

2.让通信双方确认自己的发送和接受能力是否正常

3.让通信双方在握手的过程中,针对一些重要的参数进行协商

比如:tcp通信过程中的序号从几开始,就是双方协商出来的(一般不是从1开始的)

          每次连接建立的时候,都会协商出一个比较大的,和上次不一样的值。

         有的时候,网络不太好,客户端和服务器之间就会连接断开,需要重新建立连接。

          重连时,旧连接的数据得丢掉,不能影响到新连接的业务逻辑。

          如何区分数据是旧的还是新建立的?------》序号的设定规则

          如果发现收到的数据序号和当前正常数据的序号差异很大,就是旧的。

TCP的四次挥手:断开连接

客户端和服务器都可以主动发起

4.滑动窗口

前面三个都是保证TCP的可靠性,那么势必会使效率降低。

于是用滑动窗口来缩短确认应答的等待时间,来让效率降低的少一点

如果通信双方,传输数据的量比较少,不频繁,就是普通的确认应答和普通的超时重传

如果通信双方,传输数据的量比较大,比较频繁,就会进入滑动窗口模式,按照快速重传。

滑动窗口模式下的丢包

1.ack丢了

2.数据包丢了

采用快速重传(超时重传)

会存在ack全丢的情况吗?

全丢就说明网线都断了,没有可靠传输。

滑动窗口越大越好吗?

不是,传输的速度太快,接收方并不一定能接受的过来,到时候出现丢包还得重传。

TCP主打可靠,在提高效率。

5.流量控制

接收方反向制约发送方的传输效率

6.拥塞控制:

流量控制考虑的是接收方的处理能力

拥塞控制考虑的是通信过程中中间节点的情况

由于中间节点结构复杂,很难直接进行量化,所以采用“实验”的方式,来找到合适的值。

让A先按照比较低的速度发送数据(小的窗口)

如果数据传输的过程非常顺利,没有丢包,在尝试更高的速度,更快的速度进行发送。

随着窗口大小不断地增大,达到一定程度,可能中间节点就会出现问题,那这个节点就可能会出现丢包。

发送方发现丢包,就会把窗口大小调整。

如果发现还是继续丢包,继续缩小;不丢包了,就继续放大。

发送方不断调整窗口大小,逐渐达成“动态平衡”

就跟谈恋爱一样

后续对上面的进行了改进

触发丢包,不让速度一下归零。

流量控制和拥塞控制都在限制发送窗口的大小,最终时机发送的窗口大小,是取流量控制和拥塞控制中的窗口的较小值。

7.延时应答

正常情况下,A把数据传给B,B就会立即返回ack给A(正常)

延时应答,A传输给B,B等一会儿再返回ack给A

延时返回ack,给接收方更多的时间,来读取接收缓冲区的数据

接收方读了数据之后,缓冲区剩余空间,就变大,返回的窗口大小也就更大了。

 eg:

初始情况下,接收缓冲区剩余空间是10kb,如果立即返回ack,返回了10kb的窗口;

如果延时200ms再返回,这200ms过程中,接收方的应用程序又读了2kb,那么此时返回ack,返回了12kb的窗口。   

本质上是提升传输效率,发送方的窗口大小是传输效率的关键。

窗口大小得根据接收方的处理能力来设定。

8.捎带应答

在延时应答的基础上,进一步提高效率。

9.面向字节流

粘(nian二声)包问题(不是TCP独有的,而是面向字节流的机制都有类似的情况)

包:指的是应用层数据包。、

如果有多个应用层数据包被传输过去,就容易出现粘包问题。

相比之下,像UDP这样的面向数据报的通信方式,没有上述的问题。

UDP的接收缓冲区中,相当于是一个个DatagramPacket对象。

应用程序读取的时候,明确知道从哪里到哪里是一个完整的应用层数据包。

解决粘包问题:

核心思路:通过定义好应用层协议,明确应用层数据包之间的边界。

1.引入分隔符

2.引入长度

eg:

1.以 \n 作为分隔符

2.

自定义应用层协议的格式

xml,json,protobuffer,本身都明确了包的边界。

10. 异常的处理 

在使用tcp的过程中,出现意外,如何处理?

1)进程崩溃

进程没了,异常终止,文件描述符表释放了,相当于调用了socket.close();

此时就会触发FIN,对方收到之后,自然就会返回FIN和ACK,发送方再返回ack(正常的四次挥手断开连接)

TCP的连接,独立于进程存在。(进程没了,TCP连接不一定没)

2)主机关机(正常流程)

     在进行关机的时候,会先触发强制终止的操作(异常1)

     就会触发FIN,对方收到之后,自然就会返回FIN和ACK。

这里不仅仅是进程没了,系统也可能关闭了。

如果在系统关闭之前,对端返回的ACK和FIN到了,系统还是会正常返回ACK,进行正常的四次挥手

如果系统已经关闭,ACK和FIN迟到了,无法进行后续ACK的响应。

站在对端的角度,对端以为自己的FIN丢包,于是就会重传FIN,重传了多次都无响应,就会放弃连接(把持有的对端的信息删掉)

3)主机掉电(非正常)

     这是一瞬间的事,来不及杀进程,来不及发送FIN,主机直接就停机。

     站在对端的角度:

     1.如果对端是在发送数据(接收方掉电),发送的数据就会一直等待ack。

      触发超时重传,触发TCP连接重置功能,发起“复位报文段”。

      如果复位报文段发过去之后,也没有效果,就会释放连接。

     2.如果对端是在接收数据(发送方掉电),对端还在等待数据到达。等了半天没消息,无法区分是对端没发消息,还是对方挂了。

     TCP提供了心跳包的机制

     接收方会周期性的给发送方发起一个特殊的,不携带业务数据的数据包,并且期望对方返回一个应答。

    如果对方没有应答,并且重复多次之后,仍然没有,就视为对方挂了,就可以单方面释放连接。

4)网线断开

     

TCP心跳机制,在日常开发中,经常见到这种心跳机制(尤其是在分布式系统)

如何识别某个机器是否是挂了?一般通过心跳来检测。

一般后续用到的心跳(毫秒级,秒级就能检测出),都是在引用程序中自动实现的,不直接使用tcp心跳机制(周期太长)

TCP和UDP之间的对比

TCP优势 可靠传输   适用于绝大部分场景

UDP优势 更高效率  适用于“可靠性不敏感”,“性能敏感”场景

“可靠性不敏感”,“性能敏感”:

局域网内部(同一个机房) 的主机之间通信

同一个机房内部,网络结构简单,带宽充足,交换机/路由器网络设备负载程度不是很高,出现丢包的概率就不大,往往希望机器之间传输数据更快。

如果要传输比较大数据包,TCP更有先(UDP有64kb限制)

如果要进行“广播传输”,优先考虑UDP。UDP天然支持广播,TCP不支持(应用程序额外写代码实现)

广播:有一种特殊场景,需要把数据发给局域网的所有的机器。

投屏功能,手机和电视得在同一个局域网下,手机这边触发投屏的功能,就会往局域网发起广播数据包,询问一下:你们谁是电视?谁能够接收投屏?

电视就会回应:我是电视,并且把自己的ip和端口告诉了手机。

手机后续就可以完成投屏

面试题:

如何基于UDP实现可靠传输?

:考察TCP的可靠传输

网络层

做的事情,主要是两方面:

1)地址管理:制定一系列规则,通过地址,描述出网络上一个设备的位置

2)路由选择:网络环境比较复杂,从一个节点到另一个节点之间,存在很多条不同的路径,就需要通过这种方式,筛选/规划出更合适的路径进行数据传输。

IP协议:

协议头格式如下:

地址管理:

IP地址,是一个32位的整数。

2 ^ 32 => 42亿9千万

理论上来讲,地址是不应该重复的。

如何解决IP地址不够用的问题?

方案一:动态分配IP

治标不治本,提高了IP地址的利用率,并没有增加IP地址的数目(广泛使用)

方案二:NAT机制(网络地址转换)

本质上,让一个地址,代表一批设备。(北京大学的学生网购,写的地址都是北京大学,同一个地址)

把地址分为两大类:

1)内网IP(局域网IP)

如果一个IP地址,是以10.*或者172.16.*-172.31.*或者192.168.*(符合上述条件之一,IP就是内网IP)

在同一个局域网内部,内网IP之间,不能重复。

在不同的局域网中,内网IP之间,可以重复。

2)外网IP(广域网IP)

剩下的IP都是外网IP(始终都不允许重复,唯一)

NAT机制具体是如何工作的?

如果当前局域网内,有多个主机,都访问同一个网站服务器

NAT机制(纯软件方案)

1.局域网内部的设备,能够主动访问外网的设备;

外网的设备无法主动访问局域网内部的设备.(UDP echo server必须部署到云服务器上才能执行)

保护了电脑安全

2.局域网同一时刻访问同一个服务器,超过了65535,NAT可能不够用,但不同时刻访问不同服务器就没事

IPv6

IPv6使用16个字节来表示IP地址

IPv6比IPv4多多少?

IPv6未默认开启,IP仍以IPv4为主

网段划分

把一个IP地址,分成了两个部分:

网络号(标识了一个局域网)+ 主机号(标识了局域网中的一个设备)

如果一个IP地址,主机号全0,当前这个IP就表示“网络号”

代表一个局域网的,不能给一个具体的主机分配这个IP。

192.168.100.0

255.255.255.0

如果一个IP地址,主机号全1,当前这个IP就表示一个“广播地址”

也不能直接给具体的主机分配这个IP

192.168.100.255

255.255.255.0

UDP天然能支持广播,就是和这个IP有关系。

使用UDP socket给这个地址发送UDP数据报,此时局域网中所有的设备都能接收到这个数据报。

TCP则无法和这个地址建立连接。

如果一个IP地址是以127开头,此时这个IP就是“环回ip”(loopback)

127.0.0.1(最常用的)表示“设备自身”,自己发给自己

操作系统提供了一个特殊的“虚拟网卡”,关联到这个IP上。

环回IP主要的用途就是进行一些测试性的工作,能够排除网络不通干扰因素,更好的排除代码中的问题。

路由选择:描述了IP协议(IP数据报)转发过程

地图知道全局信息,可以给出“最优解”

但IP数据报转发的时候,每个路由器都无法知道网络的全貌,只知道一些局部信息(一个路由器知道哪些设备和自己相连),很难给出”最优解“。

一个网络层的数据报,每次到达一个路由器,都会进行"问路"。

每个路由器内部都有一个数据结构”路由表“,根据数据报中的目的IP去查询路由表

如果查到了,就直接按照路由表给定的方向(从哪个网络接口进行转发)继续转发;

如果没查到,路由表里有一个”默认的表项“(下一跳的地址),按照默认的表项转发即可。

数据链路层

有很多种协议,比较常见的就是"以太网协议"

通过网线/光纤来通信,使用以太网协议

以太网横跨数据链路层+物理层


以太网数据帧格式

:帧头+载荷(IP数据报)+帧尾

IP地址和Mac地址各自的用途?

IP协议立足于全局,完成整个通信过程的路径规划工作;

以太网则是关注于局部,相邻两个设备之间的通信过程。

像交换机这样的设备,收到以太网数据帧的时候,就需要进行转发。

(这个转发过程就需要根据mac地址,判定出数据走哪个网口)

这里的网口是指物理意义上插网线的口

(IP协议,路由器,走哪个网络接口,是抽象的,最终还是要在数据链路层决定走哪个网口)

具体如何转发?

交换机内部有一个数据结构  ”转发表“

转发表是一个像hash这样的映射

转发表如何构造(里面的内容怎么来的)主要就是通过arp协议来生成

应用层:

域名解析系统(DNS): 应用层协议&一套系统

使用IP地址,来描述设备在网络上的位置。

IP地址不适合进行宣传,引入域名的方式来解决。

eg:www.baidu.com(需要有一套自动的系统,把域名翻译成IP地址,想象域名和IP是一组键值对)

把域名和IP的映射关系保存到DNS系统中(一组服务器)

如果想访问某个域名,就先给DNS服务器发起请求,查询一下当前域名对应的ip,然后再访问目标网站

(后续如果有域名的更新,只需要更新这一组指定的服务器即可,不需要修改每个用户电脑的hosts)

很多设备都要进行DNS请求。这一组DNS服务器,能否扛得住高的请求量?
(一个服务器硬件资源是有限的(cpu,内存,硬盘,网络带宽....)服务器处理每个请求,都需要消耗一定的资源。单位时间内,请求太多,消耗的总资源超过了机器本身的资源上限,机器就挂了)

处理高并发的问题:

1.开源

搭建DNS系统的大佬,号召各个网络运营商,他们自己可以搭建一组(很多组)”DNS镜像服务器“,镜像服务器的数据,都从源DNS系统那里同步。此时用户就会优先访问离自己最近的镜像服务器

2.节流

让请求量变少,让每个上网的设备搞本地缓存

比如我的电脑1min之内要访问10次www.baidu.com

只是第一次请求DNS即可,把请求的结果保存到本地,后面9次请求都使用第一次结果即可。

   


网站公告

今日签到

点亮在社区的每一天
去签到