计网
计网常见知识
常见含义
ISP:互联网服务提供商,如中国电信、中国联通等
MAC: 网络接口卡(网卡) 在制造时固化的一个全球唯一标识符
RTT:最小往返时延(考虑网络拥堵)
MSS:TCP数据部分的最大长度
TCP/IP模型
OSI和TCP/IP

如上图所示:
- 我们使用的
HTTPS、FTP、DHCP、以及HTTP都属于应用层 TCP、UDP都属于传输层IP则属于网络层
一个应用场景,完美契合TCP/IP模型

Socket
套接字,就是ip地址+端口号
TCP
核心:三握四挥
TCP首部会用掉20个字节

TCP报文里有SYN、ACK和FIN标识
- 设置为1就是开启这些标识
- 设置为0就是关闭这些标识
三次握手
流程
- 客户端发送SYN报文,并设置好序号
- 服务端发送SYN+ACK报文,设置序号,将确认号的值设置为客户端SYN报文的序号+1
- 客户端发送ACK报文,序号用服务端报文的确认号,确认号用服务端报文的序号+1


为什么需要三次握手
三次握手才能保证双方具有接收和发送的能力
- 能阻止历史连接的建立,能减少双方不必要的资源开销,帮助双方同步初始化序列号
四次挥手
流程
- 主动断开方(可以是客户端,也可以是服务端)发送一个FIN和ACK报文,并设置好序号和确认号
- 被动断开方发送一个ACK报文,报文的序号为断开请求的确认号,报文的确认号为断开请求的序号+1
- 被动断开方还可以进行数据的发送,剩余数据发送完后,被动断开方会向主动断开方发送一个FIN+ACK结束响应报文
- 主动断开方在收到FIN+ACK断开响应报文后,还需要进行最后的确认,向被动断开方发送一个ACK确认报文,序号为被动断开方的确认号,确认号为被动断开方的序号+1
- 主动断开方随之进入
TIME_WAIT,等待2MSL一段时间后,进入CLOSE状态,连接关闭


为什么要进行四次挥手?
因为服务端可能还有数据需要发送,客户端发送FIN后表示其不再发送数据,但还可以接收数据
为什么需要TIME_WAIT状态
- 防止历史连接的数据被新连接错误的接收
如果网络出现延迟,导致数据包在网络滞留,如果没有TIME_WAIT状态,这些滞留的数据包就会被传递给新连接,导致新连接被旧连接的数据干扰
- 保证被动断开方能被正确关闭,即保证最后的ACK可以让被动断开方接收
若主动断开方的最后一次ACK报文丢失,被动断开方没有接收到报文就会重发FIN报文,而主动断开方收到重传的FIN报文后就会传回RST报文,被动断开方收到这个RST报文就会报错
为什么TIME_WAIT等待的时间是2MSL
- MSL是
Maximum Segment Lifetime,报文最大生存时间,是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃 - 2MSL:发送方和接收方一来一回需要2MSL时间
TCP重传机制
超时重传
两种情况

注意
- RTT:往返时延,数据包的往返时间
- RTO:超市重传时间
- RTO的值应该略大于RTT的值
快速重传
核心:不以时间为驱动,以数据驱动重传
原理:当收到三个相同的ACK报文时,会重传丢失的报文段
局限性:但是重传的时候,不知道要重传多少数据包,因此引入了SACK(选择性确认)技术
SACK
为了解决重传哪些报文的问题而提出
需要在TCP头部加一个SACK的东西,可以将已收到的数据的信息发送给发送方,发送方就可以知道哪些数据没收到,只重传丢失的数据
TCP流量控制
目的:主要是为了解决发送方发送数据过快导致接收方缓冲区溢出的问题
核心思想:接收方通过告知发送方自己还有多少剩余的缓冲区空间来主动控制发送方的发送速率
核心机制:滑动窗口
工作流程
- 连接建立时
- 双方都会维护一个接收缓冲区
- TCP首部中的
Window字段表示当前可接收的窗口大小(rwnd),最大为65535字节
- 数据传输中
- 发送方发送数据后会等待ACK
- 接收方每次ACK时,会带上当前的接收窗口大小
- 发送方根据ACK中的
rwnd调整自己的发送速率
- 窗口滑动
- 当接收方应用程序从接收缓存中取走部分数据时,空出来的空间就意味着窗口可以“滑动”
- 接收方在下一次ACK中通知发送方新的窗口大小
窗口结构
- 发送方和接收方都有自己的窗口结构
- 发送方需要将数据分为发送+已确认,发送窗口,不能发送
- 发送窗口还细分为可用窗口和已发送+未确认
- 已发送+未确认可用于重传
- 可用窗口则是表示可以发送的数据
- 发送窗口还细分为可用窗口和已发送+未确认
- 接收方需要将数据分为接收+已确认,接收窗口,不能接收

滑动窗口增大了TCP的吞吐量,可以在同一时间发送多个数据包,实现并行发送,使用累计确认机制保证数据传输完整性
即便前面的某个确认应答(ACK)在路上丢了,只要后续的 ACK 顺利到达,发送方就知道前面的数据也已经被安全接收了。
假设客户端传了7、8、9三个数据包可以服务端,服务端成功返回ACK7、ACK8、ACK9,但是ACK8在网络上丢失了,但是客户端收到了ACK9,那么客户端认为序号9之前的数据服务端都已经接收到,客户端因此可以继续发送数据
拥塞控制
网络拥塞:网络中的链路或者路由器过载时,会丢弃数据包
核心:采用一系列方法控制拥塞窗口的大小,进而控制发送窗口的大小,发送窗口=min(拥塞窗口,接收窗口)
流程
- 初始时设置
ssthresh(慢启动阈值),采用慢启动,初始化拥塞窗口cwnd为1MSS(TCP数据部分的最大长度) - 初始拥塞窗口为1,呈现指数增长
- 到达
ssthresh后呈线性增长,进入拥塞避免阶段 - 当发生超时重传时
- 说明网络拥塞严重,重新回到慢启动
ssthresh = cwnd / 2cwnd被重置为1MSS
- 当收到3个重复的ACK时
- 说明网络拥塞不那么严重,触发快速重传和快速恢复
ssthresh = cwnd / 2cwnd被重置为ssthresh+ 3MSS- 然后进入拥塞避免阶段(线性增长)

KeepAlive
TCP保活机制:启用KeepAlive的时候,操作系统会定期发送一些特定的探测数据包来维护连接灵活性
- 发送的数据包一般是空的,无实际的数据内容
- 如果一端接收到了探测数据包,它会回复一个ACK数据包;如果一段时间内发送端没有接收到确认数据包,发送端会认为连接已经断开,从而触发连接关闭
- 长时间的持久连接会占用服务器资源,因此在实际生产一般会设置
KeepAlive的超时时间
BBR
是Google提出的新一代TC 拥塞控制算法
- 不靠丢包来判断网络拥堵情况
- 靠实时估算网络能提供的**最大带宽(BtlBw)和最小往返时延(RTprop)**来控制发送速率
UDP
无连接、不可靠的传输协议,常用在端口寻址、实施在线游戏、实时音视频传输等
特点
- 无连接:通信前不需要建立连接,直接发送数据包即可
- 不可靠交付:不提供确认、重传等机制
- 无拥塞控制:不管网络状况多差,UDP都会以恒定的速率发送数据
- 支持广播
UDP首部只用掉8个字节

IPV4和子网掩码
IPV4
ipv4是由4组8位二进制组成的,组之间用.隔开
ip地址 = 网络号 + 主机号
- 网络号:同一个物理网络的所有设备,网络号是相同的
- 路由寻址:路由器只关心目标IP地址的网络号,从而实现数据包的转发
- 主机号:IP地址中在特定网络内用于标识唯一设备的一部分
- 最终交付:数据包到达目标网络后,路由器会查看目标IP的主机号,从而将数据包准确地发送给正确的设备
ip地址类型
- A类:网络数为128,主机数为16777216
- B类:网络数为16384,主机数为65536
- C类:网络数为2097152,主机数为256

注意
- 网络地址的主机位全部为0,会占用1个ip,是整个网络的唯一标识
- 广播地址的主机位全部位1,会占用1个ip,向网络中所有设备发送广播
- 因此,C类网络只能分配
256-2=254个ip地址
本地广播与直接广播
- 本地广播:在本网段内广播,路由器会屏蔽掉广播地址的IP包,使得数据在本网段内传输
- 直接广播:跨网段广播
子网掩码
一个32位二进制数,为了划分网路号和主机号而产生的
- 相同的网络号会用子网掩码的1进行锁定
- 主机号为0的二进制位也会用子网掩码的1来进行锁定
也就是说,在子网掩码中
- 1对应的位是网络位:标识一个子网。同一个子网内的所有IP地址,其网络位必须完全相同
- 0对应的位是主机位:标识子网内的具体设备。主机位在子网内可变,且必须唯一

CIDR表示方法
看子网掩码中有多少个1,在IP地址后加/1的个数即可
IPV6
ipv6地址是由128位二进制数组成,通常以十六进制形式表示,分为8组,每组16位二进制数(4个十六进制数字)用冒号分隔
地址压缩
- 零压缩:连续的零组可以用双冒号(
::)表示,但在一个地址中只能使用一次 - 前导零压缩:每组中的前导零可以省略,例如0001可以表示为1
地址的组成部分
前缀:前缀用于标识网络部分,类似于IPv4中的网络地址,前缀长度通常以斜杠后跟数字的形式表示
接口标识符:用于标识网络中的具体接口,通常为后64位

地址类型

NAT
网络地址转换

原理:
- 内网访问外网通过出口路由时,源地址会转换成特定公有地址,并且将两个ip映射关系加到NAT映射表上
- 在外网向内网通信时,目的地址还是特定公有地址,但是到达出口路由器后,查看NAT映射表,从而转换为私有地址
问题:
- 破坏端到端通信:两个都在NAT后的设备难以直接建立P2P连接
- 服务暴露困难:外部网络无法直接主动访问NAT后的内部服务
虚拟机网络
NAT
图解

- 虚拟机之间无法互相访问
- 宿主机、局域网设备无法访问虚拟机
- 虚拟机可以通过宿主机访问互联网
NAT网络
图解

- 在虚拟机前加了一台虚拟的交换机
- 加上了网关和DHCP服务
桥接
图解

- 虚拟机和宿主机同级,在同一个网络里
- 宿主机和虚拟机在同一个DHCP服务获取私有IP地址,因此虚拟机会消耗宿主机所在局域网的IP地址
DHCP
动态主机配置协议,是处于应用层的协议
作用:自动为网络中的电脑、手机等设备分配IP地址
动态配置
可以在路由器中配置IP池,增加私有IP的数量,从而增加联网设备的数量
DHCP握手
流程
- 客户端发送**
DHCP Discover**- 传输层使用UDP进行数据传输,客户端使用68端口,服务端使用67端口
- 网络层中,不知道源IP地址填写
0.0.0.0,不知道目标IP地址填写255.255.255.255,这样交换机接受到之后就会进行广播 - 数据链路层与IP地址道理相同

- DHCP服务器发送**
DHCP Offer**,给客户端提供私有IP地址、子网掩码、网关、DNS- 网络层中DHCP服务器会发送确定的源IP地址和目标IP地址供客户端选择
- 数据链路层中的MAC地址也同理

- 客户端进行**
DHCP Request**,广播所有DHCP服务器客户端选择了哪个IP

- 服务端发送**
DHCP ACK**,新设备可以开始上网

DNS
DNS默认使用UDP协议
- 不出现分片情况下,UDP协议最大有效载荷是512字节以内
- 根服务器地址需要塞进一个UDP包里,最多只能放下13组记录
域名结构树
- 顶层的根
.是由一群服务器组成的,这群服务器只用了13个域名

域名服务器类型

DNS解析过程
- 浏览器缓存:首先在浏览器检查是否有该域名对应的IP
- 操作系统缓存:如果没有,浏览器会调用操作系统(如通过
gethostbyname系统调用),检查本地的Hosts文件和操作系统DNS缓存 - 本地DNS解析器:如果本地没有,请求会发送到配置的本地DNS服务器
- 根域名服务器:若本地DNS解析器没有缓存,会向根域名服务器发起查询,根域名服务器只会返回负责你输入的域名的TLD(如
.com、.cn)的顶级域服务器地址,只告诉你从哪个顶级域服务器继续查询 - 顶级域名服务器(TLD):本地DNS服务器再向TLD服务器查询,得到权威域名服务器的地址,只告诉你从哪个权威域服务器继续查询
- 权威域名服务器:最后,本地DNS服务器向权威域名服务器查询你输入的域名的IP
- 返回并缓存:本地DNS服务器将IP地址返回给操作系统,并缓存该记录。操作系统再返回给浏览器,并缓存

常见DNS记录类型
A记录:将域名指向一个IPv4地址AAAA记录:将域名指向一个IPv6地址CNAME记录: 域名别名,将一个域名指向另一个域名MX记录: 邮件交换记录,指定负责接收邮件的服务器
递归查询和迭代查询
递归查询
DNS客户端向上层DNS服务器发起查询,并要求这些服务器直接返回查询结果
- 也就是说DNS客户端只需要发送一个查询请求,然后等待完整结果
- 上层DNS服务器会自行查询下一级服务器,将结果返回给DNS客户端
迭代查询
DNS客户端向上层DNS服务器发起查询,但不要求这些服务器直接返回查询结果
- DNS只从上层DNS服务器获取一个更高级的域名服务器地址,然后自行向那个更高级的域名服务器发起查询
SSH
一种加密的通信方式,在SSH握手过程使用非对称加密获得对称密钥
连接流程
- 进行TCP连接
- 进行SSH握手
- 客户端和服务端协商SSH协议版本
- 进行密钥交换初始化,协商应该使用什么算法
- 客户端生成临时私钥和临时公钥,将临时公钥发送给服务端
- 服务端生成临时私钥和临时公钥,将客户端的临时公钥和自己的临时私钥和临时公钥生成共享安全密钥
- 服务端将自己的临时公钥发送给客户端,客户端将服务端的临时公钥和自己的临时私钥和临时公钥生成共享安全密钥
- 服务端生成一对
host公钥和host私钥,生成交换哈希值,并使用host私钥对交换哈希值进行加密,生成交换哈希值的数字签名,将数字签名和host公钥发送给客户端 - 客户端拿到服务端的
host公钥对数字签名进行解密,并生成自己的交换哈希值(客户端和服务端算出来的交换哈希值是一样的),比较二者是否一样

交换哈希值
构成:
- 客户端和服务端的版本号字符串
- 客户端和服务端密钥交换初始化负载(算法名称的字符串)
- 服务端的
host公钥 - 客户端临时公钥和服务端临时公钥
- 共享安全密钥
使用SSH证书
在客户端使用ssh-keygen命令生成密钥对,私钥放在本地,使用ssh-copy-id将公钥发送给服务器,可以实现免密登录,提高登录安全性
HTTP
超文本传输协议
- 无状态(Stateless):服务器不会记忆HTTP的状态,因此不需要额外的资源记录状态信息,减轻服务器负担
- 明文传输(HTTP1.1以前):有利于调试,但信息透明,不安全
- 可扩展(Header可以自定义)
- 灵活(支持文本、图片、视频等多种资源类型)
- 请求-响应模型
- 默认端口是80
核心概括
- HTTP/1.1:持久连接、明文文本、队头阻塞
- HTTP/2:二进制分帧、多路复用、头部压缩
- HTTP/3:基于QUIC/UDP、解决TCP队头阻塞、集成TLS
HTTP/1.1
长连接(Keep-Alive)
只要客户端和服务端任意一端就没有断开连接,就保持TCP连接状态
请求流水线
在同一个TCP请求里面,客户端可以多次发起请求,第一次请求的响应还未返回便可以发送第二次请求
但是服务器必须按照请求顺序对这些请求做出响应,因此有队头阻塞的问题
队头阻塞:如果服务器在处理请求A耗时较长,那后续的请求都会被阻塞
缺点
明文传输:HTTP/1.1仍保持明文传输,有利于调试,但信息透明,不安全
报文格式:使用纯文本协议,明文传输,(请求/响应是ASCII文本,头部和Body分界),可读性高
每次请求/响应都发送完整的头部,存在大量重复开销
报文首部不压缩,报文主体压缩
靠多个TCP连接并发加载资源,数据包丢失时容易造成队头阻塞
HTTP/2
实现多路复用
核心是将每个请求/响应对抽象成流,每个流被拆分成多个带有ID的帧,服务器根据流ID对帧进行重组
在一个TCP连接上同时传输多个请求与响应,解决HTTP/1.1的应用层队头阻塞问题
但在传输层仍然存在队头阻塞的问题
首部压缩
- 使用
HPACK算法对请求头和响应头进行压缩,降低传输压力 - 使用动态表与静态表减少冗余,降低带宽占用
二进制分帧
- 将数据分割成二进制帧进行传输,分为首部帧和数据帧等类型
- 将请求/响应拆分成更小的帧
- 帧可以在同一个TCP连接上交错发送和接收
- 每个帧包含唯一的流标识符(
Stream_ID),服务器可按流独立组装
传输层的队头阻塞
- HTTP2在传输层使用了TCP协议,TCP协议必须保证接收到的字节是完整的,如果丢包了就会发生阻塞,导致字节留在内核缓冲区,服务器无法拿到数据
缺点
- 传输层的队头阻塞
- TCP与TLS的握手延迟:发出HTTP请求时,需要进行TCP三次握手和TLS四次信息交换,共计3RTT时延才能发出请求数据
- 网络迁移需要重新连接:一个TCP连接由【源IP地址,源端口,目标IP地址,目标端口】组成,如果IP地址或端口发生变化,需要重新建立TCP连接
HTTP/3
HTTP/2队头阻塞的原因是因为传输层的TCP,在HTTP/3中将TCP改成了UDP
核心:整合
- 把传输层从TCP改为QUIC(基于UDP),解决TCP队头阻塞问题
- 保留HTTP2的二进制分帧
- 握手:在QUIC中集成TLS1.3握手

HTTPS
核心:通过非对称加密安全的交换一个对称加密的会话密钥
默认端口是443
是对Http的升级,后面的S指的是SSL/TLS
Https=Http+SSL/TLSSSL/TLS是一种加密安全协议,可以对发起http请求的请求和响应进行加密SSL是TLS的前身,现在很多浏览器都支持TLS
对称加密
加密和解密用的是同一个密钥
- 发送方用密钥和加密算法对明文进行加密
- 接收方用密钥和加密算法对密文进行还原
分发密钥时就会遇到挑战,通过网络传输的话密钥容易被黑客截取,黑客可以很轻松对密文进行还原
非对称加密
客户端和服务端各使用一把公钥和一把私钥,公钥可进行传递,用于加密,私钥不可泄漏,用于解密
- 发送方在网络上获取公钥,用自己的私钥和公钥对信息进行加密
- 接收方同样用私钥和公钥对信息进行解密
- 黑客无法知道私钥是什么,也就无法解密信息

SSL证书
将网站的身份信息与一个公钥进行绑定,并由权威的**证书颁发机构(CA)**进行数字签名,证明其真实性
TLS1.2握手流程(在这之前,服务端和客户端会进行TCP连接)
- 客户端发送
Client Hello,TLS版本,加密套件和第1随机数(Client Random)给服务端- 第1随机数为一个由客户端生成的随机字符串
- 客户端发送它支持的加密套件列表供服务端选择
- 服务端发送
Server Hello,TLS版本,加密套件和第2随机数(Server Random)给客户端- 第2随机数为一个由服务端生成的随机字符串
- 此时返回给客户端服务端所选择的加密套件
- 服务端发送证书给客户端
- 证书包含了服务器的公钥、域名、颁发机构、有效期等信息
- 服务端进行
Server Key Exchange,发送自己的临时公钥 - 服务端发送
Server Hello Done,告诉客户端信息发送完毕 - 客户端验证证书,如果证书不通过,连接中止
- 客户端生成一个预主密钥,使用服务端提供的公钥进行加密,然后发送给服务端
- 服务端利用自己的私钥解密得到预主密钥
- 客户端和服务端按照约定好的算法对这三个随机数生成相同的会话密钥
- 客户端告诉服务端切换密码规范,使用刚刚生成的会话密钥对会话内容进行加密
- 客户端发送
Client Finished完成消息 - 服务端发送
Finished消息
TLS1.3将握手往返次数从2次减少到1次,显著降低了延迟,会话密钥都是由对方公钥加上自己的私钥和公钥生成的
握手总结
- 预主密钥是客户端生成的第3个随机数
- 后续的信息传输使用会话密钥

QUIC
对协议层进行重构
- 融合了
HTTP2、TLS、TCP的特征 - 顶层使用
HTTP3 - 传输层使用
UDP - 使用
QPACK进行数据压缩

QUIC数据包
- 应用数据拆分成QUIC流
- QUIC流组合成QUIC帧
- QUIC帧连接成QUIC包

传输时延
- 首次通信是1RTT,此时首部数据会较长,后续加密通信首部会较短
- 通信的恢复是0RTT

加密
- QUIC对连接ID等信息进行了加密
- 导致ISP难以根据连接ID进行流量监测

RPC
远程过程调用:Remote Procudure Call
- 基于UDP/TCP等传输层协议,也可以配合HTTP等应用层协议
- 在分布式系统中,让一个应用像调用本地函数一样去调用另一台机器上的方法,拥有跨语言能力
- 有多种实现,如
grpc、Dubbo等 - 序列化方法也有多种实现,常见有
protobuf等
流程
- 客户端发起调用
- 客户端存根(
stub)处理,对函数名、数据等进行序列化,封装成网络信息 - 网络传输
- 服务端接收
- 服务端存根(
skeleton),对请求进行反序列化,还原出函数名和参数 - 服务端执行业务代码
- 服务端存根将结果进行序列化并发送给客户端
- 客户端对传输数据反序列化出结果
grpc
rpc的一种实现方法,由Google实现
- 支持多种语言
- 基于IDL文件定义服务
- 基于HTTP2设计
- 序列化支持
Protocol Buffer和JSONPB使用.proto文件定义数据结构和接口规范,使用自带的编译器为多种编程语言生成对应的代码- 包括消息结构体、客户端和服务端接口代码
protobuf比JSON小3-10倍,解析速度快5-20倍左右
- 支持拦截器:Unary interceptor(一元)和Stream interceptor(流式)
metadata,元数据,类似HTTP的header,可以存放token、rid等
通信模式
一元RPC(
Unary RPC):客户端发送单个请求,服务器返回单个响应服务端流式RPC(
Server Streaming RPC):客户端发送单个请求,服务器返回多个响应(数据流)客户端流式RPC(
Client Streaming RPC):客户端发送多个请求(数据流),服务端返回单个响应双向流式RPC(
Bidrectional Streaming RPC):客户端和服务端都可以发送多个请求/响应
