- 参考链接:zhuanlan.zhihu.com/p/31297574
- 参考链接:blog.csdn.net/androidstar…
- 参考链接:blog.csdn.net/sinat_36422…
- 参考链接:www.liaoxuefeng.com/wiki/102291…
全双工
“全双工”(Full-Duplex)是通信领域的核心概念,指 通信双方(如客户端与服务器)可以同时双向发送数据,不存在 “一方发送时另一方必须等待” 的限制。在前端实时通信中,WebSocket 是典型的全双工协议,这一特性使其区别于 HTTP、SSE 等半双工或单向通信方案。
全双工的核心特点(以 WebSocket 为例)
双向同时通信 客户端和服务器建立连接后,双方可随时独立发送数据,无需等待对方响应。
- 例如:聊天场景中,A 发送消息给 B 的同时,B 也能向 A 发送消息,两者并行不冲突;
- 对比 HTTP:客户端必须等待上一次请求的响应返回后,才能发起下一次请求(半双工);
- 对比 SSE:仅支持服务器向客户端单向推送(单工)。
基于持久连接 全双工依赖 持久连接(连接建立后保持打开状态),避免了频繁建立连接的开销。
- WebSocket 连接通过一次 HTTP 握手建立,之后复用 TCP 连接进行双向数据传输;
- 数据传输时仅需携带少量帧头(约 2-14 字节),效率远高于 HTTP 每次请求的完整头部(数百字节)。
独立的数据通道 客户端到服务器、服务器到客户端的数据流是 独立的两条通道,双方发送的数据不会相互阻塞。
- 例如:服务器正在向客户端推送实时日志的同时,客户端可以随时发送 “暂停推送” 的指令,两者互不干扰。
全双工的核心价值是 打破通信的时序限制,让双方能 “同时说话”,这对于需要低延迟、高频双向交互的场景(如实时协作、游戏)至关重要。WebSocket 作为前端全双工通信的主流方案,通过持久连接和轻量帧结构,高效实现了这一特性,成为实时应用的首选技术。
WebSocket
为什么需要websocket? 疑问? 我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?
答:
- 因为 HTTP 协议有一个缺陷:通信只能由客户端发起
- 我们都知道轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开), 因此websocket应运而生。
简介
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
WebSocket目前支持两种统一资源标志符ws和wss,类似于HTTP和HTTPS。
而且它为我们实现即时服务带来了两大好处:
- 节省资源:互相沟通的Header是很小的-大概只有 2 Bytes。
- 推送信息:不需要客户端请求,服务器可以主动传送数据给客户端。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
特点
全双工 通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合 例如指 A→B 的同时 B→A ,是瞬时同步的 二进制帧 采用了二进制帧结构,语法、语义与 HTTP 完全不兼容,相比 http/2,WebSocket 更侧重于“实时通信”,而 HTTP/2 更侧重于提高传输效率,所以两者的帧结构也有很大的区别 不像 HTTP/2 那样定义流,也就不存在多路复用、优先级等特性 自身就是全双工,也不需要服务器推送 协议名 引入 ws 和 wss 分别代表明文和密文的 websocket 协议,且默认端口使用 80 或 443,几乎与 http 一致
握手与连接
浏览器发出连线请求,此时的request如下:
通过get可以表明此次连接的建立是以HTTP协议为基础的,返回101状态码。
如果不是101状态码,表示握手升级的过程失败了
101是Switching Protocols,表示服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送这个响应后的空档,将http升级到webSocket。
- 其中
Upgrade和Connection字段告诉服务端,发起的是webSocket协议 Sec-WebSocket-Key是浏览器经过Base64加密后的密钥,用来和response里面的Sec-WebSocket-Accept进行比对验证Sec-WebSocket-Version是当前的协议版本Sec-WebSocket-Extensions是对WebSocket的协议扩展
服务器接到浏览器的连线请求返回结果如下:
Upgrade和Connection来告诉浏览器,服务已经是基于webSocket协议的了,让浏览器也遵循这个协议Sec-WebSocket-Accept是服务端确认后并加密后的Sec-WebSocket-Accept
至此,webSocket连接成功,接下来就是webSocket的协议了。
客户端的简单示例
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
ws.onerror = function(evt) {
console.log("error!!!");
};
websocket持久连接 不关闭就会一直连着吗?
WebSocket 持久连接不会无条件一直连着—— 即使客户端和服务器都不主动调用 close(),连接也可能因网络波动、超时机制、资源限制等原因被断开,只是它会尽力维持持久连接状态。
默认情况下的连接生命周期
1.主动维持阶段:当客户端 new WebSocket(url) 建立连接后,只要:
- 网络通畅(无断网、路由切换、防火墙拦截);
- 服务器未主动断开(如未触发超时清理、未重启服务);
- 客户端未主动调用
ws.close();
连接会持续保持,双方可随时发送 / 接收数据(全双工特性)。
2.被动断开的常见场景(即使不主动关闭):
- 网络中断 / 波动:如客户端切换网络(Wi-Fi → 4G)、断网后重连,底层 TCP 连接会断开,WebSocket 依赖的 TCP 连接失效后,连接会终止;
- 服务器超时清理:多数服务器会配置 “空闲连接超时”(如 Nginx 默认 60 秒、Node.js
ws库无默认超时但可手动配置),若长时间(如几分钟)无数据传输,服务器会主动断开连接以释放资源; - 服务器重启 / 扩容:后端服务部署更新、重启或扩容时,旧的 WebSocket 连接会被终止;
- 防火墙 / 代理拦截:部分企业内网、路由器会清理长时间空闲的 TCP 连接(默认可能 30 秒~5 分钟),导致 WebSocket 连接被断开;
- 客户端资源回收:客户端页面隐藏在后台(如浏览器 tab 切换),部分浏览器会限制后台连接的资源占用,可能导致连接被静默断开。
如何让 WebSocket 尽可能 “一直连着”?
核心是解决 “空闲连接被断开” 的问题,常用方案是 心跳检测(Keep-Alive):
-
原理:客户端和服务器定期(如 30 秒)互相发送 “心跳包”(无实际业务意义的简单数据,如
{ type: 'heartbeat' }),让连接保持 “活跃”,避免被超时机制断开; -
实现示例(补充到之前的 WebSocket 代码中):
// 前端:定时发送心跳包
let heartbeatTimer;
ws.onopen = function() {
console.log('连接建立成功');
// 每 30 秒发送一次心跳
heartbeatTimer = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'heartbeat' }));
}
}, 30000);
};
// 服务器:接收心跳包并响应(或仅接收不响应,只要有数据传输即可)
wss.on('connection', (ws) => {
ws.on('message', (message) => {
const data = JSON.parse(message);
if (data.type === 'heartbeat') {
// 可选:响应心跳(让客户端确认连接正常)
ws.send(JSON.stringify({ type: 'heartbeat-ack' }));
}
});
});
// 客户端 / 服务器:断开时清除心跳计时器
ws.onclose = function () {
clearInterval (heartbeatTimer);
};
关键补充
- 断线后的恢复:即使做了心跳检测,仍可能因网络波动等原因断开,此时需配合 自动重连机制(之前 WebSocket 示例中提到的
setTimeout(initWebSocket, 3000)),断开后定期重试连接,恢复通信; - 服务器压力注意:WebSocket 是持久连接,若大量客户端长时间连接,服务器需具备高并发支持(如使用集群部署、Redis 发布订阅实现跨节点通信),否则可能因连接数过多导致资源耗尽;
- 与 SSE 的区别:SSE 原生支持“自动重连”(客户端
EventSource内置),但 WebSocket 需手动实现心跳+重连,才能达到类似的“持久化效果”。
总结:WebSocket 持久连接是“尽力维持”的,而非绝对“永久不中断”——不主动关闭时,连接会在无异常的情况下持续,但实际场景中需通过“心跳检测+自动重连”应对被动断开的情况,才能实现稳定的长连接通信。
websocket库
(1)socket.io-client
github官网:github.com/socketio/so…
安装
npm install socket.io-client
或
yarn add socket.io-client
使用
import React, { Component } from "react";
import io from 'socket.io-client';
export default class Main extends Component {
constructor(props) {
super(props);
this.state = {
news: []
};
};
getNews() {
//和后端服务建立链接
const socket = io('ws://10.0.3.69:8442');
//监听后端推送过来的数据(注,init可以自定义的,只要和后端约定好可行了!!)
socket.on('init', (data) => {
console.log(data); //这是后端推送过来的数据
this.setState({
news: data
});
});
let msg = '我是前端向后端发送的数据!!';
//向后端发送数据
socket.emit('send', { text: msg});
//后端在接收时也就是监听send就可以得到前端传过来的数据了
//(注,send可以自定义的,只要和后端约定好可行了!!)
};
componentWillMount() {
this.getNews();
};
componentDidMount() {
};
render() {
return (
<section className="main">
<ul className="news-box">
<li>
{
this.state.news.map((item, index) =>{
return (`<b>${item.num}</b> <span>${item.content}</span>`)
})
}
</li>
</ul>
</section>
);
};
};
方法说明
socket.on()方法:
-
socket.on()用于监听获取服务端(后端)发送过来的数据
-
socket.on('monitorName', callBack)有两个参数:
-
monitorName:是监听的标识,是自定义的,只要和后端约定好可行了!!)
-
callBack:是一个回调函数,里面的参数就是后端发送过来的数据
-
socket.emit()方法:
-
socket.emit()用于向服务端(后端)发送数据
-
socket.emit('monitorName', sendData)有两个参数:
-
monitorName:是监听的标识,是自定义的,只要和后端约定好可行了!!)
-
sendData:可以是字符串,也可以是{}JSON对象,这是向后端发送过去的数据
-
socket.on()方法 和 socket.emit()方法 在前后端是成对出现的
后端向前端推送数据时,前后端的写法:
- 后端:
socket.emit('news', {data: [{id: 1, text: '我是向前端发送的新闻列表'}]});
- 前端:
socket.on('news', function(data) {
console.log(data)
});
前端向后端推送数据时,前后端的写法:
- 后端:
socket.on('send', function(data) {
console.log(data)
});
- 前端:
socket.emit('send', {uid: 'mu-2018', name: 'myName', text: '我是向后端发送的数'});
后端在向前端发送时定义的标识是 news,前端在接收时也是news
前端在向后端发送时定义的标识是 send,后端在接收时也是send
(2)socket.io
(3)ws
在Node.js中,使用最广泛的WebSocket模块是ws