WebSocket

209 阅读10分钟

全双工

“全双工”(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目前支持两种统一资源标志符wswss,类似于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如下:

image.png

通过get可以表明此次连接的建立是以HTTP协议为基础的,返回101状态码。

如果不是101状态码,表示握手升级的过程失败了

101是Switching Protocols,表示服务器已经理解了客户端的请求,并将通过Upgrade 消息头通知客户端采用不同的协议来完成这个请求。在发送这个响应后的空档,将http升级到webSocket

  • 其中UpgradeConnection字段告诉服务端,发起的是webSocket协议
  • Sec-WebSocket-Key是浏览器经过Base64加密后的密钥,用来和response里面的Sec-WebSocket-Accept进行比对验证
  • Sec-WebSocket-Version是当前的协议版本
  • Sec-WebSocket-Extensions是对WebSocket的协议扩展

服务器接到浏览器的连线请求返回结果如下:

  • UpgradeConnection来告诉浏览器,服务已经是基于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):

  1. 原理:客户端和服务器定期(如 30 秒)互相发送 “心跳包”(无实际业务意义的简单数据,如 { type: 'heartbeat' }),让连接保持 “活跃”,避免被超时机制断开;

  2. 实现示例(补充到之前的 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);
};

关键补充

  1. 断线后的恢复:即使做了心跳检测,仍可能因网络波动等原因断开,此时需配合 自动重连机制(之前 WebSocket 示例中提到的 setTimeout(initWebSocket, 3000)),断开后定期重试连接,恢复通信;
  2. 服务器压力注意:WebSocket 是持久连接,若大量客户端长时间连接,服务器需具备高并发支持(如使用集群部署、Redis 发布订阅实现跨节点通信),否则可能因连接数过多导致资源耗尽;
  3. 与 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

www.w3cschool.cn/socket/sock…

cw.hubwiz.com/card/c/543b…

(3)ws

在Node.js中,使用最广泛的WebSocket模块是ws