IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 网络原理之UDP和TCP协议详细解释 -> 正文阅读

[网络协议]网络原理之UDP和TCP协议详细解释

TCP/IP五层模型栈

应用层

应用层是程序猿最经常打交道的一层,其他四层都是操作系统,驱动,硬件实现好的,不需要程序猿管(除非系统工程师,驱动开发工程师…)

在应用层中,最重要的就是"设计并实现一个应用层协议"

设计一个应用层协议,主要包含两个工作

  1. 明确传输的信息,

  2. 明确数据的组织格式

举个栗子:

如果公司在开发一个项目,关于点外卖的软件,其中有个功能是这样的:

当前要开发的功能,叫做获取用户的订单历史,(在数据库里,服务器中来拿)这样的功能需要涉及到前端(客户端)和后端(服务器)彼此之间的交互

前端和后端是通过网络来进行交互的,在这个交互的构成中就需要约定好,前端发什么样的数据,后端回应什么样的

image-20220412133308891

形如这样的工作,就是在设计一个应用层协议,上图的这部分工作就是在规划请求和响应之间要传递的信息.

目前为止,我们约定了相互传的数据,但仍然是不够的,我们还需要一个具体的格式

但是由于应用层协议是可以随心所欲的来指定的,所以就会导致两极分化的情况非常严重 (大佬设计的非常好,菜鸟设计的协议就非常糟糕)对于次,大佬发明了一些比较好的协议的模板,可以让我们直接往上套.

当下比较流行的协议模板(数据的组织格式)]

  1. xml
  2. json
  3. protobuffer

xml

xml是属于一种比较老牌的数据格式了,虽然仍在使用,但是使用更多越来越少了

xml的格式是由标价构成的

<标签名>    内容    </标签名>
开始标签  要表示的值  结束便签

举个栗子:

发送请求的协议模板如下:

<request>
    <userId>1234</userId>
    <startTime>2022-04-01</startTime>
    <finishTime>2022-04-12</finishTime>
    <count>10</count>
</request>

可以将这看做k-v模型

标签名就是key

便签值就是value

通过这些便签,就更好的体现了这个数据的可读性,尤其是那部分是什么意思,一目了然.

虽然xml提高了可读性,但是引入了太多的辅助信息(标签名) ,对于一个服务器程序来说,最贵的硬件资源就是网络带宽,对于xml来说,因为要表示这些辅助信息,就会导致传输相同数目的请求的时候,占用的网络带宽是更高的;如果带宽固定,相同时间能传输的请求个数就是更少的.

xml模板的可读性好,但是运行效率不高

xml现在很少会作为应用层协议的设计模板,现在使用xml作为一些配置文件

json模板

json是当下最流行的一种设计应用层协议的数据格式

格式:

{
	键:值,
	键:值,
	.......
}

通过一对大括号构成了键值对结构.

一个大括号中有很多的键值对,

键值对之间使用逗号分割,

键和值之间使用冒号分割,

要求键必须是字符串类型

值允许为很多种类型(数字,字符串,布尔,数组,另一个json…)

举个栗子:

{
	userId:1234,
	startTime:'2022-04-01',
	finishTime:'2022-04-12',
	count:10
}

json中表示字符串使用单引号或者双引号都是可以的(类似于SQL)最后一对个键值对,后面可以加逗号,也可以不加逗号(标准是没有的,但是一般的json解析器不会在意这个细节)

json要求key一定是字符串,因此key这里的引号可以省略,除非key包含了一些特殊符号,(比如空格或者-…)必须要加上引号

json模型对比于xml,json他同样保证了可读性,同时又没有xml那样的繁琐,占用的宽带要更少一些

虽然json的传输速率比xml要高,但是仍然要多传递一些冗余信息,这一点在表示数组的时候,尤为明显

举个栗子:

用json表示响应:

{
	ok:true,
	reason:"",
	data:[
		{
			name:'蛋炒饭',
			price:12,
			count:1,
			totalPrice:12
		},
		{
			name:'炒面',
			price:14,
			count:2,
			totalPrice:24
		}
	]	
}

当表示复杂的数据,比如数组的时候,此时的很多key就会重复出现N次,也就占用了更多的额外带宽

至此,protobuffer诞生

protobuffer

protobuffer 是一个二进制格式的数据,在protobuffer的数据中,不包含上述的key的名字了,而是通过顺序和一些特殊符号来区分每个字段的含义,同时传一个IDL文件来描述这个数据格式(每部分的意思),IDL只是起到一个辅助开发的效果,并不会真正的进行传输,传输的仅仅是二进制的纯粹的数据

简化版本如下:

蛋炒饭\212\21\212\3炒面\214\22\224

通过二进制的数据重新对这里的内容进行编排,甚至可能还会进行一些数据压缩

这样虽然传输效率会更高,但是也会让这个数据肉眼难以观察,调用起来不方便

综上所述:json的应用范围要比protobuffer更广

总结:

应用层协议要做的工作

  1. 明确传输的数据(根据需求)
  2. 明确传输的格式(参考模板json,xml,protobuffer

传输层

传输层是操作系统内核实现,程序猿不需要直接和传输层打交道,但是传输层对我们来说仍然意义重大!进行网络编程都要用到socket,一旦调用了socket代码就进入到传输层的范畴

端口号

端口号用于区分一台主机中接收到的数据报应该转交给哪一个进程进行处理。

端口号取值于 0 - 65525之间的整数

知名端口号:把0 - 1024 这些端口号,给划分出了一些具体的作用

比如:

80端口 : http服务器

443端口 : https服务器

22 ssh

23 ftp

传输层中的协议有很多,最常见的就是UDP和TCP

UDP

UDP的报文格式:

image-20220412144826295

代码中写的端口号,就会被打包成这样的UDP数据报中(在报头中体现)

UDP客户端为例

image-20220412145318997

此时的源端口就是操作系统自动分配的端口号

目的端口就是服务器端口

报文长度

数据报长度表示整个数据报(UDP首部+UDP数据)的最大长度

此处的报文长度是2个字节,范围是0 - 65525 ,0-64k

这也就让UDP使用中存在一个非常致命的缺陷,无法表达一个比较大的数据报,

校验和

用来验证网络传输的这个数据是否是正确的.

网络中传递的数据的本质是电信号和光信号,但是如果有一些外界干扰磁场之类的,就可能导致原有的一些传递出去的数据发生了改变,校验和就可以帮助我们发现数据中的错误

TCP

image-20220412150755428

  • 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;

  • 32位序号/32位确认号:后面详细讲;

  • 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60

  • 6位标志位:

    • URG:紧急指针是否有效
    • ACK:确认号是否有效
    • PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
    • RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
    • SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
    • FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
  • 16位窗口大小:后面再说

  • 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不

  • 光包含TCP首部,也包含TCP数据部分。

  • 16位紧急指针:标识哪部分数据是紧急数据;

  • 40字节头部选项:暂时忽略;

TCP中的一些核心机制

有连接

面向字节流

全双工

可靠连接

上面的四个机制中,前三个可以在代码中体现出来,但是"可靠传输"虽然不能在代码中体现,但是这个确实TCP中最核心的机制,引入TCP的关键原因就是为了保证可靠传输.

因此TCP中的很多机制都是围绕着可靠传输来展开的.

一、 确认应答

可靠性:发送方发出去的数据之后,能够知道对方有没有收到

关键在于接收方收到消息之后,给发送方返回一个应答报文(ACK,acknowledge),表示自己已经收到了

举个栗子:
同学A有一道数学题不会做了,所以他发短信给同学B,想让同学B帮忙解答一下

image-20220414135433998

如果同学B没有回复,那么我就不知道自己的消息有没有发出去,可能没有发出去就丢包了,只有B回复同学A的答案我才知道同学B看到了同学A的消息.

因为网络环境很赋值,可能消息传输着就消失不见了.

当下的确认应答有一些小问题,如果同学A给同学B发了多道题呢?

image-20220414140107222

上图的情况应该是正常情况,但是网络中的环境很复杂的,可能会发生后发先至的情况

image-20220414140244031

答案4明明时候发的但是却先到达,此时A就会收到错误顺序的答案.

后发先至的情况在网络中是很常见的情况,忘喽环境十分复杂,连续发的两个包,不一定走的是同一条路.

为了解决上述的后发先至的问题,很简单的办法就是对消息进行编号.

image-20220414141113488

确认序号: 表示当前这个应答报文针对哪个消息进行的确认应答

序号和确认序号在TCP报文头中也有体现

image-20220414141639323

TCP针对消息的序号,并不是按照"消息的条数"进行编号的,而是按照字节来进行编号的

image-20220414141808603

此时第一个字节编号为序号1,第二个字节编号为序号2,以此类推

确认序号也不是上面举例所讲的那样

image-20220414142012697

现在主机A向主机B发送了1000个字节,序号为1 - 1000,主机B给A返回的应答报文(ACK)会带有确认序号1001,缺人序号的意思就是说:小于1001的数据已经被主机B收到了,接下来主机A应该从1001这个序号开始往后进行传递

发送的数据(1-1000)意思就是TCP报头中的序号为1,报文长度为1000.通过这个信息来明确的范围

1-1000这是同一个TCP数据报,这一个TCP数据报通过层层封装变成了一个以太网数据帧进行传输

如果多个数据报,分装成了多个以太网数据帧,多个数据帧之间会出现先发后至

二、超时重传

相当于对确认应答进行了补充.确认应答是网络一切正常的时候,通过ACK通知发送方我收到了,如果出现了丢包的情况,超时重传的机制就起到效果了.

image-20220414144051084

此时发送方发出信息,等待ACK。情况一对方没有收到消息,发出消息在传输到一半丢失了,接收方不可能发送ACK,发送方就必不可能收到ACK.情况二 接受方收到了信息,但是发送ACK的时候,却丢失了ACK,发送方也收不到ACK.

对于发送方来说,无法确定是哪种原因导致没有收到ACK,那就往坏的想:发送方就认为对方压根就没有收到信息,那么就重新发送消息。

此时的重发也不是立即就重发,等一会,如果超过了等待时间,还没有收到ACK,就后重发一次

image-20220414144841435

image-20220414144913461

对于第二种情况,如果ACK丢失了,触发了超时重传,就会导致接收方收到重复的消息。

TCP内部就有一个去重操作:接受方收到的数据会先放在操作系统内核的接收缓冲区,接收缓冲区可以视为是一个内存空间,并且也可以视为是一个阻塞队列,TCP就会根据序号,来查看这个数据是不是在接收缓冲区已经存在了,如果不存在,就放进去,如果存在,直接丢弃。保证应用程序调用socket api拿到的这个数据一定不是重复的。

超时重传,重传的数据一定能成功吗?肯定不是100%

如果只是因为网络抖动了一下,这个时候重传还是很容易成功的,如果是网络遭受了严重的伤害,肯恶搞就没那么容易恢复,重传也成功不了。

image-20220414150503136

重传如果失败,可能还会再尝试,但不会无休止的重传,连续几次的重传不成功,就认为这个网络可能遇到了严重的情况,此时再怎么重传都是不行的,就只能放弃(自动断开TCP的连接)

重传的时间间隔也不是固定的,一般来说是逐渐变大的(重传的频率会逐渐降低)

一次传输失败的概率本身是很低的,连续两次传输都失败,概率更是小上加小,这个时候TCP就不太指望可以重传成功了。

以上两个机制,TCP的可靠性得到了有效的保障。

三、连续管理(重点)

1)如何建立连接 (三次握手)

客户端和服务其之间,通过三次交互,完成建立连接到过程。“握手是形象的比喻”

image-20220414151701247

客户端是主动发起连接请求的一端,客户端先发送一个SYN同步报文段,给服务器

image-20220414152115949

image-20220414152241700

建立连接就如同谈恋爱一样,是一个双向奔赴的过程,客户端想和服务器建立连接,就发送了SYN,服务其接收后,回应客户端就发送了ACK,同时服务器也想和客户端建立连接,就也发送了SYN,客户端收到后,就返回ACK,然后客户端和服务其就建立了连接。

虽然此时是三次交互,但是中间的两次可以合二为一。每次的传输的数据都要经过封装和分用,才能完成传输,封装两次不如封装一次更高效。这就好比淘宝店铺买两件衣服,商家肯定会打一个包裹给我。

image-20220414152942557

此处的CLOSED、SYN——SENT其实就是TCP的状态。

TCP有很多状态(比多线程多很多)

LISTEN:表示服务器启动成功,端口绑定成功,随时可以有客户端来建立连接(手机开机,信号良好,随时可以给我打电话)

ESTABUSHED: 表示客户端已经连接成功,随时可以通信。

2)如何断开连接(四次挥手)

三次握手,就让客户端和服务器之间建立好了连接.

建立好连接之后,操作系统内核中,就需要是用一定的数据结构来保存连接相关的信息,保存的信息其实最重要的就是五元组(源ip,源端口,目的ip,目的端口,tcp),保存的信息需要占用系统资源(内存)

如果有一天,连接断开了,那么之前保存的连接信息就没意义了,对应的空间也就释放了

image-20220415150007893

三次握手,一定是客户端主动发起的

四次挥手,可能是客户端主动发起的,也可能是服务器发起的.

三次握手,中间两次能合并

四次挥手,中间两次不能合并

不能合并的原因:B发送ACK和B发送FIN的时机是不同的

四次挥手中,B给A发的ACK是内核负责的,B给A发的FIN是用户代码负责的(B的代码调用了socket.close()方法,触发了FIN)

如果两个操作之间的时间差比较大,是不可以合并的,但是如果两个操作之间的时间差比较小,是可以合并的(延时应答和捎带应答)

状态转换的详细情况

image-20220415151337671

重要的两个状态:

CLOSE_WAIT:四次挥手挥了两次之后出现的状态,这个状态就是在等待代码调用socket.close()方法来进行后来的挥手操作

TIME_WAIT:谁主动发起FIN,谁就进入TIME_WAIT,起到的效果就是给最后一次ACK提供机会.

如果,A最后发出ACK之后,可能会出现丢包的情况,这时候,B就会从新发送FIN给A;如果A在规定时间内没收到B的FIN,就直接销毁.

四、滑动窗口

image-20220415155018161

由于确认应答机制的存在,导致每次执行一次发送操作,就要等待上个ACK的到达,大量的时间都是在等待ACK

使用滑动窗口就可以提高效率

image-20220415155258484

一次发4组数据,在发送4组数据的过程不等待,4组数据发送后,统一进行等待.一次等待时间,等待多份ACK,就把等待多份ACK的时间压缩到了一份了.

如果一次批量发送数据为N,统一等待一波,此时这里的N称为"窗口大小"

"滑动"的意思就是,并不用把N组ACK全部等到了才继续往下发送,而是收到一个ACK,就继续往下发送一组

当前是在等待1001,2002,3001,4001四组ACK,不需要等待4001才继续往下发,只要1001到了,就继续往下多发一组(4001-5000),此时等待ACK的范围就是2001,3001,4001,5001,如果2001到了.就继续往下多发一组(5001-6000),此时ACK的范围就变为3001,4001,5001,6001.

窗口大小越大,认为传输速度就越快,窗口大了,同一时间等待的ACK就更多了

在滑动窗口中,出现丢包情况

ACK丢了

image-20220415160820384

此时虽然ACK丢失了,但是数据却实实在在的传输到了主机B,在发送4001-5000之前,收到了2001的ACK,表示的含义是2001之前的数据全部确认收到了,此时1001ACK有没有被收到已经无足轻重了,因为2001之前的所有数据全部被收到了

所以,如果ACK丢了,不需要做其他操作

数据丢了

image-20220415161231846

图中1001-2000的数据丢失了,所以B会不断索要1001这个数据,即使A给B已经往后发了这个时候仍然不停的索要1001,直到A反应过来,触发了超时重传

虽然1001-2000在传输过程中丢失了,但并不妨碍A之后传输的数据,接受的数据放在了接受缓冲区,所以不需要重新再发送一遍

五、流量控制

滑动窗口的延申,为了保证滑动窗口的可靠性

滑动窗口中,窗口大小越大,传输效率越高,所以这时候不能只考虑发送法,也要考虑接受方

如果发送方的发送速度很快,但是接受方的接受速度很慢,就会导致接受方根本处理不过来,导致将发送法发的包给丢了,这时候,发送发还得重新发

流量控制的关键就是能够衡量接受方的处理速度,此时这个可以使用接收方接收缓冲区的大小,来衡量当前的处理能力

image-20220415162622514

这个过程也可以看做是一个生产者,消费者模型

A就是生产者 , B就是消费者 B的接受缓冲区就是交易场所

接收缓冲区有一个总大小,随着A发送数据,B的接受缓冲区剩余空间就越来越小

如果剩余空间比较大,就会认为B的处理能力比较强,就可以让A多发些

如果剩余空间比较小,就会认为B的处理能力比较弱,就可以让A少发些

image-20220415163015426

通过ACK就可以拿到接受缓冲区的大小,可以看到TCP报头中,有16位窗口大小,里面保存的就是接收缓冲区的剩余空间大小,发送方就可以通过这个16位窗口大小来衡量发送速度.

虽然这里窗口大小只有16位,但其实并不止,在TCP首部40字节的可选项中包含了窗口扩大因子M,实际的窗口大小是窗口字段的值左移M位

image-20220415163512978

在传输过程中,有时间B的接收缓冲区的剩余空间为0,A虽然不用发数据,但并不是完全不发数据,因为缓冲区中的数据一直在被用应用层调用,剩余空间也会逐渐增加.A会时不时的向B发送窗口探测报文,探测报文不包含实际的数据,只是为了触发ACK,知道当前的窗口大小是多少

六、拥塞控制

拥塞控制也是滑动窗口的延申,也是限制滑动窗口的发送的速率

拥塞控制衡量的是,发送方到接收方,这整个链路之间的拥塞情况(处理能力)

image-20220415164519046

A的传输速率有有多快,不光光取决于B的处理能力,还取决于中间链路的处理能力

A和B之间的节点是不清楚的,所以很难的对设备进行衡量,拥塞控制最有效的方法就是通过"实验"的方法,逐渐调整,找到合适的值

A开始的时候,以一个比较好的窗口发送数据,如果数据很流畅的就到达了,就扩大窗口大小;如果加大到一定程度之后,出现了丢包的情况,这个时候在缩小窗口.通过不断地增大/缩小过程逐渐摸索到了一个合适的范围.拥塞控制在这个范围不断变化,达到动态平衡

image-20220415164136310

最开始,取的窗口大小非常小,然后开始指数规律增长(初始窗口太小了,可能合适的值是一个很大的值,通过这个过程,可以更快的接近合适值),指数增长到一定范围,就开始线性增长.如果增长到一定范围出现了丢包,就让发送方让窗口变小(回归到初始窗口大小),然后重复之前的过程,而且新的阈值就是丢包时窗口大小的一半

七、延时应答

流量控制的延申,流量控制想法于踩了刹车,使发送方发的不要太快

延时应答,就是再这个基础上,尽量让窗口一更大一些

举个栗子:

image-20220415170206203

一个水池,一边进水,一边出水。

每一次进水,都会查询当前水池里剩余空间有多少。此时采取的策略就是不立即回答,而是稍微晚一会回答,迟一点回答,就会意味着延迟时间里会出更多的水。此时水池中的剩余空间就更大了。

如果立即回答,可能回答:水池剩余空间20吨水;如果延时一会回答:水池剩余空间30吨。(延时时间里,多出了10吨水)

这个操作就是再有限的情况下,又尽可能的提高了一点传输速度。

八、捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 “一发一收” 的。意味着客户端给服务器说了 “How are you”,服务器也会给客户端回一个 “Fine, thank you”;那么这个时候ACK就可以搭顺风车,和服务器回应的 “Fine,thank you” 一起回给客户端

image-20220415185217679

因为延时应答的存在,导致ACK并不会立刻返回的,如果当时的延时应答,导致ACK的返回时机和应用层中返回的响应时机重合了,就可以把这个ACK和响应数据合二为一

九、面向字节流–粘包问题

TCP粘包指的是 粘 的是应用层数据,再TCP接收缓冲区,若干个应用层数据包混在一起了,分不清是谁的了

image-20220415185723571

这些数据报到达B之后,就会进行分用,,分用意味着把TCP数据进行解析了,取出的应用层数据放在接收缓冲区,以备应用程序来取,如果没有额外的限制,其实就很难区分了,归根到底就是没有明确包之间的边界

解决方案:关键就是要在应用层协议这里,加上包之间的边界

例如:约定每个包以 ;结尾

image-20220415190227445

有了边界,就可以很明确的区分每个包了

十、TCP的异常处理

  1. 进程终止

在进程毫无防备的情况下,突然结束进程,这个时候该进程的TCP连接是通过socket来建立的,socket本质上是进程打开一个文件,文件其实就是存在于进程的PCB里面有个文件描述符表,每次打开一个文件(包括socket),都在文件描述符表里,增加一项,每次关闭一个文件,都在文件描述符表里,进行删除一项.

瑞国直接杀死进程,PCB也就没了,里面的文件描述符表也就没了,此时的文件相当于自动关闭了,这个过程其实和调用socket.close()一样,都会触发4次挥手

  1. 机器关机

按照操作系统约定的正常流程关机,会杀死所有进程,然后在关机

  1. 机器掉电/网络断开

image-20220415190841520

image-20220415190852365

TCP vs UDP对比

  1. 啥时候使用TCP?

对可靠性有一定要求(日常开发中的大多数情况,都是基于TCP)

  1. 啥时候用UDP?

对可靠性要求不高,对于效率要求更高

典型面试题:

基于UDP如何实现可靠传输??(考TCP)

本质就是在应用层基于UDP复刻TCP的机制

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-04-18 18:20:11  更:2022-04-18 18:22:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 4:22:56-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码