最近复习过程中积累的笔记,本文内容主要是关于 TCP/IP 协议各层知识点。内容来源五花八门,如有不妥或错误欢迎指出。
TCP/IP 网络模型
- 应用层,提供特定于应用服务的协议,如 DNS,HTTP,FTP(文件传输协议),IMAP(电子邮件客户端检索协议)。浏览器通过
DNS获取到 IP 地址后通过调用Socket库来委托协议栈工作。协议栈的上半部分包括TCP协议和UDP协议,下半部分包括IP协议。 - 传输层,负责端到端的通信,如TCP,UDP。
- 网络层,使用 IP 地址将数据包发送到特定的计算机,如 IP协议。
- 网络接口层,负责二进制数据包在物理网络中的传输。
OSI 七层模型
- 应用层,负责给应用程序提供统一的接口;
- 表示层,负责把数据转换成兼容另一个系统能识别的格式;
- 会话层,负责建立、管理和终止表示层实体之间的通信会话;
- 传输层,负责端到端的数据传输;
- 网络层,负责数据的路由、转发、分片;
- 数据链路层,负责数据的封帧和差错检测,以及 MAC 寻址;
- 物理层,负责在物理网络中传输数据帧;
从输入URL到页面加载
- 首先进行 URL 解析。
- DNS 将域名解析为 IP 地址。
- 通过 TCP 三次握手建立可靠连接。
- 如果是 HTTPS 连接,进行 TLS 握手。
- 客户端发送 HTTP 请求。
- 服务端处理 HTTP 请求、返回响应。
- 浏览器接收响应,解析渲染页面。
HTTP 与 HTTPS 的区别?
HTTP 连接是无状态的。
HTTP 协议是明文传输,客户端与服务端之间通信的内容可以被直接截获,默认端口为 80。HTTPS 协议相比于前者更加安全,加入了 SSL 加密传输协议,默认端口为 443。
HTTPS 连接流程:如果使用 HTTPS 协议,那么在 TCP 三次握手之后,会开始 TLS 握手,传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的,会经过四次 TLS 握手。
- 客户端向服务器发起
ClientHello加密通信请求,包含客户端使用的 TLS 版本号、支持的密码套件列表和生成的随机数(Client Server)。 - 服务端器接收到请求之后,会确认 TLS 版本号是否支持,并选择一个密码套件,生成随机数(Server Random),接着发送
ServerHello响应,服务端还会给客户端发送数字证书,并发送Servcer Certificate消息,随后发送ServerHelloDone消息。 - 客户端验证数字证书合法性,并生成新的随机数(pre-master),使用数字证书中的 RSA 公钥加密该随机数,发送
Clien Key Exchange。至此,双方共享了三个随机数并生成会话密钥(Master Secret)用于对后续的 HTTP 请求数据加解密,客户端发送Encrypted Handshake Message(Finishd)消息。 - 服务端进行同样的操作,发送
Change Cipher Spec和Encrypted Handshake Message(Finishd),之后客户端和服务端之间利用生成的密钥进行对称加密。
公钥和私钥是非对称加密中的一组密钥,通常用于数据加密、数字签名、身份认证等。核心思想是:公钥加密,私钥解密(用于加密数据);私钥签名,公钥验证(用于数字签名)。
数字证书用于认证公钥持有者(服务器)的身份,以防止第三方进行冒充。通常包含了公钥、持有者信息、证书认证机构(CA)的信息、CA 对这份文件的数字签名及使用的算法、证书有效期和一些其他额外信息。数字签名的签发和验证流程是:首先,CA 将信息打包并用 Hash 算法生成 Hash 值,接着用私钥签名生成 Certificate Signature。客户端验证时是将这些信息用同样的 Hash 算法生成 Hash 值 H1,服务端用公钥验证 Certificate Signature 得到 Hash 值 H2,比较 H1 和 H2 如果相同则为可靠证书。
通常我们在 TLS 加密中得到的数字证书不是根证书(为了确保根证书的安全性),而是由中间证书。于是存在证书信任链的问题:浏览器和操作系统内置了一个受信任的根证书列表,当我们收到一个中间证书,会沿着证书链逐级验证,最终必须追溯到一个信任的根证书才会确认该证书可信。
中间人攻击(Man-in-the-Middle Attack, MITM)就是在 HTTPS 加密通信中,攻击者伪造证书冒充服务器,进而篡改或窃取通信数据的⾏为。
HTTP/1.0 1.1 2 3 的演变
-
HTTP/1.0 是短连接,在一个 TCP 连接中进行一次 HTTP 请求并收到响应之后,需要再次建立 TCP 连接,导致高延迟。并且⼀个 IP 只能绑⼀个域名,不支持虚拟主机。
-
HTTP/1.1 于1997年提出,允许一个 TCP 连接中可以多次发起 HTTP 请求,用长连接的方式改善了短连接造成的性能开销。
但是,目前的 HTTP/1.1 依然有很大的性能问题:
主要的性能问题是高延迟。虽然 HTTP/1.1 支持了长连接,但是要求一次 HTTP 请求收到响应之后,才能发起第二次 HTTP 请求,造成了 HTTP 队头阻塞。而且浏览器对 TCP 连接有最大并发数限制(为了防止 ddos 攻击),例如谷歌浏览器最大并发连接数是 6 个,再加上 TCP 握手慢启动的耗时,都会造成更高的延迟。虽然 HTTP/1.1 支持管道(pipeline)传输,也就是可以同时发送多个请求,但是管道运输要求按照发送的顺序接收响应,这造成实际应用困难,因此很少使用。
另一个性能问题是 HTTP 头部开销巨大。由于 HTTP是无状态的(Stateless),这意味着服务器不会主动记录每次 HTTP 请求的上下文状态,因此浏览器的每次请求都需要携带 HTTP 头部。
HTTP/1.1 也不支持服务器推送。虽然 TCP 提供了全双工双向通信,但是 HTTP/1.1 并没有充分利用这个能力,只允许单向的浏览器请求、服务器响应,当客户端需要获取通知时,只能通过定时器不断地拉取消息(Websocket 也是为了应对这个问题)。
我们可以用一些方法来优化 HTTP/1.1 的性能:
第一是使用缓存来避免不必要的请求,例如强制缓存与协商缓存将资源缓存在本地磁盘。
第二也是减少不必要的请求,例如按需加载资源、将多个小资源合并成大资源来一次请求,如精灵图(雪碧图)。
第三是压缩响应数据的大小,从而减少传输时间。可以使用请求头
Content-Encoding表示浏览器支持的压缩方法,服务端据此返回压缩后的数据。 -
HTTP/2 于2015年提出,支持多路复用、头部压缩、服务器推送、二进制格式来应对 HTTP/1.1 的各种问题。
多路复用:在同一个 TCP 连接中,可以同时传输多个请求和响应,并且不需要等待其他请求完成 ,避免了 HTTP 队头阻塞。这种并发主要是通过 Stream、Message、Frame 这三个概念实现的,具体是指一个 TCP 连接中包含一个或者多个 Stream,不同 Stream 的帧可以乱序发送,因为每个帧的头部会携带 Stream ID 信息,所以接收端可以通过 Stream ID 有序组装成 HTTP 消息。Stream 里可以包含 1 个或多个 Message,Message 指的是一个请求或响应,Message 里包含一条或者多个 Frame,是以二进制压缩格式存放的头部或消息体。但是 HTTP/2 存在 TCP 队头阻塞问题,即 TCP 需要保证顺序,如果发生丢包就会触发超时重传机制,其他的所有响应要等找到这个丢失的包才能传输。
头部压缩(静态表+动态表+Haffman编码) :如果同时发出多个请求,他们的头是一样的或是相似的,协议会消除重复的部分。并且 HTTP/2 为高频出现在头部的字符串和字段建立了一张包含 61 组头部信息的固定静态表,静态表用长度较小的索引号(index)表示重复的头部字段名(Header Name)和值(Header Value),再用 Huffman 编码压缩数据,Huffman 编码的原理是将高频出现的信息用较短的编码表示,从而缩减字符串长度。不在静态表范围内的头部字符串就要自行构建动态表,动态表生效的前提是在同一个连接上,重复传输完全相同的 HTTP 头部,如果头部只发送一次或每次略有不同,动态表就无法很好地利用。
二进制格式:HTTP/1.1 使用纯文本形式的报文,HTTP / 2 使用二进制形式的帧(frame),分为头信息帧(Headers frame)和数据帧(Data frame)。
服务器推送:服务端不再是被动地响应,可以主动向客户端发送消息。
但是因为技术迁移成本以及 TCP 队头阻塞等问题,HTTP2 至今并未得到广泛使用。
-
HTTP/3 于2019年提出,使用 UDP 代替 TCP 来避免可能存在的 TCP 队头阻塞,并基于 UDP 协议在应用层实现了 QUIC 协议,它具有类似 TCP 的连接管理、拥塞窗口、流量控制的网络特性,相当于将不可靠传输的 UDP 协议变成可靠的了。
DNS 与 CDN 是什么?
DNS(域名系统)保存了域名与IP地址的对应关系,DNS 解析是应用层的部分,任务是将人类可读的域名(如 www.example.com )转换为计算机可读的IP地址(如 8.8.8.8)。DNS 会首先检查浏览器中是否存在缓存,如有则直接返回 IP 地址,如没有则继续查询操作系统缓存,如有则直接返回,如没有则继续查询 host 文件缓存,如有则直接返回,如没有则查询 本地 DNS 解析器(通常由 ISP 提供)。如果本地没有缓存,则递归查询根域名服务器、顶级域名服务器、权威域名服务器。
DNS 中的域名都是用句点来分隔的,越靠右等级越高。实际上域名最后还有一个点,比如 www.server.com.,这个最后的一个点代表根域名。也就是,. 根域是在最顶层,它的下一层就是 .com 顶级域,再下面是 server.com。所以域名的层级关系类似一个树状结构:根 DNS 服务器(.)、顶级域 DNS 服务器(.com)、权威 DNS 服务器(server.com)。
CDN(内容分发网络) 提供了一系列边缘服务器,使浏览器可以从最近的边缘服务器获取数据,而不是直接访问源服务器,从而提高加载速度、减轻源服务器压力。
TCP 与 UDP 的区别?
UDP 是无连接、不可靠,面向报文的协议,但开销更小,适用于实时传输,面向报文意味着操作系统不会对消息进行拆分,也就是每个 UDP 报文就是一个用户消息的边界,但可能丢失、重复、乱序。
TCP 是面向连接的可靠的字节流协议。
在一个 TCP 连接中,通信双方会明确 Socket(由 IP 地址和端口号组成)、序列号(解决乱序问题)、窗口大小(用于流量控制)。
TCP 必须经过三次握手才能建立连接,一个 TCP 连接通过四元组源地址、源端口、目的地址、目的端口(也就是两个 Socket,源地址与源端口是一个,目的地址与目的端口是另一个)唯一标识。
我们说 TCP 面向字节流,这意味着消息可能会被操作系统分组成多个 TCP 报文,而 IP 协议传输时也会将较大的数据包分包。因此,TCP 要通过序列号来保证数据准确无误地按序传输。
TCP 还通过 重传机制、流量控制、拥塞控制等特性来保证可靠传输。
重传机制
- 超时重传:TCP 协议下,收到每个数据包时,接收方都会向发送方发送
ACK确认。发送方在发送数据时会设定一个定时器,当超过指定的时间后,没有收到ACK确认应答报文,就会重发该数据。 - 快速重传:接收方如果收到一个乱序的数据包(比如收到了Seq=5的包,但Seq=4还没到),会立即重复发送上一个已经确认的包的ACK。发送方如果连续收到3个重复的ACK,就推断出这个ACK后面紧跟的那个数据包很可能丢失了,于是立刻重传那个被认为丢失的数据包。
- 选择确认(Sack):配合快速重传,能精确重传多个丢失包。
流量控制:防止接收方缓冲区溢出。接收方在每次发送ACK确认报文时,都会在TCP首部带上接收窗口 rwnd。发送方则维护发送窗口不能超过接收方的接收窗口大小。
拥塞控制:防止过多的数据导致网络压力过载。发送方有慢启动,通过感知网络状态,主动自我限制发送速率,维护 拥塞窗口 cwnd 。真正的发送窗口大小 = min(rwnd, cwnd)。
IP 协议有什么用?
IP 协议的任务在于寻址和路由。
IP地址:IP 地址分为网络号和主机号,网络号标志该 IP 地址属于哪一个子网,主机号标志同一子网下的不同主机。例如 10.100.122.0/24 中,10.100.122.0 是 IP 地址,/24 是子网掩码 255.255.255.0 。子网掩码与 IP 地址按位与运算,得到网络号;子网掩码取反后与 IP 地址进行按位与运算,得到主机号。寻址过程中,先匹配到相同的网络号,才会去找对应的主机。
IPv4与IPv6:IPv4有地址长度为32位,有2^32个地址数量,地址表示为点分十进制(192.168.1.1) 。IPv6有地址长度为128位,有2^128个地址数量,地址表示为冒分十六进制(2001:0db8::1) 。
ICMP和ARP:IP 中还有ICMP 协议和 ARP 协议,ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息,ARP 用于根据 IP地址 查询相应的 MAC地址。
常见HTTP状态码
1xx:信息状态码,表示请求已被接收,需要继续处理
- 100 Continue 客户端应继续发送请求的剩余部分
- 101 Switching Protocols 服务器同意切换协议
- 102 Processing 服务器已收到请求,正在处理但尚未完成
- 103 Early Hints 在完整响应之前返回一些响应头,用于预加载关键资源
2xx:成功
- 200 OK 请求成功,服务器返回正常数据
- 201 Created 请求成功,并创建了新的资源(常用于 POST / PUT 请求)
- 202 Accepted 服务器已接受请求,但尚未处理完成(异步任务)
- 204 No Content 请求成功,但服务器没有返回内容(常用于 DELETE 请求)
- 206 Partial Content 服务器返回的 body 数据是资源的部分内容而非全部(常用于断点续传)
3xx:重定向状态码
- 301 Moved Permanently 永久重定向,说明请求的资源已经不存在,需要用新的 URL 再次访问并缓存新的 URL
- 302 Found / Moved Temporarily 临时重定向,说明请求的资源存在,但是需要暂时用其他 URL 访问
- 303 See Other 重定向到其他资源,常用于 POST / PUT 方法的响应中
- 304 Not Modified 资源未修改,用于缓存控制
- 307 Temporary Redirect 临时重定向,类似于302,但必须使用原请求方法
- 308 Permanent Redirect 永久重定向,类似于301,但必须使用原请求方法
4xx:客户端错误
- 400 Bad Request 客户端请求的语法错误(如查询参数错误)
- 401 Unauthorized 合法请求,但未授权(用户未登录),需要提供身份认证信息
- 403 Forbidden 合法请求,但服务器拒绝访问(用户无权限)
- 404 Not Found 页面找不到,一般是请求路径错误
5xx:服务端错误
- 500 Internal Server Error 服务器内部错误,一般是代码异常
- 502 Bad Gateway 网关错误,服务器充当网关或者代理的角色时,从上游服务器收到一个无效的响应
- 503 Service Unavailable 服务器不可用(如服务器过载、维护)
- 504 Gateway Timeout 网关超时,服务器充当网关或者代理的角色时,未能从上游服务器及时收到响应
- 505 HTTP Version Not Supported 服务器不支持请求的 HTTP 版本
常见HTTP头
客户端请求头
GET ws://api.other-domain.com/chat HTTP/1.1
Host: example.com // 目标域名
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) // 客户端信息
Accept: text/html, application/json // 客户端支持的响应类型
Accept-Encoding: gzip, deflate, br // 客户端支持的压缩算法
Content-Type: application/json // 请求体的类型
Cache-Control: public, max-age=3600 // 强制缓存头
Expires: Wed, 21 Oct 2023 07:28:00 GMT // 强制缓存头
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT // 协商缓存头
If-None-Match: "33a64df551425fcc55e4d42a148795d9" // 协商缓存头
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Cookie: sessionId=abc123; theme=dark
Origin: https://example.com // 域
Access-Control-Request-Method: POST // 预检请求方法
Access-Control-Request-Headers: Content-Type // 预检请求头
Connection: keep-alive // 连接控制
Upgrade: websocket // 协议升级
服务端响应头
GET ws://api.other-domain.com/chat HTTP/1.1
Content-Encoding: gzip // 响应体的压缩算法
Content-Type: text/html; charset=utf-8 // 响应体的类型
Cache-Control: public, max-age=3600 // 强制缓存头
Expires: Wed, 21 Oct 2023 07:28:00 GMT // 强制缓存头
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT // 协商缓存头
ETag: "33a64df551425fcc55e4d42a148795d9" // 协商缓存头
Strict-Transport-Security: max-age=31536000 // 强制HTTPS
Content-Security-Policy: default-src 'self' // 内容安全策略(应对xss)
Access-Control-Allow-Origin: * // 允许的域
Access-Control-Request-Method: POST // 允许的方法
Access-Control-Request-Headers: Content-Type // 允许的头
Access-Control-Allow-Credentials: true // 允许凭据
Set-Cookie: authToken=xyz789; Secure; HttpOnly; SameSite=Strict // HttpOnly防止xss盗取cookie,SameSite防止登录CSRF,Secure要求以HTTPS传输cookie
Location: https://new.example.com // 重定向地址
XSS、CSRF 是什么?
跨站脚本攻击(XSS) 主要指攻击者在网页中注入恶意脚本诱导用户执行,从而窃取数据或执行恶意操作。应对方式主要包括:
-
服务端过滤与转义用户输入。
-
服务端配置使用内容安全策略,使用
Content-Security-Policy响应头,限制浏览器能执行的资源。Content-Security-Policy: default-src 'self' -
服务端在
Set-Cookie设置 HttpOnly,使其不能被脚本读取。
跨站请求伪造(CSRF) 是指攻击者诱导用户在已登录的情况下,向目标网站发送恶意请求,从而伪造用户请求(如转账、修改密码等)。应对方式主要包括:
- 服务端在
Set-Cookie设SameSite=Strict选项,限制 cookie 跨站点传递。 SameSite 有 Strict、Lax 和 None 三个值,Strict 最严格,完全禁止第三方 Cookie,Lax 相对宽松,使用 None 的话在任何情况下都会发送 Cookie。 - 在关键操作如转账、改密码时,要求用户输入密码、验证码进行双重验证。
- 服务端在用户登录或访问页面时,生成随机的 csrf token 放在浏览器网页中,后续请求时浏览器会将这个 token 发送到服务端验证请求站点,恶意网站因为同源策略而不能读取 token 从而无法校验。
TCP三次握手的过程
第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务端,并进入SYN_SENT状态,等待服务器确认。
第二次握手:服务端收到SYN包,发送ACK包(ack=j+1),同时也发送一个自己的SYN包(syn=k),即SYN+ACK包,此时服务端进入SYN_RECV状态;
第三次握手:客户端收到服务端的SYN+ACK包,向服务端发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务端进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
三次握手保证双方都有发送和接收信息的能力。
TCP四次挥手的过程
第一次挥手:客户端停止发送数据,发送连接释放报文(FIN=1,seq=u),进入FIN-WAIT-1(终止等待1)状态。
第二次挥手:服务端收到连接释放报文,发出确认报文(ACK=1,ack=u+1),服务端进入CLOSE-WAIT(关闭等待)状态。此时,服务端还可以向客户端发送数据。客户端收到服务端的确认报文后,进入FIN-WAIT-2(终止等待2)状态,并接收服务端发送的数据。
第三次挥手:服务端将最后的数据发送完毕后,向客户端发送连接释放报文(FIN=1,seq=v),服务端进入了LAST-ACK(最后确认)状态。
第四次挥手:客户端收到服务端的连接释放报文后,发出确认报文(ACK=1,ack=v+1),客户端进入了TIME-WAIT(时间等待)状态,经过2MSL(2*最大报文段生命周期)时间后进入CLOSED(关闭)状态。服务器只要收到了客户端发出的确认,立即进入CLOSED(关闭)状态。
WebSocket
TCP 连接是全双工的,即双方在同一时间都可以主动向对方发送数据。但是通用的 HTTP / 1.1 是半双工,适用于网页文章的场景,只需要客户端请求、服务端响应。对于扫码登录的简单场景,可以用定时轮询、长轮询来伪造服务器推送的效果。但游戏网页是客户端和服务器之间都要互相主动发大量数据的场景,这时就需要基于 TCP 的支持全双工的 WebSocket。
如果要建立 WebSocket 连接,浏览器将在 TCP 三次握手建立连接之后发起一个 HTTP Upgrade Request(HTTP 升级请求)。
GET ws://api.other-domain.com/chat HTTP/1.1
Host: api.other-domain.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 浏览器随机生成的密钥
Sec-WebSocket-Version: 13
Origin: https://your-site.com
如果服务器支持,会返回状态码 101 Switching Protocols 和其他信息。如果浏览器解码返回的字符串一致,则 WebSocket 握手验证通过。
HTTP/1.1 101 Switching Protocols
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=
Upgrade: WebSocket
Connection: Upgrade
为什么要有 RPC?
纯裸 TCP 是能收发数据,但它是个无边界的数据流,易发生粘包等问题,因此上层需要定义消息格式用于定义消息边界。于是,HTTP 和各类 RPC 协议就是在 TCP 之上定义的应用层协议。
RPC 出现更早,早期 HTTP 主要用于 B/S 架构,而 RPC 更多用于 C/S 架构。RPC 的定制化程度更高,gRPC 底层直接用 HTTP / 2 ,性能也更好,因此,企业内部集群的微服务之间可能会采用 RPC 协议进行通讯。
Cookie、Session、Token
Cookie 存储在浏览器,每次请求时会自动附带相关 Cookie。明文存储,容易被 CSRF 攻击窃取。
Session 存储在服务端,每个用户通常有唯一的 Session ID 标识,Session ID 通常存储在 Cookie 中,或通过 URL 参数传递。
Token 储存在客户端(Cookie 或 LocalStorage 中),本质是一个令牌,需要开发者手动通过请求头(如Authorization)携带,因此安全性更高。例如,恶意⽹站⽆法通过诱导浏览器发送请求得到 Token(CSRF 攻击),而是必须先通过 XSS 窃取 Token 再⼿动构造请求,难度⼤幅提升。我们在项⽬中将 setCookie 设置为 HttpOnly: true, SameSite=Strict, secure: true 保护 Cookie,⽽ Token 则结合短期有效期和内存存储,双重保障⽤户安全。