本篇是本人温习网络知识的笔记,主要摘抄自图解网络。
一、网络基础
在建立网络通信时,需要发送端和接收端之间有很多规范,而这些规范的建立就是许许多多的协议,我们统称TCP/IP协议族。
这里的TCP和IP并不代表具体协议,是多种协议方案的统称,比如DNS、FTP、HTTP等。
通过下图了解一下在http通信过程中,HTTP、DNS、IP 协议、TCP都发挥了什么作用?
这里涉及到的几个协议如下
- HTTP 协议:生成针对目标服务器的请求报文
- DNS 协议:用于将域名解析成
IP 地址,因为域名更符合人类的记忆习惯,IP 地址更符合机器识别,所以DNS 协议应运而生。 - TCP 协议:提供可靠传输,就是将报文分割成多个小段,后续会详细解析,其实这么做也是为了让 TCP 能够传输大块数据。
- IP 协议:IP 协议上面已经说过,主要是从互联网中寻找一条路径,使得数据能够达到服务器。
TCP/IP中有很多协议,为了设计和使用的方便,对其进行了分层管理。
1. TCP/IP 的四层架构
TCP/IP协议族按层次分别分为以下四层:应用层、传输层、网络层和数据链路层(网络接口层)。
从下图左边部分看到网络包接收的流程,右边部分刚好反过来,它是网络包发送的流程。
1 应用层(HTTP/FTP/DNS..)
应用层决定用户使用哪种协议方案,比如HTTP,FTP,DNS都属于该层。
HTTP
HTTP是超文本传输协议,是个双向协议,有问必答。
当浏览器中键入 URL,浏览器处理步骤如下:
- 对
URL进行解析 - 生成发送给服务器的请求信息
URL 实际上是请求服务器里的文件资源,如图所示
对 URL 进行解析之后,浏览器确定了 Web 服务器和文件名,接下来就是根据这些信息来生成 HTTP 请求消息了。
DNS
DNS 作用是查询服务器域名对应的 IP 地址,根据域名查询 IP 的过程如下图所示
查询过 IP 的域名会保存到本地 DNS 服务器缓存。
2 传输层(TCP/UDP/ICMP)
提供处于网络连接中的两台计算机之间的数据传输。该层有两个知名协议,分别是 TCP 和 UDP。
| TCP | UDP | |
|---|---|---|
| 连接方式 | 面向连接,提供可靠传输。 | 面向无连接,提供不可靠传输 |
| 流量控制 | 提供流量控制 | 不提供流量控制 |
| 传输顺序 | 有序传输 | 无序传输 |
| 传输形式 | 字节流 | 数据包 |
TCP
HTTP 是基于 TCP 协议传输的,所以在这我们先了解下 TCP 协议。
TCP 包头格式
我们先看看 TCP 报文头部的格式:
首先,源端口号和目标端口号是不可少的,如果没有这两个端口号,数据就不知道应该发给哪个应用。
接下来有包的序号,这个是为了解决包乱序的问题。
还有应该有的是确认号,目的是确认发出去对方是否有收到。如果没有收到就应该重新发送,直到送达,这个是为了解决丢包的问题。
接下来还有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力,别发送的太快,撑死我,也别发的太慢,饿死我。
除了做流量控制以外,TCP还会做拥塞控制,对于真正的通路堵车不堵车,它无能为力,唯一能做的就是控制自己,也即控制发送的速度。不能改变世界,就改变自己嘛。
三次握手
在 HTTP 传输数据之前,首先需要 TCP 建立连接,通常称为三次握手。
这个所谓的「连接」,只是双方计算机里维护一个状态机,在连接建立的过程中,双方的状态变化时序图就像这样。
- 一开始,客户端和服务端都处于
CLOSED状态。先是服务端主动监听某个端口,处于LISTEN状态。 - 然后客户端主动发起连接
SYN,之后处于SYN-SENT状态。 - 服务端收到发起的连接,返回
SYN,并且ACK客户端的SYN,之后处于SYN-RCVD状态。 - 客户端收到服务端发送的
SYN和ACK之后,发送对SYN确认的ACK,之后处于ESTABLISHED状态,因为它一发一收成功了。 - 服务端收到
ACK的ACK之后,处于ESTABLISHED状态,因为它也一发一收了。
TCP分割数据
如果 HTTP 请求消息比较长,超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
MTU:一个网络包的最大长度,以太网中一般为1500字节。MSS(Maximum Segment Size):除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度。
数据会被以 MSS 的长度为单位进行拆分,拆分出来的每一块数据都会被放进单独的网络包中。也就是在每个被拆分的数据加上 TCP 头信息,然后交给 IP 模块来发送数据。
TCP报文生成
TCP 协议里面会有两个端口,一个是浏览器监听的端口(通常是随机生成的),一个是 Web 服务器监听的端口(HTTP 默认端口号是 80, HTTPS 默认端口号是 443)。
在双方建立了连接后,TCP 报文中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报文之后,就需交给下面的网络层处理。
至此,网络包的报文如下图。
此时,遇上了 TCP 的 数据包激动表示:“太好了,碰到了可靠传输的 TCP 传输,它给我加上 TCP 头部,我不再孤单了,安全感十足啊!有大佬可以保护我的可靠送达!但我应该往哪走呢?”
四次挥手
四次挥手释放连接时,等待 2MSL 的意义?
- Linux 里 1MSL(Maximum Segment Lifetime)是 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经丢失了。
- 在主动断开方发送最后一次 ack 时,如果被动方没有收到,会触发超时并重新发送 FIN,而主动方在收到FIN后会重发 ACK 给被动方,一来一回正好2MSL。
UDP
UDP 是一个简单、不可靠的传输协议,而且是 UDP 包之间是无序的,也没有依赖关系。而且,UDP 是不需要连接的,也就不需要握手和挥手的过程,所以天然的就比 TCP 快。
3 网络层(IP协议)
网络层最常使用的是 IP 协议(Internet Protocol) ,这个 IP 协议不同于 IP 地址,两者不要搞混。
IP 协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(以太网中一般为 1500 字节)就会再次进行分片,得到一个即将发送到网络的 IP 报文。
IP 协议有两个重要能力寻址和路由。
寻址
网络层负责将数据从一个设备传输到另一个设备,世界上那么多设备,又该如何找到对方呢?因此,网络层需要区分设备的编号。
我们一般用 IP 地址给设备进行编号,对于 IPv4 协议, IP 地址共 32 位,分成了四段(比如,192.168.100.1),每段是 8 位。只有一个单纯的 IP 地址虽然做到了区分设备,但是寻址起来就特别麻烦,全世界那么多台设备,难道一个一个去匹配?这显然不科学。
因此,需要将 IP 地址分成两种意义:
- 网络号,负责标识该 IP 地址是属于哪个「子网」的;
- 主机号,负责标识同一「子网」下的不同主机;
IP 地址需要通过子网掩码的计算才能得到网络号和主机号。
举个例子,比如 10.100.122.0/24,后面的/24表示就是 255.255.255.0 子网掩码,255.255.255.0 二进制是「11111111-11111111-11111111-00000000」,大家数数一共多少个1?不用数了,是 24 个1,为了简化子网掩码的表示,用/24代替255.255.255.0。
知道了子网掩码,该怎么计算出网络地址和主机地址呢?
将 10.100.122.2 和 255.255.255.0 进行按位与运算,就可以得到网络号,如下图:
将 255.255.255.0 取反后与 IP 地址进行按位与运算,就可以得到主机号。
路由
IP 协议还有另一个重要的能力就是路由。
实际场景中,两台设备并不是用一条网线连接起来的,而是通过很多网关、路由器、交换机等众多网络设备连接起来的,那么就会形成很多条网络的路径,因此当数据包到达一个网络节点,就需要通过路由算法决定下一步走哪条路径。
路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。
ICMP
ICMP 全称是 Internet Control Message Protocol,它是TCP/IP协议族的一个子协议,提供可能发生在通信环境中的各种问题反馈。
如图所示
ICMP的通知消息使用IP协议发送回来,因此会原路返回。
返回的ICMP消息格式如图所示:
ICMP包头的类型字段,大致可以分为两大类(查询报文类型、差错报文类型),如图所示:
ICMP协议有以下两个应用:
ping命令就是基于回送消息实现的,发包时ICMP header的type是8,接收时,ICMP header的type是0traceroute命令则是通过设置TTL(Time To Live,生存周期),它的值随着每经过一次路由器就会减 1,直到减到 0 时该 IP 包会被丢弃。此时,路由器将会发送一个ICMP超时消息给发送端主机,通知该包已被丢弃。
4 数据链路层
在网络层生成了 IP 头部之后,接下来要交给 数据链路层(网络接口层) ,在 IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)发送到网络上。
IP 协议依赖于 ARP(Address Resolution Protocol) 协议,ARP 协议是一个地址解析协议,它可以根据 IP 地址解析出 MAC 地址,所以我们在得到另一端的 IP 地址后,可以通过 ARP 协议得到对方的 MAC 地址,由 IP 协议决定下一站发往哪个中转地的 MAC 地址,依次类推,最终找到另一端的 MAC 地址,将数据包发送过去。
网络接口层的传输单位是帧(frame),IP 层的传输单位是包(packet),TCP 层的传输单位是段(segment),HTTP 的传输单位则是消息或报文(message)。但这些名词并没有什么本质的区分,可以统称为数据包。
2. HTTP篇
请求方法
- GET
用于获取已经被 URI 识别的资源,若资源是文本,则直接返回文本,若资源是类似 CGI(Common Gateway Interface 通用网关接口)这种程序,则返回执行后的结果。
- POST
用于传输实体主体,虽说和 GET 方法类似,但 POST 的主要目的是向资源提交数据处理请求,POST 请求可能会导致新的资源的建立或已有资源的修改。
- HEAD
HEAD 请求用于鉴别 URI 的有效性及更新时间等,和 GET 方法类似,但不返回实体主体,只返回报文首部。
- PUT
PUT 方法用于传输文件,因为 HTTP/1.1 的 PUT 方法自身不具备验证机制,任何人都可以上传文件,存在安全问题,所以不再使用。
- DELETE
DELETE 方法用于删除服务器上的资源,但是,HTTP/1.1 的 DELETE 方法本身和 PUT 方法一样不带验证机 制,所以不再使用。
- OPTIONS
用来询问服务器,指定 URI 资源下支持的请求方法有哪些,返回请求头Allow,对应的值是GET、POST等请求方法。
- TRACE
客户端在发送请求时,可能会经过防火墙、代理服务器、网关等应用程序,这些应用程序可能对请求报文做修改,TRACE 方法提供了一种让客户端能够知道服务器最终收到的请求的样子,即返回的响应中,包括了请求报文。TRACE 方法容易引发 XST(跨站追踪),所以一般 web 服务器会禁止这个方法。
- CONNECT
CONNECT 方法用于开启一个双向通信,它通常用于打开网络隧道,隧道的作用是让两台因网络受限的计算机能够正常通信,因为 CONNECT 方法可控性不强,所以一般只开放访问 SSL(Secure Sockets Layer,安全套接层)和 TLS(Transport Layer Security,传输层安全) 的 443 端口网站。
当客户端无法直接访问某个网站,用CONNECT方法将目标地址和端口号发送到代理服务器,代理服务器收到CONNECT请求后,会在指定端口上建立与目标服务器的TCP连接,然后返回HTTP/1.1 200 Connection Established响应,告知客户端已经建立网络隧道。再之后客户端发送请求时,就是通过代理去请求目标服务器,然后再返回给客户端了。
下面是建立 TCP 连接时的请求头的例子
CONNECT www.web-tinker.com:80 HTTP/1.1
Host: www.web-tinker.com:80
Proxy-Connection: Keep-Alive
Proxy-Authorization: Basic "这里是base64编码的用户名和密码"
Content-Length: 0
其中Proxy-Authorization解释一下,有时连接代理服务器需要权限,这里填写用户名和密码的 base64 字符串。
当成功建立 TCP 连接后,返回
HTTP/1.1 200 Connection Established
···
···
状态码
- 1XX 继续
| 状态码 | 含义 |
|---|---|
| 100 | 请求的一部分已经被服务器接收,请接着发送剩下的请求部分。 |
| 101 | 服务器已经理解了该请求,将从响应首部中的 upgrade 通知客户端用其他协议完成这个请求。 |
| 102 | 代表处理将被继续执行 |
- 2XX 请求成功
| 状态码 | 含义 |
|---|---|
| 200 | 请求成功,响应首部和响应体将随着响应报文一起返回 |
| 204 | No Content,服务器成功处理了请求,但不返回任何实体内容, 只通过实体首部返回新的或更新后的元信息,客户端或浏览器不做任何界面上的更新。 |
| 206 | 范围请求,在下载大文件时,通常会使用此类响应来实现断点续传。 |
- 3XX 重定向
| 状态码 | 含义 |
|---|---|
| 301 | 永久重定向,服务端返回 301 时表示,请求时使用的 URI 将被永远替换为新 URI,客户端可以存储新 URI,下次直接用新的。 |
| 302 | 临时重定向,表示请求的资源已被分配了新的 URI,希望 用户(本次)能使用新的 URI 访问。 |
| 303 | 也是临时重定向,表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。 |
| 304 | 这个 304 不属于重定向,只是被放在 3XX 这里的,服务器端允许请求访问资源,但未满足条件的情况。304 状态码返回时,不包含任何响应的主体部分。304 虽然被划分在 3XX 类别中,但是和重定向没有关系。 |
| 307 | 该状态码与 302 Found 有着相同的含义。尽管 302 禁止 POST 变换成 GET,但实际使用时大家并不遵守。 |
当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET,并删除请求报文内的主体,之后请求会自动再次 发送。 301、302 标准是禁止将 POST 方法改变成 GET 方法的,但实际使 用时大家都会这么做。
- 4XX 客户端状态码
| 状态码 | 含义 |
|---|---|
| 400 | 客户端语义或参数错误,无法被服务器理解。 |
| 401 | 请求需要身份验证,再次请求需要带上身份信息。 |
| 403 | 请求被禁止,服务器已经理解这个请求,但是拒绝执行,客户端不应再次发送请求。 |
| 404 | 请求的服务器资源不存在,该状态码被广泛应用于服务器拒绝请求,且不愿意说明原因的情况。 |
- 5XX 服务器状态码
| 状态码 | 含义 |
|---|---|
| 500 | 服务器出现无法预料的错误,一般服务端代码出现问题时出现。 |
| 503 | 该状态码表明服务器暂时处于超负载或正在进行停机维护,现在无法 处理请求。如果事先得知解除以上状况需要的时间,最好写入 RetryAfter 响应首部字段再返回给客户端。 |
http缓存
HTTP 缓存有两种实现方式,分别是强制缓存和协商缓存。
强制缓存
强缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期:
Cache-Control, 是一个相对时间;Expires,是一个绝对时间;
如果 HTTP 响应头部同时有 Cache-Control 和 Expires 字段的话,Cache-Control 的优先级高于 Expires。
实际缓存使用流程如下:
- 当浏览器第一次请求访问服务器资源时,服务器会在返回这个资源的同时,在 Response 头部加上 Cache-Control,Cache-Control 中设置了过期时间大小;
- 浏览器再次请求访问服务器中的该资源时,会先通过请求资源的时间与 Cache-Control 中设置的过期时间大小,来计算出该资源是否过期,如果没有,则使用该缓存,否则重新请求服务器;
- 服务器再次收到请求后,会再次更新 Response 头部的 Cache-Control。
协商缓存
当我们在浏览器使用开发者工具的时候,你可能会看到过某些请求的响应码是 304,这个是告诉浏览器可以使用本地缓存的资源,通常这种通过服务端告知客户端是否可以使用缓存的方式被称为协商缓存。
https安全
HTTPS 可以总结为HTTPS = HTTP + 加密处理 + 证书认证 + 完整性保护,那完整性保护是如何保证的?
实际上在上面的流程中,从应用层发送数据时,会附加一种叫 MAC(Message Authentication Code) 的报文摘要,MAC 能够查知报文是否遭到篡改,从而保护报文的完整性。
持久连接
HTTP 持久连接(HTTP persistent connection,也称作 HTTP keep-alive 或 HTTP connection reuse。
浏览器浏览一个包含多张图片的 HTML 页面时,在发送请求访问 HTML 页面资源的同时,也会请求该 HTML 页面里包含的其他资源。如果每次请求都会造成无谓的 TCP 连接建立和断开,会增加通信量的开销。如图所示
持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
Http1.1 以后,Keep-Alive已经默认支持并开启。客户端(包括但不限于浏览器)发送请求时会在 Header 中增加一个请求头Connection: Keep-Alive,当服务器收到附带有Connection: Keep-Alive的请求时,也会在响应头中添加 Keep-Alive。
持久连接的优缺点如下:
- 优点
- 节省了服务端 CPU 和内存适用量
- 降低拥塞控制 (TCP 连接减少)
- 减少了后续请求的延迟(无需再进行握手)
- 缺点
- 对于某些低频访问的资源 / 服务,比如一个冷门的图片服务器,一年下不了几次,每下一次连接还保持就比较浪费了(这个场景举的不是很恰当)。
- Keep-Alive 可能会非常影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间,额外占用了服务端的连接数。
如何断开持久保持的连接?
HTTP/1.1 版本的默认连接都是持久连接。为此,客户端会在持久连接上连续发送请求。 当想断开持久连接时,有两种方式
- 通过 Keep-Alive Timeout 标识,如上图所示,10s 后关闭持久连接
- 当服务器端想明确断开连接时,可指定 Connection 首部字段的值为 Close。
管线化
持久连接使得多数请求以管线化(pipelining)方式发送成为可能。从前发送请求后需等待并收到响应,才能发送下一个请求。管线化技术出现后,不用等待响应亦可直接发送下一个请求。
这样就能够做到同时并行发送多个请求,而不需要一个接一个地等待 响应了。
但是服务器必须按照接收请求的顺序发送对这些管道化请求的响应。
如果服务端在处理 A 请求时耗时比较长,那么后续的请求的处理都会被阻塞住,这称为「队头堵塞」。
所以,HTTP/1.1 管道解决了请求的队头阻塞,但是没有解决响应的队头阻塞。
实际上 HTTP/1.1 管道化技术不是默认开启,而且浏览器基本都没有支持。总之 HTTP/1.1 的性能一般般,后续的 HTTP/2 和 HTTP/3 就是在优化 HTTP 的性能。
HTTP/1.1、HTTP/2、HTTP/3演变
这里只是概括总结,详细内容在### HTTP/1.1、HTTP/2、HTTP/3演变。
HTTP1.1
HTTP/1.1相比于1.0有以下改变:
- 增加了长连接,避免通信开销
- 支持管线化传输,减少整体的响应时间
但 HTTP/1.1 还是有性能瓶颈:
- 未压缩:请求 / 响应头部(Header)未经压缩就发送,首部信息越多延迟越大。只能压缩
Body的部分 - 队头阻塞:管线虽然解决了
请求的队头阻塞问题,但没有解决响应的队头阻塞。 - 首部冗余:互相发送相同的首部造成的浪费较多
- 请求只能从客户端开始,服务器只能被动响应。
HTTP/2
HTTP/2作了如下优化:
- 头部二进制化:对计算机友好,不必进行转换
- HPack 算法:解决了
首部冗余问题,提升通信速度 Stream并发传输:解决了队头阻塞问题(依然存在,不过问题出现在TCP协议)- 服务器推送:以前从服务器请求 HTML 文件,如果 HTML 依赖 CSS 来渲染页面,这时客户端还要再发起获取 CSS 文件的请求,需要两次消息往返,在 HTTP/2 中,客户端在访问 HTML 时,服务器可以直接主动推送 CSS 文件,减少了消息传递的次数。
HTTP/2 也有缺陷:
HTTP/2是基于 TCP 协议来传输数据的,TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且连续的,这样内核才会将缓冲区里的数据返回给 HTTP 应用,那么当「前 1 个字节数据」没有到达时,后收到的字节数据只能存放在内核缓冲区里,只有等到这 1 个字节数据到达时,HTTP/2 应用层才能从内核中拿到数据,这就是 HTTP/2 队头阻塞问题。
HTTP/3(推广缓慢)
推广缓慢原因是:
QUIC 是新协议,对于很多网络设备,根本不知道什么是 QUIC,只会当做 UDP,这样会出现新的问题,因为有的网络设备是会丢掉 UDP 包的,而 QUIC 是基于 UDP 实现的,那么如果网络设备无法识别这个是 QUIC 包,那么就会当作 UDP包,然后被丢弃。
HTTP/2队头阻塞的问题是因为 TCP,所以 HTTP/3 把 HTTP 下层的 TCP 协议改成了 UDP!
QUIC 有以下 3 个特点:
- 无队头阻塞:因为改用了UDP
- 更快的连接建立:以前需要 TCP 三次握手后,再 TLS 三次握手,
QUIC只需要三次握手建立连接,因为QUIC内部包含了TLS。 - 连接迁移
- 基于 TCP 传输协议的 HTTP 协议,是通过四元组(
源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。 于是当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立连接。 - QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID 来标记通信的两个端点,因此不受 IP 变化影响。
- 基于 TCP 传输协议的 HTTP 协议,是通过四元组(
3. 应用
ping
上文也提到了,ping命令是通过ICMP协议的回送消息实现,如图所示
实现 ping 命令需要以下步骤
- DNS
- 构建 IP 协议
- 构建 icmpheader
- 发送
- 接收
- 从 buffer 分离 icmp 消息
- 校验 icmp 包
- 计算时间
traceroute
traceroute 则充分利用ICMP的差错报文类型,通过设置特殊的 TTL,来追踪去往目的地时沿途经过的路由器。
它的原理就是利用 IP 包的生存期限 从 1 开始按照顺序递增的同时发送 UDP 包,强制接收 ICMP 超时消息的一种方法。
目前有两种实现方式基于 UDP 实现和基于 ICMP 实现。Traceroute基于UDP实现和ICMP实现
基于UDP实现
在基于 UDP 的方案中,traceroute 使用了一个大于 30000 的端口号,服务器在收到这个数据包的时候会返回一个端口不可达的 ICMP 错误信息,客户端通过判断收到的错误信息是type = 11,TTL 超时还是type = 3,端口不可达来判断数据包是否到达目标主机。
但 UDP 实现有个弊端:经常出现***,原因如下:
使用 UDP 的 traceroute,失败还是比较常见的。这常常是由于,在运营商的路由器上,UDP 与 ICMP 的待遇大不相同。为了利于 troubleshooting,ICMP 的request 和 replay 是不会封的,而 UDP 则不同。UDP 常被用来做网络攻击,因为 UDP 无需连接,因而没有任何状态约束它,比较方便攻击者伪造源 IP、伪造目的端口发送任意多的 UDP 包,长度自定义。所以运营商为安全考虑,对于 UDP 端口常常采用白名单 ACL,就是只有 ACL 允许的端口才可以通过,没有明确允许的则统统丢弃。比如允许 DNS/DHCP/SNMP 等。
Linux 和 MacOS 平台上的 traceroute 都是基于 UDP 实现的。
基于ICMP实现
发送icmp包,类型为8,每个路由返回的icmp包类型是11的超时包,当到达目的地址时,目的地址会 replay 类型为0回送应答消息的包。
基于 ICMP 实现的 Traceroute 也有可能出现
***。
在 mac 的 terminal 中,输入traceroute -I baidu.com 就是采用ICMP协议的方式做traceroute。
4. Cookie和Session
HTTP 是一种不保存状态,即无状态(stateless)协议。HTTP 协议自 身不对请求和响应之间的通信状态进行保存。也就是说在 HTTP 这个 级别,协议对于发送过的请求或响应都不做持久化处理。
可是,随着 Web 的不断发展,因无状态而导致业务处理变得棘手的 情况增多了。比如,用户登录到一家购物网站,即使他跳转到该站的其他页面后,也需要能继续保持登录状态。针对这个实例,网站为了能够掌握是谁送出的请求,需要保存用户的状态。
HTTP/1.1 虽然是无状态协议,但为了实现期望的保持状态功能,于是引入了 Cookie 技术。有了 Cookie 再用 HTTP 协议通信,就可以管理状态了。
Cookie
- HTTP 是无状态的协议(对于事务处理没有记忆能力,每次客户端和服务端会话完成时,服务端不会保存任何会话信息)
- cookie 存储在客户端: cookie 是服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。
- cookie 是不可跨域的: 每个 cookie 都会绑定单一的域名,无法在别的域名下获取使用,一级域名和二级域名之间是允许共享使用的(靠的是 domain)
Cookie 会根据从服务器端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,通知客户端保存 Cookie。当下次客户端再往该服务器 发送请求时,客户端会自动在请求报文中加入 Cookie 值后发送出 去。
服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到之前的状态信息。
- 没有 Cookie 信息状态下的请求
- 第 2 次以后(存有 Cookie 信息状态)的请求
上图展示了发生 Cookie 交互的情景,HTTP 请求报文和响应报文的内容如下:
- 请求报文(没有 Cookie 信息的状态)
- 响应报文(服务器端生成 Cookie 信息)
GET /reader/ HTTP/1.1
Host: hackr.jp *首部字段内没有Cookie的相关信息
HTTP/1.1 200 OK
Date: Thu, 12 Jul 2012 07:12:20 GMT
Server: Apache
<Set-Cookie: sid=1342077140226724; path=/; expires=Wed, 10-Oct-12 07:12:20 GMT>
Content-Type: text/plain; charset=UTF-8
- 请求报文(自动发送保存着的 Cookie 信息) 有关请求报文和响应报文内 Cookie 对应的首部字段,
Session
- session 是另一种记录服务器和客户端会话状态的机制
- session 是基于 cookie 实现的,session 存储在服务器端,sessionId 会被存储到客户端的cookie 中
session 认证流程:
- 用户第一次请求服务器的时候,服务器根据用户提交的相关信息,创建对应的 Session
- 请求返回时将此 Session 的唯一标识信息 SessionID 返回给浏览器
- 浏览器接收到服务器返回的 SessionID 信息后,会将此信息存入到 Cookie 中,同时 Cookie 记录此 SessionID 属于哪个域名
- 当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作。
根据以上流程可知,SessionID 是连接 Cookie 和 Session 的一道桥梁,大部分系统也是根据此原理来验证用户登录状态。
两者区别
- 安全性: Session 比 Cookie 安全,Session 是存储在服务器端的,Cookie 是存储在客户端的。
- 存取值的类型不同:Cookie 只支持存字符串数据,想要设置其他类型的数据,需要将其转换成字符串,Session 可以存任意数据类型。
- 有效期不同: Cookie 可设置为长时间保持,比如我们经常使用的默认登录功能,Session 一般失效时间较短,客户端关闭(默认情况下)或者 Session 超时都会失效。
- 存储大小不同: 单个 Cookie 保存的数据不能超过 4K,Session 可存储数据远高于 Cookie,但是当访问量过多,会占用过多的服务器资源。
二、常见面试题
键入网址到网页显示,期间发生了什么
为什么http 是三次握手建立连接,却不是三次挥手断开连接?
答案是为了确保数据传输的完整性。
- 首先TCP 是可以双向传输数据的,也就是全双工协议。所以双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。
- 关闭连接时,客户端向服务端发送FIN后,仅仅表示客户端不再发送数据了但是还能接收数据。
- 服务端收到FIN报文时,先回一个ACK表示收到断开请求,然后处理一些待理完和待发送的数据,等没有待处理和待发送数据了,才发送 FIN 报文给客户端来表示同意现在关闭连接。
为什么 TIME_WAIT 等待的时间是 2MSL?
MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。
MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。
TTL 的值一般是 64,Linux 将 MSL 设置为 30 秒,意味着 Linux 认为数据报文经过 64 个路由器的时间不会超过 30 秒,如果超过了,就认为报文已经消失在网络中了。
TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。
比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 FIN 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。
可以看到 2MSL时长 这其实是相当于至少允许报文丢失一次。比如,若 ACK 在一个 MSL 内丢失,这样被动方重发的 FIN 会在第 2 个 MSL 内到达,TIME_WAIT 状态的连接可以应对。
为什么不是 4 或者 8 MSL 的时长呢?你可以想象一个丢包率达到百分之一的糟糕网络,连续两次丢包的概率只有万分之一,这个概率实在是太小了,忽略它比解决它更具性价比。
2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。
在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒。
其定义在 Linux 内核代码里的名称为 TCP_TIMEWAIT_LEN:
#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT
state, about 60 seconds */
如果要修改 TIME_WAIT 的时间长度,只能修改 Linux 内核代码里 TCP_TIMEWAIT_LEN 的值,并重新编译 Linux 内核。
为什么基于TCP的IM依然要心跳保活?
TCP 中的 KeepAlive 是心跳监测,定时发送一个空的TCP Segment,来监测连接是否存活。
- KeepAlive 只是保证连接的存在,无法探知可用性(比如服务器 CPU 负载 100%,无法响应任何请求了),所以需要心跳和重连机制,这里的机制会影响性能和耗电;
- 还有一个原因是 keepAlive 对服务器负载比较重;
- socket 代理不支持 keepAlive;
- 国内移动无线网络运营商在链路上5分钟内没有数据通讯后, 会淘汰NAT表中的对应项, 造成链路中断。
URL和URI的区别
URI(Uniform Resource Identifier)统一资源标识符,我们通常听过的是URL(Uniform Resource Locator),那什么是 URI 呢?RFC2396对这三个词的定义如下:
- Uniform:规定统一的格式方便处理不同类型的资源,而不用根据环境来识别资源不同的访问方式。另外,加入新增的协议方案(如 http: 或 ftp:)也更容易。
- Resource:资源的定义是可标识的任何东西,不论这个资源是文档、图像还是服务,甚至是资源的集合也行。
- Identifier:标识符,表示可标识的对象。
官方的定义的URI就是,它在某个协议方案(http,ftp 等)下,用某种方式(http://www.ietf.org/rfc/rfc2396.txt)能够标识唯一资源,那这就是 URI,但这个看着也像 URL 啊?怎么理解呢,URI 和 URL 有什么不同呢?举个例子
比如我们在淘宝买东西,商家通过的姓名+手机号能够唯一标识买家,那么 URI 就是姓名+手机号;而商家也可以通过物流的地址(北京市朝阳区建国路 93 号万达广场 9 号楼 3 单元 102)来标识买家,那么 URL 就是这个地址。
综上所述,URL 是 URI 的子集,在某个协议方案下,URL 以定位的方式实现了 URI,它标识了资源在互联网中的位置。
下面再来看一下 URI 的格式
http://user:pass@www.example.com:8080/dir/index.html?uid=1#ch1
上面是绝对 URI 格式,其中的
http(必填项)协议方案名:使用http或https协议方案请求资源时,要写上协议方案名外加一个冒号:。user:pass(可选项)是登录信息:在请求服务器资源时,用于登录服务器。www.example.com(必填项)是服务器地址:服务器地址可以用www.example.com这种可以被DNS解析的名称,也可以直接用192.168.1.1这种IPv4地址名,还可以是[0:0:0:0:0:0:0:1]这种IPv6地址名。8080(可选项)服务器端口号:若用户省略,则使用默认端口号。/dir/index.html(可选项)带层次的文件路径:指服务器下的文件路径。uid=1(可选项)查询字符串:在获取到指定资源后,可以通过查询字符串传入参数。#ch1(可选项)片段标识符:用于获取资源中的子资源,比如文档中的某个位置。
三、题外话
什么是token
摘自傻傻分不清之 Cookie、Session、Token、JWT
Acesss Token
- 访问资源接口(API)时所需要的资源凭证
- 简单 token 的组成: uid(用户唯一的身份标识)、time(当前时间的时间戳)、sign(签名,token 的前几位以哈希算法压缩成的一定长度的十六进制字符串)
- 特点:
- 服务端无状态化、可扩展性好
- 支持移动端设备
- 安全
- 支持跨程序调用
- token 的身份验证流程:
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
- 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
- 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里
- 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据。用解析 token 的计算时间换取 session 的存储空间,从而减轻服务器的压力,减少频繁的查询数据库
- token 完全由应用管理,所以它可以避开同源策略
Refresh Token
- 另外一种 token——refresh token
- refresh token 是专用于刷新 access token 的 token。如果没有 refresh token,也可以刷新 access token,但每次刷新都要用户输入登录用户名与密码,会很麻烦。有了 refresh token,可以减少这个麻烦,客户端直接用 refresh token 去更新 access token,无需用户进行额外的操作。
- Access Token 的有效期比较短,当 Acesss Token 由于过期而失效时,使用 Refresh Token 就可以获取到新的 Token,如果 Refresh Token 也失效了,用户就只能重新登录了。
- Refresh Token 及过期时间是存储在服务器的数据库中,只有在申请新的 Acesss Token 时才会验证,不会对业务接口响应时间造成影响,也不需要向 Session 一样一直保持在内存中以应对大量的请求。