计网 01 WebSocket | MDN

WebSocket - Web API | MDN

先铺垫一个核心类比:

WebSocket 连接想象成「你和朋友的电话通话 」------ 拨号(new WebSocket)建立连接,说话(send)发消息,听对方说(onmessage)收消息,电话接通(onopen)才能聊天,电话断了(onclose)要重拨,电话出问题(onerror)要处理,通话状态(readyState)能看是不是在通话中。

一、构造函数:new WebSocket(url) ------ 发起「电话拨号」,建立连接

1. 是什么?

这行代码的作用是「让前端(浏览器)主动联系后端的 WS 服务」,就像你用手机拨朋友的电话号码,发起通话请求。

2. 关键细节(初学者必看)
  • url:后端 WS 服务的「地址」(类似朋友的电话号码),格式必须是 ws://wss:// 开头(前面讲过,ws 是明文,wss 是加密,生产环境用 wss);
    • 示例:ws://localhost:8080/chat(本地后端 WS 服务,端口 8080,聊天接口 /chat);
    • 带登录态(你的项目需要):ws://localhost:8080/chat?token=xxx(拼接 token 让后端认得出你,避免未登录就聊天)。
  • 执行后返回一个「WS 实例」(比如赋值给 const ws = new WebSocket(...)),后续所有操作(发消息、听消息)都靠这个 ws 对象。
3. 简单代码示例
javascript 复制代码
// 拨号:连接后端 WS 服务(替换成你的真实地址)
const ws = new WebSocket('ws://localhost:8080/chat?token=用户登录后的Token');
4. 为什么需要?

没有这一步,前端和后端就没有「双向通信的通道」,后续的收发消息都无从谈起(就像没拨号,没法和朋友聊天)。

二、发送消息:ws.send(data) ------ 对着电话「说话」,传递消息

1. 是什么?

通过前面创建的 ws 实例,把前端的消息(比如你输入的聊天内容)发送给后端,就像你对着电话说话,声音通过电话线传给朋友。

2. 关键细节(初学者必踩坑)
  • data:要发送的消息内容,只能是字符串、Blob(二进制文件,比如图片)、ArrayBuffer(二进制数据)
    • 最常用:字符串(比如聊天文本),如果要发复杂数据(比如包含用户名、消息内容、时间),需要转成 JSON 字符串(后端再转回去);
      • 错误示例:ws.send({ content: '你好', name: '小明' })(直接发对象会报错!);
      • 正确示例:ws.send(JSON.stringify({ content: '你好', name: '小明' }))(用 JSON.stringify 转成字符串)。
  • 必须在「连接成功后」调用:如果刚拨号(new WebSocket)就调用 send,会失败(就像电话还没接通,你说话朋友也听不见)。
3. 简单代码示例
javascript 复制代码
// 假设用户输入的聊天内容存在 inputValue 里
const inputValue = '今天天气真好!';

// 说话:发送消息给后端(转成 JSON 字符串,方便后端解析)
ws.send(JSON.stringify({
  content: inputValue,
  from: '当前登录用户名',
  time: new Date().toLocaleTimeString()
}));
4. 为什么需要?

这是前端主动发消息的唯一方式(比如你输入聊天内容后点「发送」,本质就是调用 ws.send)。

三、接收消息:ws.onmessage = (event) => { ... } ------ 听电话「接话」,接收后端消息

WebSockets 标准

1. 是什么?

这是一个「事件监听」:告诉浏览器 "如果后端通过 WS 给我发消息了,就执行这个函数",就像你把耳朵贴在电话上,等着听朋友说话

2. 关键细节
  • event:是浏览器自动传递的「消息事件对象」,里面的 event.data 就是后端发来的消息内容;
    • 后端发的是字符串:直接用 event.data
    • 后端发的是 JSON 字符串(最常见):需要用 JSON.parse(event.data) 转成对象才能用;
  • 只要连接没断,这个监听会一直生效(后端随时发消息,前端都能收到)。
3. 简单代码示例
javascript 复制代码
// 听电话:接收后端发来的消息
ws.onmessage = (event) => {
  // 后端发的是 JSON 字符串,转成对象
  const message = JSON.parse(event.data);
  
  console.log('收到后端消息:', message);
  // 比如 message 是 { content: '你也好!', from: '小红', time: '14:30' }
  // 之后可以把这个消息添加到聊天列表,界面就显示出来了
};
4. 为什么需要?

WebSocket 是「双向通信」,不仅前端能发,后端也能主动推消息(比如别人给你发聊天内容,后端会推给你),onmessage 就是接收这些推送的唯一方式。

四、状态监听:onopen / onerror / onclose ------ 监听电话的「接通、故障、挂断」状态

这三个都是「事件监听」,对应 WS 连接的三个关键状态,必须处理,否则会出现 "发消息没反应""断连了不知道" 等问题。

1. ws.onopen ------ 电话「接通」了(连接成功)
  • 是什么?:连接成功建立后,浏览器会自动执行这个函数(就像电话接通后,你听到 "嘟" 的一声,知道可以说话了)。

  • 什么时候用?:发送第一条消息必须在这里面(或者判断连接成功后再发),避免连接没通就发消息失败。

  • 代码示例:

    javascript 复制代码
    // 电话接通了:连接成功回调
    ws.onopen = () => {
      console.log('WS 连接成功!可以发消息了~');
      // 比如连接成功后,发送一条"进入聊天"的消息
      ws.send(JSON.stringify({ content: '我进入聊天啦!', from: '当前登录用户名' }));
    };
2. ws.onerror ------ 电话「出故障」了(连接错误)
  • 是什么?:连接过程中出错(比如后端服务没启动、地址写错、网络断了),浏览器会执行这个函数。

  • 什么时候用?:给用户提示 "聊天连接异常",或者触发重连。

  • 代码示例:

    javascript 复制代码
    // 电话出故障:连接错误回调
    ws.onerror = (error) => {
      console.error('WS 连接出错了!', error);
      // 给用户显示提示(比如在页面上弹个框)
      alert('聊天连接异常,请稍后再试~');
    };
3. ws.onclose ------ 电话「挂断」了(连接关闭)
  • 是什么?:连接断开时(比如用户离开聊天页、后端主动关闭、网络中断),浏览器会执行这个函数。

  • 什么时候用?:处理「重连逻辑」(比如断连后 3 秒自动重新拨号),避免用户还在聊天页但发不出消息。

  • 代码示例:

    javascript 复制代码
    // 电话挂断了:连接关闭回调
    ws.onclose = (event) => {
      console.log('WS 连接断开了~', event.code, event.reason);
      // 断连重连(简单版):3 秒后重新创建连接
      setTimeout(() => {
        console.log('正在重新连接...');
        // 重新拨号(这里可以复用之前的 new WebSocket 逻辑)
        const newWs = new WebSocket('ws://localhost:8080/chat?token=xxx');
        // 注意:新连接需要重新绑定 onmessage、onopen 等事件
        ws = newWs; // 替换原来的 ws 实例
      }, 3000);
    };
4. 为什么需要这三个监听?

WS 连接不是永久稳定的(可能断网、后端重启),没有这些监听,你不知道连接是不是通的、出错了怎么办、断了怎么恢复,聊天功能会非常不稳定(就像打电话不知道通没通、断了没反应)。

五、连接状态:ws.readyState ------ 查看电话当前是「拨号中 / 接通 / 挂断」

1. 是什么?

ws 实例的一个属性,用来「实时查看当前连接的状态」,就像你看手机屏幕,知道电话是 "正在拨号""通话中" 还是 "已挂断"。

2. 四个核心状态值(不用记,用的时候查)

WS 定义了 4 个状态常量,对应 readyState 的值:

状态常量 数值 含义(类比电话) 能不能发消息?
WebSocket.CONNECTING 0 正在拨号(连接中) 不能
WebSocket.OPEN 1 通话中(连接成功)
WebSocket.CLOSING 2 正在挂断(关闭中) 不能
WebSocket.CLOSED 3 已挂断(连接关闭) 不能
3. 怎么用?(初学者常用场景)

发送消息前,先判断 readyState 是不是 1(OPEN),避免断连后发消息失败:

javascript 复制代码
const sendMessage = (content) => {
  // 先判断连接是否可用
  if (ws.readyState === WebSocket.OPEN) {
    // 连接正常,发送消息
    ws.send(JSON.stringify({ content }));
  } else {
    // 连接不可用,提示用户
    alert('当前聊天连接不可用,请稍等~');
  }
};
4. 为什么需要?

比如用户断网了(readyState 变成 3),但还在输入消息点发送,如果不判断,ws.send 会失败且没有提示,用户会疑惑 "为什么发不出去";判断后可以给出明确提示,提升体验。

六、初学者必看:完整流程串起来(代码整合)

把上面的所有点整合到一起,就是一个完整的「React 中 WS 收发消息」基础逻辑(你练手 Demo 时可以直接参考这个结构):

javascript 复制代码
import { useState, useEffect } from 'react';

function Chat() {
  const [ws, setWs] = useState(null); // 存储 WS 实例
  const [messages, setMessages] = useState([]); // 聊天消息列表
  const [inputValue, setInputValue] = useState(''); // 输入框内容

  // 1. 组件挂载时,创建 WS 连接(useEffect 处理副作用)
  useEffect(() => {
    // 拨号:创建连接(带登录 Token)
    const newWs = new WebSocket(`ws://localhost:8080/chat?token=${localStorage.getItem('token')}`);

    // 2. 连接成功(onopen)
    newWs.onopen = () => {
      console.log('WS 连接成功');
      setWs(newWs); // 存储 WS 实例到状态
    };

    // 3. 接收消息(onmessage)
    newWs.onmessage = (event) => {
      const newMsg = JSON.parse(event.data);
      setMessages([...messages, newMsg]); // 更新消息列表
    };

    // 4. 连接错误(onerror)
    newWs.onerror = (err) => {
      console.error('WS 错误:', err);
      alert('聊天连接异常');
    };

    // 5. 连接关闭(onclose)
    newWs.onclose = () => {
      console.log('WS 连接断开');
      // 3 秒后重连
      setTimeout(() => {
        // 重新执行 useEffect(这里简化处理,实际可以抽成函数)
      }, 3000);
    };

    // 组件卸载时关闭连接(清理副作用)
    return () => {
      newWs.close();
    };
  }, []); // 空依赖:组件挂载时只创建一次连接

  // 6. 发送消息函数
  const handleSend = () => {
    if (!inputValue.trim()) return; // 空消息不发
    if (ws && ws.readyState === WebSocket.OPEN) {
      // 连接正常,发送消息
      ws.send(JSON.stringify({
        content: inputValue,
        from: '当前用户名',
        time: new Date().toLocaleTimeString()
      }));
      setInputValue(''); // 清空输入框
    } else {
      alert('连接不可用,无法发送');
    }
  };

  return (
    <div>
      {/* 消息列表 */}
      <div>{messages.map((msg, idx) => <div key={idx}>{msg.from}: {msg.content}</div>)}</div>
      {/* 输入框 + 发送按钮 */}
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        placeholder="输入消息..."
      />
      <button onClick={handleSend}>发送</button>
    </div>
  );
}

最后总结(初学者牢记)

  1. new WebSocket(url) 拨号建连接,得到 ws 实例;
  2. ws.onopen 确认连接成功,再用 ws.send(data) 发消息(data 转字符串);
  3. ws.onmessage 收后端消息(转成对象用);
  4. onerror 处理错误,onclose 处理重连;
  5. 发消息前用 ws.readyState === 1 判断连接是否可用。
相关推荐
真正的醒悟1 天前
图解网络34
网络
unique_perfect1 天前
vue2与springboot实现deepseek打印机聊天
spring boot·websocket·ai·vue2·deepseek
IT·小灰灰1 天前
告别“翻墙“烦恼:DMXAPI让Gemini-3-pro-thinking调用快如闪电
网络·人工智能·python·深度学习·云计算
任子菲阳1 天前
学Java第五十六天——网络编程
网络
程序员zgh1 天前
常用通信协议介绍(CAN、RS232、RS485、IIC、SPI、TCP/IP)
c语言·网络·c++
小汐睡着了1 天前
解决虚拟机VMware与宿主机网络不通的问题-error
linux·网络·redhat
Heart_to_Yang1 天前
Telnet 调试屏幕输出信息卡死问题解决
网络·windows·经验分享
资料库011 天前
华为OSPF详解
网络·华为
yenggd1 天前
锐捷路由器nat上网+ipsec配置案例
网络
liebe1*11 天前
第九章 防火墙入侵防御
运维·网络·防火墙