计算机网络不完全指南
前言
本文通过OSI七层模型阐述前端常考的计网知识点
应用层
HTTP
参见HTTP灵魂之问
DNS
DNS的作用就是通过域名查询到IP
因为IP存在数字和英文的组合IPv6,很不利于人类记忆,所以出现了域名。你可以把域名看成某个IP的别名,DNS就是去查询这个别名真正的名称是什么
在TCP握手之前就已经进行了DNS查询,这个查询是操作系统自己完成的,当在浏览器中想访问www.google.com
会进行以下操作。
- 本地客户端向服务器发起请求查询 IP 地址
- 查看浏览器有没有该域名的 IP 缓存
- 查看操作系统有没有该域名的 IP 缓存
- 查看 Host 文件有没有该域名的解析配置
- 如果这时候还没得话,会通过直接去 DNS 根服务器查询,这一步查询会找出负责 com 这个一级域名的服务器
- 然后去该服务器查询 google.com 这个二级域名
- 接下来查询 www.google.com 这个三级域名的地址
- 返回给 DNS 客户端并缓存起来
以上介绍的是DNS迭代查询,还有一种是递归查询,区别是前者是由客户端去请求,后者是由系统配置的DNS去请求,得到结果之后将数据返回给客户端。
DNS解析优化
主要分为两个方案
- DNS预解析
- 减少DNS请求
DNS预解析
DNS解析也需要时间的,可以通过预解析的方式来预先获取域名所对应的IP
link方式:手动解析1
<link rel="dns-prefetch" href="//blog.poetries.top">
meta方式:https自动解析1
<meta http-equiv="x-dns-prefetch-control" content="on">
设置响应头:自动解析1
ctx.set('X-DNS-Prefetch-Control', 'on')
表示层
URL的加密和解密
图片编码和解码
会话层
验证登陆
- cookie session
- token
- sso 单点登陆
- OAuth第三方登陆
cookie session
登陆流程:首次登陆
- 用户访问
a.com/pageA
并输入密码登陆 - 服务器验证密码无误后,会创建Session ID并将其保存起来
- 服务端响应这个请求,并通过Set-Cookie将Session ID写入Cookie中
再次登陆:
- 用户访问
a.com/pageB
自动带上第一次登陆写入的Cookie - 服务端对比Cookie中的SessionID和保存在服务端的SessionID是否一致
- 一致则成功
cookie session存在的问题
- 服务器需要对接大量的客户端,导致服务器压力大。
- 如果服务器是一个集群,为了同步登陆态,就需要将SessionID同步到每一台机器上,无形中增加了服务器的维护成本。
- 由于SessionID存放在Cookie中所以无法避免CSRF攻击
token登陆
为了解决cookie+session暴露出来的问题,我们可以使用token的登陆方式
token是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登陆完成后,服务器会生成一个token并返回给客户端,客户端后续访问只要带上这个token就可以完成身份验证。
token机制实现流程
首次登陆:
- 用户输入账号密码,并点击登录。
- 服务器端验证账号密码无误,创建 Token。
- 服务器端将 Token 返回给客户端,由客户端自由保存。
后续页面访问时:
- 用户访问 a.com/pageB 时,带上第一次登录时获取的 Token。
- 服务器端验证 Token ,有效则身份验证成功。
Token 机制的特点
根据上面的案例,我们可以分析出Token的优缺点:
- 服务器端不需要存放token,所以不会对服务器造成压力,即使服务器集群,也不需要增加维护成本。
- Token可以存放在前端的任何地方,可以不用保存在Cookie中,提升了页面的安全性。
- Token下发之后,只要在生效时间内,就一直有效,如果服务器想收回此token的权限,并不容易。
Token的生成方式
最常见的Token生成方式是使用JWT,他是一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。
上文中我们说到,使用Token后,服务器端并不会存储Token,那怎么判断客户端的token是否合法有效?
答案其实就在Token字符串中,其实Token并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串。如下:
JWT算法主要分成了三个部分
- header 头信息
- payload 消息体
- signature 签名
header部分指定了JWT使用的签名算法:1
header = '{"alg":"HS256","typ":"JWT"}' // `HS256` 表示使用了 HMAC-SHA256 来生成签名。
payload部分指明了JWT的使用意图1
payload = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的时间
signature部分为JWT的签名 主要为了让JWT不能随意更改,签名的方法分为两个步骤
- 输入base64url编码的header部分,base64url编码的payload部分,输出unsignedToken
- 输入服务器端私钥,unsignedToken,输出signature签名
1 | const base64Header = encodeBase64(header) |
最后Token计算如下:1
2
3
4
5const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const base64Signature = encodeBase64(signature)
token = `${base64Header}.${base64Payload}.${base64Signature}`
服务器在判断token的时候1
2
3
4
5
6
7
8
9
10
11
12
13
14const [base64Header, base64Payload, base64Signature] = token.split('.')
const signature1 = decodeBase64(base64Signature)
const unsignedToken = `${base64Header}.${base64Payload}`
const signature2 = HMAC('服务器私钥', unsignedToken)
if(signature1 === signature2) {
return '签名验证成功,token 没有被篡改'
}
const payload = decodeBase64(base64Payload)
if(new Date() - payload.iat < 'token 有效期'){
return 'token 有效'
}
有了 Token 之后,登录方式已经变得非常高效,接下来我们介绍另外两种登录方式。
SSO单点登陆
单点登陆指的是在公司内部搭建一个公共的认证中心,公司下的所有产品的登陆都可以在认证中心中完成,一个产品在认证中心登陆后,再去访问另外一个产品,可以不用再次登陆,即可获取登陆状态。
SSO机制实现流程
用户首次访问需要在认证中心登陆
断点续传
https://juejin.cn/post/6844904046436843527
传输层
TCP和UDP
- tcp是面向链接的 udp是无连接的即发送数据前不需要建立连接
- tcp提供可靠的服务,也就是说通过tcp连接发送的数据,无差错,不丢失,不重复,且按序到达,udp尽最大的努力交付,即不保证可靠交付。并且因为tcp可靠,面向连接,不会丢失数据因此适合大数据量的交换
- tcp是面向字节流,udp面向报文,并且网络出现拥塞不会使得发送速率降低(因此会产生丢包,对实时的应用比如ip电话和视频会议等)
- tcp只能是一对一的,udp支持1对1 1对多
- tcp的首部较大为20字节 udp只有8字节
- tcp是面向连接的可靠性传输,udp是不可靠的
常见使用场景
UDP:
- 直播
- 游戏
TCP三次握手
建立连接前,客户端和服务端需要通过握手来确认对方
- 客户端发送SYN同步序列编号请求,进入SYN_SEND状态,等待确认
- 服务端接收并确认SYN包之后发送SYN+ACK包,进入SYN_RECV状态
- 客户端接收SYN+ACK包之后,发送ACK包,双方进入ESTABLISHED状态
为什么采用三次握手?
举例:已失效的连接请求报文段
- 如果客户端发送请求报文但由于网络问题导致报文失效,服务端接收到请求后就会发送确认报文表示同意连接。此时如果是两次握手,那么此时服务端已经建立了新的连接,但由于这个请求是失效的,客户端并没有建立连接,服务端就会浪费很多资源。如果是三次握手,那么此时服务端收不到确认连接的信息,就知道客户端没有建立连接。这就是三次握手的作用。
TCP有六种标识SYN建立连接,ACK确认,PSH传送,FIN结束,RST重置,URG紧急
三次握手过程中可以携带数据吗?
- 第一次第二次握手不能携带数据,因为第一次握手和第二次握手服务端还没有建立连接,此时发送数据会让服务器受到攻击。
- 第三次握手服务器已经处于ESTABLISHED状态,对于客户端来说已经建立了连接,并且也知道服务器的接收发送能力是正常的了,所以能携带数据。
四次挥手
为了确保数据能完成传输
- 客户端—FIN—>服务端,FIN——WAIT
- 服务端—ACK—>客户端,CLOSE——WAIT
- 服务端—ACK,FIN—>客户端,LAST——ACK
- 客户端—ACK—>服务端,CLOSED
为什么连接的时候是三次握手,关闭的时候是四次挥手?
- 服务端接收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。
- 但是在关闭连接的时候,当服务端收到FIN报文的时候 ,很可能并不会立刻关闭连接,所以只能先回复一个ACK报文,告诉客户端,你发的FIN报文收到了,只有等服务器所有的报文发送完了才能发送FIN报文,因此不能一起发送,故需要四次挥手。
拥塞控制
网络拥塞(congestion)是指在分组交换网络中传送分组的数目太多时,由于存储转发节点的资源有限而造成网络传输性能下降的情况。当网络发生拥塞时,一般会出现数据丢失,时延增加,吞吐量下降,严重时甚至会导致“拥塞崩溃”(congestion collapse)。通常情况下,当网络中负载过度增加致使网络性能下降时,就会发生网络拥塞。下图则描述了在有无拥塞控制的干预下,网络吞吐量随输入负载的增加的变化情况。
通过以上的描述,我们大概可以得到以下信息:
- 网络拥塞往往是由于对资源的请求超出了存储转发节点的能力而导致的。
- 网络拥塞可能会导致数据丢失,时延增加,吞吐量下降等问题。
- 若出现拥塞而不进行控制,有可能会使整个网络情况恶化,甚至网络吞吐降为0。
- 网络的拥塞状况与当前网络负载是密切相关的。
拥塞控制算法
- 慢启动
- 拥塞避免
- 快重传
- 快恢复
流量控制
如果发送方的数据发送过快,接收者来不及接收,就有分组丢失。为了避免分组丢失,控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。
流量控制的根本目的是防止分组丢失,它是构成TCP可靠性的一方面
实现流量控制
滑动窗口协议(连续ARQ协议)实现。它保证了分组无差错,有序接收,也实现了流量控制。
主要的方式是 接收方返回的ack包会包含自己接收窗口的大小,并且利用大小来控制发送方的数据发送
流量控制引发的死锁?怎么避免死锁发生?(没看明白)
当发送者收到了一个窗口为0的应答,发送者便停止发送,等待接收者的下一个应答。但是如果这个窗口不为0的应答在传输过程丢失,发送者一直等待下去,而接收者以为发送者已经收到该应答,等待接收新数据,这样双方就相互等待,从而产生死锁。为了避免流量控制引发的死锁,TCP使用了持续计时器。每当发送者收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小。若接收者仍然返回零窗口,则重置该计时器继续等待;若窗口不为0,则表示应答报文丢失了,此时重置发送窗口后开始发送,这样就避免了死锁的产生。
拥塞控制和流量控制的区别
拥塞控制就是作用于网络的,它是防止过多的数据注入到网络中,避免出现网络负载过大的情况。常用的方法是:慢开始,拥塞避免,快重传,快恢复。
流量控制就是作用于接收者的,它是控制发送者的发送速度从而使得接收者来得及接收,防止分组丢失