Harmony os WebSocket 实战:在 HarmonyOS 里搞定双向实时通信(顺便记点踩坑)

鸿蒙第四期活动
这两天在看 HarmonyOS 的 Network Kit,顺手把 WebSocket 这一块啃了一遍,准备以后在“在线聊天 / 实时通知 / 小游戏联机”这些场景里直接用上。怕自己过两天忘了,就当写一篇学习笔记,也顺便留给以后来的自己查。

下面都是基于官方最新文档 + 我自己理解整理的,有不严谨的地方,以实际 API 文档为准。developer.huawei.com


一、先搞清楚:为什么是 WebSocket,而不是 HTTP?

简单粗暴地说:

  • HTTP
    • 请求 → 响应,完事就断开
    • 适合:一次性拿数据,比如首屏加载、详情页拉接口
  • WebSocket
    • 一次握手,后面连接常驻
    • 全双工:客户端、服务端都能主动发消息
    • 适合:
      • 聊天室
      • 实时游戏
      • 实时行情 / 实时通知
      • 在线协作编辑

在 HarmonyOS 里,WebSocket 是通过 @kit.NetworkKit 提供的,既有 client 也有 server 能力(注意:服务端目前只支持智慧屏,而且是从 API version 19 开始才有服务端)。developer.huawei.com


二、HarmonyOS WebSocket 能力总览(我自己先给它捋一遍)

官方帮我们准备好了两套东西:

  1. 客户端能力(大部分应用都会用到的)
    • createWebSocket():创建客户端对象
    • connect(url, cb):连接服务器
    • on('open' | 'message' | 'close' | 'error'):订阅各种事件
    • send():发消息(文本 / 二进制)
    • close():主动断开连接
    • 支持心跳机制pingInterval + pongTimeout,不响应自动断开developer.huawei.com+1
  2. 服务端能力(目前 only 智慧屏)
    • createWebSocketServer():创建服务端
    • start(config):设置端口、最大连接数等
    • on('connect' | 'messageReceive' | 'close' | 'error')
    • send():给某个 client 发消息
    • listAllConnections():列出所有连接
    • close(connection):关掉某个客户端连接
    • stop():停服

一句话总结
客户端这一套跟浏览器 WebSocket 很像,只是多了 BusinessError;
服务端则是“轻量 WebSocket server”,适合跑在智慧屏上做一些本地小服务。


三、客户端开发:从 0 到连上一个 WebSocket 服务器

1. 导入模块

import { webSocket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

这两个应该已经很眼熟了,NetworkKit 负责网络,BusinessError 负责标准错误结构。

2. 创建 WebSocket 对象 & 准备 URL

let ws = webSocket.createWebSocket();

// 这里替换成你自己的 ws 地址
const WS_URL = 'ws://your.server.com:8080/ws';

后面所有操作都是基于这个 ws 来玩的。

3. 订阅几个关键事件(这是 WebSocket 的灵魂)

我一般是按这几个顺序写:openmessagecloseerror

// 1)连接成功
ws.on('open', (err: BusinessError, value: Object) => {
  if (err) {
    console.error('WebSocket open failed: ' + JSON.stringify(err));
    return;
  }
  console.info('WebSocket opened: ' + JSON.stringify(value));

  // 连接成功后先发一条测试消息
  ws.send('Hello, server!', (err: BusinessError, ok: boolean) => {
    if (err) {
      console.error('send failed: ' + JSON.stringify(err));
    } else {
      console.info('send ok: ' + ok);
    }
  });
});

// 2)收到消息
ws.on('message', (err: BusinessError, value: string | ArrayBuffer) => {
  if (err) {
    console.error('on message error: ' + JSON.stringify(err));
    return;
  }
  console.info('收到消息:' + value);

  // 假设约定收到 "bye" 就主动断开
  if (value === 'bye') {
    ws.close((err: BusinessError, ok: boolean) => {
      if (err) {
        console.error('close failed: ' + JSON.stringify(err));
      } else {
        console.info('close ok: ' + ok);
      }
    });
  }
});

// 3)连接关闭
ws.on('close', (err: BusinessError, result: webSocket.CloseResult) => {
  if (err) {
    console.error('on close error: ' + JSON.stringify(err));
    return;
  }
  console.info(`连接关闭,code=${result.code}, reason=${result.reason}`);
});

// 4)异常
ws.on('error', (err: BusinessError) => {
  console.error('WebSocket error: ' + JSON.stringify(err));
});

这里有几个小点我自己记了一下:

  • messagevalue 可能是 string 也可能是 ArrayBuffer,二进制就要自己转。
  • close 回调里的 CloseResult 里面有 codereason,做调试特别好用。
  • error 事件最好统一打日志,不然有时候你根本不知道连接在哪一步又挂了。

4. 正式发起连接

ws.connect(WS_URL, (err: BusinessError, ok: boolean) => {
  if (err) {
    console.error('连接失败:' + JSON.stringify(err));
    return;
  }
  console.info('连接结果:' + ok);
});

到这一步,如果服务端正常,日志里就会先看到 open,然后你发的 Hello, server! 就应该在对端收到了。


四、服务端开发:在智慧屏上跑一个轻量 WebSocket Server

这一块目前只支持智慧屏,而且是从 API version 19 开始。也就是说,你在手机上是没法直接开这个 Server 的。developer.huawei.com

1. 导入模块 & 配置参数

import { webSocket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';

let localServer: webSocket.WebSocketServer;

const config: webSocket.WebSocketServerConfig = {
  serverPort: 8080,          // 监听端口
  maxConcurrentClientsNumber: 10,  // 最大并发客户端数
  maxConnectionsForOneClient: 10   // 单 client 最大连接数
};

这里就已经能看出来,它更偏向本地小规模实时场景,不是拿来顶一个大规模公网服务的。

2. 创建 WebSocketServer 并监听事件

localServer = webSocket.createWebSocketServer();

// 新客户端连接
localServer.on('connect', async (connection: webSocket.WebSocketConnection) => {
  console.info(`新客户端连上了,ip=${connection.clientIP}, port=${connection.clientPort}`);

  // 主动给它打个招呼
  localServer.send("Hello, I'm server!", connection)
    .then((ok: boolean) => {
      console.info('send to client: ' + ok);
    })
    .catch((err: BusinessError) => {
      console.error(`send failed, code=${err.code}, msg=${err.message}`);
    });

  // 看一下当前总共有多少连接(可选)
  try {
    const all = await localServer.listAllConnections();
    console.info(`当前连接数量:${all.length}`);
  } catch (error) {
    console.error(`listAllConnections failed: ${JSON.stringify(error)}`);
  }
});

// 收到客户端消息
localServer.on('messageReceive', (message: webSocket.WebSocketMessage) => {
  try {
    console.info(`收到消息,data=${message.data}`);

    // 简单 echo 一下
    localServer.send(`echo: ${message.data}`, message.clientConnection);

    // 如果对方发 "bye",就把这个连接关掉
    if (message.data === 'bye') {
      localServer.close(message.clientConnection)
        .then((ok: boolean) => {
          console.info('关闭客户端连接结果:' + ok);
        })
        .catch((err: BusinessError) => {
          console.error('关闭客户端失败:' + JSON.stringify(err));
        });
    }
  } catch (error) {
    console.error(`messageReceive 处理失败: ${JSON.stringify(error)}`);
  }
});

// 有客户端断开
localServer.on('close', (clientConn: webSocket.WebSocketConnection, reason: webSocket.CloseResult) => {
  console.info(`客户端断开:code=${reason.code}, reason=${reason.reason}`);
});

// 服务端异常
localServer.on('error', (error: BusinessError) => {
  console.error(`server error, code=${error.code}, msg=${error.message}`);
});

3. 启动 & 停止服务

// 启动
localServer.start(config)
  .then((ok: boolean) => {
    console.info('WebSocket server 启动结果:' + ok);
  })
  .catch((err: BusinessError) => {
    console.error(`server 启动失败:code=${err.code}, msg=${err.message}`);
  });

// 不再需要时停止
localServer.stop()
  .then((ok: boolean) => {
    console.info('server 停止结果:' + ok);
  })
  .catch((err: BusinessError) => {
    console.error(`server 停止失败:${JSON.stringify(err)}`);
  });

一般我自己的习惯是:

  • 在 Ability 销毁前确保 stop()
  • 不然容易有意料之外的端口占用 / 资源没释放干净的情况。

五、心跳机制:pingInterval & pongTimeout 怎么理解?

这个机制说白了就是防“假死”连接developer.huawei.com+1

  • 建立连接后,客户端会按 pingInterval 秒发送 Ping 帧 给服务端
  • 如果服务端支持 WebSocket 协议,收到 Ping 会自动回复 Pong 帧
  • 如果在 pongTimeout 秒内没收到 Pong,客户端就会认为连接异常,主动断开

官方还多补了两个点:developer.huawei.com

  • pongTimeout 必须 pingInterval
  • 两者都有最大 / 最小值限制

实际项目里可以这么玩:

  1. 你的业务对“实时性”的容忍度高一点,比如聊天消息 10 秒内能重连回来就行:
    • pingInterval 可以设长一点,比如 30s
    • 减少电量 & 流量消耗
  2. 对实时性要求比较高,比如在线对战小游戏:
    • pingInterval 可以设短一点,比如 5s
    • 这样掉线能更快感知到,然后做重连

小建议

  • 心跳 + 手动重连配合使用效果更好
  • 重连记得做“指数退避”,别一秒钟疯狂重连把服务器打爆

六、我自己踩过/想到的一些注意点

最后列几个我觉得容易忽略的点,当备忘录用:

  1. URL 必须是 ws:// 或 wss://
    写成 http:// 就直接拒绝你,别问为什么,协议都不对。
  2. 服务端只跑在智慧屏上
    不能指望在手机上开 WebSocketServer 然后当总后台用,它的应用场景更偏“本地小服务”。
  3. 权限 & 配置别忘了
    • 网络访问相关的权限、配置要在工程里配好
    • 不然后台抓半天包都连不上,其实根本没权限出网
  4. 处理好二进制数据
    message 回调的 value 可能是 ArrayBuffer,这时候要自己用 DataView / Uint8Array 解析,别简单当 string 打印。
  5. 日志一定要写全
    • open / message / close / error 每个环节都打日志
    • 线上问题 80% 靠日志排
  6. 跟后端提前约定好协议内容
    比如这篇文档里用 bye 作为断开连接的示例字段,这种东西一定要跟后端提前统一,不然一个写 “bye” 一个写 “BYE” 就是永远收不到。

七、总结一句:WebSocket 在 HarmonyOS 里用起来并不难

整体用下来,我的感受是:

  • 如果你写过浏览器里的 new WebSocket(url),上手 HarmonyOS 的 WebSocket 客户端几乎没学习成本;
  • 服务端这一块,适合以后做一些“智慧屏 + 本地设备”的联动,比如:
    • 智慧屏做控制中心
    • 手机/IoT 设备当客户端连过来
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐