Skip to Content
全部文章前端大杂烩盘点前端和服务端交互协议演进

盘点前端和服务端交互协议演进

前言

现在时间已经到了2025年了,回想这10多年的web相关技术的进步,虽然不像电脑手机更新换代那样感受差异巨大,但是进步也可谓是不可一日而语, 早些年web应用为了实现一个长连接都比较麻烦,使用最多的就是定时器轮询,而如今已经有了socket及延伸技术,sse等一些更便捷的协议可用。

今天突然想总结盘点一下这些年浏览器相关协议的出现及应用。

协议

so,let’s go!

Http

http协议,它的一个交互过程就类似于两个人在聊天,你问一个问题,对方回答你,然后你再问下一个问题,如此循环往复。 问问题的人就是浏览器(客户端),回答问题的就是服务器,通过这种方式将用户和服务器应用程序(数据)链接起来。

下图是http请求的交互时序图,这里需要说明的是,因为http底层还是tcp协议,因此在真正发送http协议数据之前,是需要经过tcp三次握手来建立连接的, 如下图: 1

Tcp协议

从上图可以看到tcp连接需要三次握手,为什么?

  1. 确认连接的两端是可以正常通信的
  2. 数据包序列号的同步、确认,网络中的数据包是零散的,发送、接收的顺序可能因为各种原因而不一致,需要协商本次请求的数据包顺序

因此,每一个请求都需要tcp建立连接之后才能发送http协议的数据包。

Http/1 时代

http1.0时代大概是在96年左右发布,后来99年更新了1.1版本,这个版本的http协议一度使用了很多年,直到现在还是广泛的使用。

http/1的很多概念,头信息、状态码、持久连接、cookie等都是在http/1版本就已经存在,并且沿用至今的http/2、http/3中。

经常说http请求,说http协议,到底http协议是什么?长什么样子?

首先http协议的原始格式通常如:

  • 请求
- 请求行:包含请求方法(如 GET、POST 等)、请求路径和 HTTP 协议版本。 - 请求头字段:包含一系列键值对,用于传递关于请求的附加信息,如Content-Type、Content-Length、User-Agent等,用来告知服务器请求的相关属性和客户端的一些信息。 - 空行:用于分隔请求头和请求体,标志着请求头的结束。 - 请求体:可选部分,包含要提交给服务器的数据,如表单数据、JSON 数据等,具体内容根据Content-Type指定的格式进行编码。
  • 响应
- 状态行:包含 HTTP 协议版本、状态码和状态描述,用于告知客户端请求的处理结果。 - 响应头字段:同样是一系列键值对,提供关于响应的信息,如Content-Type、Content-Length、Date等,用来描述响应数据的属性和服务器的一些信息。 - 空行:分隔响应头和响应体,标志着响应头的结束。 - 响应体:包含服务器返回给客户端的数据,数据格式由Content-Type指定,可能是 HTML、JSON、XML、图片等各种类型的数据。

举个例子,我有一个注册账号的接口 http://test.com/api/register,那么浏览器发送和响应的格式是:

  • 请求
POST /api/register HTTP/1.1 Host: test.com Content-Type: application/x-www-form-urlencoded Content-Length: 44 username=newuser&password=newpass123&email=newuser@example.com

相信前端开发的同学看到这个格式并不会陌生,事实上你在代码里面的请求,最终发送的时候是长这个样子的。

  • 响应
HTTP/1.1 200 OK Content-Type: application/json Content-Length: 34 Date: Thu, 21 Feb 2025 06:00:00 GMT {"status": "success", "message": "注册成功"}

响应示例的数据就多了状态码,浏览器会把这些数据处理之后给到我们的请求回调中,http协议就长这个样子。

这就是Http/1时代。

Http/2

HTTP/2.0 在 2015 年发布,最大的提升是提高了网络的利用、资源传输效率的提升上。

包括多路复用、头部压缩等一系列优化,在早期的web项目中,因为浏览器的限制,同时处于请求中的请求个数有限制,如果你的网站静态资源过多,就可能出现 请求等待、排队的情况,影响了浏览器资源的渲染速度,而这些年的web应用越来越复杂,打包后资源越来越多,这个问题就更加明显了。

因此在http/2 时代,提出了多路复用,多路复用后的请求时序图是这样的

2

这样我们就省去了每个请求都要tcp三次握手建立连接的过程,而是通过一个连接发送多个请求和响应,这样就可以充分利用带宽,提高传输效率。

而在2025年的今天,http/2已经基本普及了。比如随意打开一个百度搜索页面,我们来看看网络请求。

3

可以看到已经大量的h2协议了,通常浏览器和服务器会协商用什么协议,如果支持2.0会自动切换,至于还有些1.1,这个可能就和一些主观的网络优化配置 、混合部署等有关。

Http3.0

HTTP/3.0,基于 QUIC 协议,进一步提升了传输速度和安全性。 现在这个还没普及,兼容性堪忧,暂时就不讨论了,有兴趣的朋友可以去看看相关资料。

Https

https是http协议加上ssl/tls来加密的一种协议,大概2015年普及,到现在基本上就连个人部署服务也会采用https了。

本质上来说,https还是http请求,只是在浏览器和服务器之间加入了一个tls的流程, 之所以要推出https,主要是因为http是明文传输,很容易被监听、篡改,这带来了严重的安全问题。

https的工作流程,这个也是前端面试常考的,https请求流程如下:

  • 省流版:
  1. 服务端像证书颁发机构申请证书,包含一对 非对称公钥密钥
  2. 客户端从服务器获取证书,从证书里拿到非对称公钥,检查证书有效性
  3. 客户端生成一个对称密钥,使用非对称公钥加密数据和这个对称密钥,然后一并发送给服务端
  4. 服务端使用非对称密钥解密收到的数据,然后拿出里面的对称密钥解密真实数据
  5. 服务端使用非对称密钥加密要响应的数据返回给客户端。
💡
Tip

对称密钥:对称密钥是加密和解密都用同一个密钥,速度快,但是安全性低,容易被破解。 非对称密钥:非对称密钥是加密和解密使用不同的密钥,安全性高,但是速度慢,需要加密和解密两步操作,公钥加密的数据,只有私钥才能解密,而私钥加密的数据公钥可以解密。

可以看到这个是一个标准的双重加密的流程,来保证安全性,但是问题也很明显,之所以15年才开始普及的原因如下:

  1. 加密流程繁琐,除了tcp三次握手之外,还要进行证书验证、对称密钥交换等繁琐的流程,效率低。
  2. 加密后数据变大,增加网络开销
  3. 证书颁发昂贵,企业/个人成本高

得益于计算机性能越来越好、网络带宽越来越高、证书颁发成本降低,而对网络安全要求高越来越被重视,于似乎https成为了普及的必然。

WebSocket

WebSocket 是也是基于TCP协议的一种全双工协议,跟Http协议最大的区别是,它不像Http只能“一问一答”,它支持客户端和服务端任何时候相互发送数据, 这对于一些实时性更新、互通信息的应用场景是首要选择,比起http协议,不仅仅是开发难度变低,比起长连接、轮询等方式,可以更实施、低损耗的和服务端交互,例如在 webim这种聊天应用,或者实时gps位置更新,实时大屏应用等。

前端示例:

client.js
// 连接到 WebSocket 服务器 const socket = new WebSocket('ws://localhost:8080'); // 监听连接成功事件 socket.onopen = function () { console.log('已连接到服务器'); }; // 监听服务器发送的消息 socket.onmessage = function (event) { console.log('收到服务端消息',event.data) }; // 监听连接关闭事件 socket.onclose = function () { console.log('与服务器的连接已关闭'); }; // 假如有一个按钮可以点击,发送数据 sendButton.addEventListener('click', function () { const message = '发送数据'; if (message) { // 向服务器发送消息 socket.send(message); } });

服务端示例:

server.js
const WebSocket = require('ws'); // 创建 WebSocket 服务器,监听 8080 端口 const wss = new WebSocket.Server({ port: 8080 }); // 监听客户端连接事件 wss.on('connection', function connection(ws) { console.log('有新的客户端连接'); // 监听客户端发送的消息 ws.on('message', function incoming(message) { console.log('接收到客户端消息:', message); // 向客户端发送响应消息 ws.send('服务器已收到消息: ' + message); }); // 监听客户端断开连接事件 ws.on('close', function close() { console.log('客户端断开连接'); }); }); console.log('WebSocket 服务器正在监听 8080 端口');

当连接建立后,可以在ws tab中看到websocket连接,而每次发送和接收的数据都可以在这里看到,如图:

3

可以看到浏览器首先是发起了一个get的http请求,但是地址是: GET ws://localhost:8080/ 当服务器收到后会返回101状态,101意思是Switching Protocols,就是从http切换协议到ws上。

WS断联问题

通常我们的ws建立之后,会经常遇到断开,这个和不同的浏览器(内核)都有关系,解决办法主要还是添加心跳包。

  1. 增加心跳逻辑,客户端每隔10秒(我遇到过10多秒不使用就会断开的内核)或者更长发送一个ping消息给服务端,服务端收到后返回响应一个pong消息。
  2. 使用一些成熟的ws库,通常内部会做一些断开自动重连的机制

MQTT

mqtt协议在浏览器中的使用不算多见,在浏览器中是基于websocket来实现的,在浏览器中,这个协议更像是对ws的封装,增加了一些逻辑上的功能,比如 采用发布、订阅机制,以及一些消息发送接收的控制,如果你不介意,当然你可以直接使用ws来实现这些逻辑。

SSE

最后值得一说的sse协议,sse是服务器向客户端发送事件的一种协议,它基于http协议,但是和http协议又有一些区别,它是一种长连接协议, 服务器可以持续不断地向客户端发送数据,客户端通过监听事件来接收数据,通常用于实时更新、通知等场景。

比如现在非常火爆的AIGC的打字机效果,就完全可以通过sse协议来实现,SSE协议的特点就是它是单向输出的。

它的工作流程是:

  1. HTTP GET 请求:SSE 是基于 HTTP 协议的,客户端通过发送一个标准的 GET 请求来向服务器请求数据流。这种请求和普通的 HTTP GET 请求没有本质区别,都是通过 GET 方法来请求资源。
  2. 长连接:服务器在接收到 GET 请求后,不会立即关闭连接,而是保持连接打开状态,并持续不断地向客户端发送数据。这种长连接方式允许服务器在事件发生时实时推送数据给客户端。
  3. Content-Type:服务器在响应头中设置 Content-Type 为 text/event-stream,告诉客户端这是一个事件流。浏览器会识别这种内容类型,并以流的方式处理接收到的数据。

示例代码如下:

server.js
const http = require('http'); const server = http.createServer((req, res) => { if (req.url === '/events') { res.writeHead(200, { 'Content-Type': 'text/event-stream',// 设置响应头以启用SSE, 浏览器检查到这个头后会以流的方式处理响应 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }); res.flushHeaders(); // 每隔1秒向客户端发送一条消息 const intervalId = setInterval(() => { const message = `data: ${new Date().toLocaleTimeString()}\n\n`; res.write(message); }, 1000); // 当客户端关闭连接时,清除定时器 req.on('close', () => { clearInterval(intervalId); }); } }); const port = 3000; server.listen(port, () => { console.log(`Server running on port ${port}`); });
client.js
const eventSource = new EventSource('/events'); eventSource.onmessage = (event) => { console.log(event.data);// 每隔1秒就会收到服务端推送的消息 }; eventSource.onerror = (error) => { console.error('EventSource failed:', error); };

client的代码上和ws最大的区别就是,它只有接收数据的回调,没有发送数据的方法。

Polyfill

由于不是所有的浏览器都能支持sse协议,因此最好是使用polyfill兼容库,当浏览器不支持sse时,polyfill会采用一些轮询之类的方式来模拟sse协议。

有兴趣可以看看 https://github.com/Yaffle/EventSource 整个库的实现很简洁,只有一个代码文件。

总结

过去的http1.1时代,C/S架构中可用的子弹很少,只有一个http协议请求,一问一答的形式,随着浏览器的发展,支持的协议越来越多,总体生态环境越来越好, 可以期待未来浏览器和服务器之间有更多更丰富的交互方式,来满足各种场景的需求。

最后编辑于

hi