自定义协议中,
我们有几种常见的数据格式:
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次请求都使用第一次结果即可。