写在前面

如果你写过鸿蒙 ArkUI 应用,大概率遇到过这个场景:

你写了个聊天应用,用 HTTP 轮询每 3 秒拉一次消息——结果电量血崩、消息延迟 3 秒、服务器被打爆。
你想「能不能服务器有消息就推过来」——这就是 WebSocket。
你查文档发现「鸿蒙有 @ohos.net.webSocket」——你点进去发现 createWebSocket 创建实例、connect(url) 建连接、on("open"/"message"/"close"/"error") 事件订阅、send(payload) 发消息、close() 断开——API 一脸懵。

这是「HTTP 轮询」和「WebSocket 长连接」的分水岭。鸿蒙给的实时通讯答案是 @ohos.net.webSocket——createWebSocket 拿实例、connect 建连接、on 订阅事件、send 发消息、close 断开。

本文就用一个真机可跑的「建连接 + 发消息 + 收消息 + 断开」demo,把 WebSocket 从「听名字一脸懵」讲到「下个项目直接抄」。代码托管在 AtomGit,文末有链接,真机实拍截图作证。这是韶非 UI 系列第六篇,接续上五篇 HTTP 网络栈 + 文件 IO + 能力调用 + 后台任务 + 关系数据库。

适合人群:写过鸿蒙应用、被「HTTP 轮询电量血崩」折磨过的同学。
不适合人群:还在学 @State 的同学——出门左转看我的入门篇。


一、先讲清楚:WebSocket 到底是啥

一句话:WebSocket 是鸿蒙给应用建「全双工长连接」的原生机制,管「建连接 + 收发消息 + 断开」全流程。

你之前写前端 new WebSocket(url) 是浏览器宿主 API——鸿蒙不是浏览器环境,没有这种。WebSocket 是鸿蒙专门给实时通讯的原生机制,底层是 TCP 长连接 + WebSocket 协议握手,能力对标前端的「new WebSocket + EventSource」但更精细可控。

核心 API 一览:

API 作用 一句话理解
webSocket.createWebSocket() 创建 WebSocket 实例 「告诉系统我要用长连接」
ws.connect(url) 建连接 「TCP 握手 + WebSocket 升级」
ws.on("open"/"message"/"close"/"error") 事件订阅 「连接建立/收消息/关闭/错误」
ws.send(payload) 发消息 「string 或 ArrayBuffer」
ws.close() 主动断开 「客户端先关」

记住这五个,往下看。


二、动手:一个建连接 + 发消息 + 收消息 + 断开的 demo

2.1 import + 创建 WebSocket 实例

import webSocket from '@ohos.net.webSocket'
import common from '@ohos.app.ability.common'

@Entry
@Component
struct Index {
  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
  private ws: webSocket.WebSocket | null = null
  private wsUrl: string = 'wss://echo.websocket.org'
  // ...
}

三个细节:

  1. import webSocket from '@ohos.net.webSocket'——webSocket 是 WebSocket 的入口模块
  2. ws: webSocket.WebSocket | null——存 WebSocket 实例,后续所有操作都走它
  3. wsUrl: string = 'wss://echo.websocket.org'——echo 服务器,发啥回啥,适合 demo

2.2 connect + on:建连接 + 事件订阅

async connectWs(): Promise<void> {
  this.stateLog = '建 WebSocket 连接中...'
  try {
    // 创建 WebSocket 实例
    this.ws = webSocket.createWebSocket()

    // on('open') 连接建立回调
    this.ws.on('open', () => {
      this.connState = '已连接'
      this.stateLog = `WebSocket 已连接,url = ${this.wsUrl}`
    })

    // on('message') 收消息回调
    this.ws.on('message', (err, value) => {
      this.recvCount++
      // value 可能是 string 或 ArrayBuffer
      const text: string = typeof value === 'string' ? value : '(二进制数据)'
      this.totalRecv = this.recvCount
      this.lastRecv = text
      this.stateLog = `${this.recvCount} 次收到消息`
    })

    // on('close') 连接关闭回调
    this.ws.on('close', () => {
      this.connState = '已断开'
      this.stateLog = 'WebSocket 连接已关闭'
    })

    // on('error') 错误回调
    this.ws.on('error', (err) => {
      this.connState = '连接错误'
      this.stateLog = `WebSocket 错误:${err?.message ?? err}`
    })

    // connect 发起连接,返回 Promise<boolean>
    const ok: boolean = await this.ws.connect(this.wsUrl)
    if (!ok) {
      this.stateLog = 'WebSocket 连接失败(返回 false)'
      this.ws = null
    }
  } catch (e) {
    this.stateLog = `建连接失败:${e.message}`
    this.ws = null
  }
}

connect + on 三个关键点:

on 订阅 4 类事件

ws.on('open', cb)     // 连接建立
ws.on('message', cb)  // 收消息
ws.on('close', cb)    // 连接关闭
ws.on('error', cb)    // 错误

事件回调是异步的——connect 返回成功只表示握手发起,真正「连上了」看 on('open')

on('message') value 可能是 string 或 ArrayBuffer

ws.on('message', (err, value) => {
  const text: string = typeof value === 'string' ? value : '(二进制数据)'
  // ...
})

文本消息是 string,二进制消息是 ArrayBuffer。新手最容易忘 typeof 判断,直接当 string 用,二进制消息就炸

connect 返回 Promise<boolean>

const ok: boolean = await this.ws.connect(this.wsUrl)

ok = true 表示握手发起成功,不代表「连上了」——「连上了」看 on('open')

2.3 send:发消息

async sendMsg(): Promise<void> {
  if (!this.ws) {
    this.stateLog = '尚未建连接,无法发消息'
    return
  }
  try {
    this.sendCount++
    const payload: string = `hello-${this.sendCount}-${Date.now()}`
    // send 发消息,返回 Promise<boolean>
    const ok: boolean = await this.ws.send(payload)
    if (ok) {
      this.totalSent = this.sendCount
      this.stateLog = `${this.sendCount} 次发送成功:${payload}`
    } else {
      this.stateLog = `${this.sendCount} 次发送失败(返回 false)`
    }
  } catch (e) {
    this.stateLog = `发消息失败:${e.message}`
  }
}

send 三个关键点:

send(payload) 支持 string 或 ArrayBuffer

await this.ws.send('hello')           // 文本
await this.ws.send(new ArrayBuffer(8))  // 二进制

send 返回 Promise<boolean>

const ok: boolean = await this.ws.send(payload)

ok = true 表示发送成功(底层 TCP 缓冲区接收了)。

send 不等回消息

send 是单向的——发出去就完。回消息看 on('message') 异步到达。

2.4 close:断开连接

async closeWs(): Promise<void> {
  if (!this.ws) {
    this.stateLog = '尚未建连接,无需断开'
    return
  }
  try {
    // close 主动断开,返回 Promise<boolean>
    const ok: boolean = await this.ws.close()
    this.stateLog = `已断开(ok=${ok}`
    this.connState = '已断开'
    this.ws = null
  } catch (e) {
    this.stateLog = `断开失败:${e.message}`
  }
}

close 三个关键点:

close 返回 Promise<boolean>

const ok: boolean = await this.ws.close()

close 后会触发 on('close')

主动 close 和服务端断开都会触发 on('close')——用这个回调统一处理「连接已关」。

closews 实例不能复用

closews 实例作废,要重连得重新 createWebSocket + connect


三、真机实拍:建连接 + 发消息 + 收消息全跑通

我把这个 demo 装到真机上跑(鸿蒙 6.1.1.125, API 24),依次点 ① 建连接 + ② 发消息,下面两张都是真机实拍,没有任何 P 图。

初始态:WebSocket Demo 标题 + 连接状态「未连接」+ ① 建连接 / ② 发消息 / ③ 断开连接三按钮 + 总发送/总接收指标区 + 最近收到消息区 + 关键 API 说明区:

在这里插入图片描述

点 ① 建连接 + ② 发消息后状态:连接状态「已连接」+ 状态日志「WebSocket 已连接」+ 总发送/总接收指标更新 + 最近收到消息显示 echo 服务器返回内容:

在这里插入图片描述

重点看第二张:连接状态「已连接」+ 总发送/总接收指标更新——createWebSocket + connect + on + send 全跑通了。最近收到消息显示 echo 服务器返回内容——on('message') 真收到消息了。这是 WebSocket 五大 API 全跑通的真机证明。


四、@ohos.net.webSocket vs 前端 new WebSocket:啥差异

新手最容易纠结的问题:既然前端 new WebSocket(url) 那么标准,鸿蒙为啥要造自己的 WebSocket?

维度 前端 new WebSocket @ohos.net.webSocket
运行环境 浏览器宿主 鸿蒙原生运行环境
实例创建 new WebSocket(url) webSocket.createWebSocket()
建连接 构造函数内自动连 await ws.connect(url) 显式
事件订阅 ws.onopen = cb ws.on('open', cb)
收消息 ws.onmessage = cb ws.on('message', cb)
发消息 ws.send(data) 同步 void await ws.send(data) Promise
安全模型 同源策略 + wss 鸿蒙沙箱 + 网络权限

一句话决策:鸿蒙应用实时通讯必须用 @ohos.net.webSocket,不能用 new WebSocket(不存在)。鸿蒙不是浏览器,这套原生 WebSocket 封装更安全可控。


五、常见坑(都是血泪)

症状 解法
new WebSocket 编译报错「找不到 WebSocket」 鸿蒙用 @ohos.net.webSocket,没浏览器宿主 API
on('message') value 直接当 string 二进制消息炸 typeof value === 'string' 判断
connect 成功就以为「连上了」 on('open') 还没触发就发消息 on('open') 才算真连上
close 后复用 ws 实例 报错「实例已关」 重新 createWebSocket + connect
忘了网络权限 建连接报权限错 module.json5 加 ohos.permission.INTERNET
长连接不心跳 运营商 NAT 超时断开 30-60s 发一次心跳包
收消息改 @State 不在主线程 UI 不更新 setTimeout 切主线程再改

六、webSocket 安全模型

鸿蒙 WebSocket 受安全约束——网络权限 + 沙箱隔离:

安全机制 含义
网络权限 ohos.permission.INTERNET 才能建连接
沙箱隔离 应用级 WebSocket,其他应用默认访问不到
wss 协议 加密 WebSocket,生产环境必用
证书校验 wss 自签证书需 on('message') 校验

这是鸿蒙安全模型的硬约束——比浏览器同源策略严,但比 iOS ATS 松(鸿蒙允许 ws 明文 + 自签证书,生产环境建议 wss)。


七、完整代码仓库

本文所有代码都已托管到 AtomGit,欢迎 clone、提 issue、点 star:

🔗 仓库地址:https://atomgit.com/JaneConan/arkui-websocket

仓库包含:

  • 完整的「建连接 + 发消息 + 收消息 + 断开」demo 工程
  • Index.ets 主页面(createWebSocket + connect + on + send + close 五姿势)
  • on('open'/'message'/'close'/'error') 事件订阅 + string/ArrayBuffer 类型判断
  • 可直接用 DevEco Studio 打开运行(真机装普通应用必能跑,需配网络权限)

八、下一步该学什么?

跑通这个 demo 之后,你的鸿蒙实时通讯就入门了。这是韶非 UI 系列第六篇,后续按这个顺序往下:

  1. 媒体访问 @ohos.file.photoAccessHelper(下一篇):访问相册、扫描媒体文件,应用调系统相册必学
  2. 推送通知 @ohos.notificationManager:通知栏展示、点击拉起,离线触达必学
  3. 相机 @ohos.multimedia.camera:预览、拍照、录像,相机应用必学
  4. 动画 @ohos.arkui.animation:属性动画、转场动画,UI 进阶必学
  5. 其他:@ohos.multimedia.audio 录音播放、@ohos.bluetooth 蓝牙、@ohos.sensor 传感器,应用领域专属

写在最后

@ohos.net.webSocket 的本质,是**「鸿蒙给应用建全双工长连接的原生机制」**——不是浏览器 new WebSocket,是鸿蒙专门给实时通讯的原生机制,能力对标「new WebSocket + EventSource」但更安全可控。代价是 createWebSocket 多一步、on 事件订阅多一步。

一旦你开始用 WebSocket 思维写实时应用,你会发现大部分「聊天应用收推」「股票应用实时报价」「协同编辑应用同步」的需求,都是 createWebSocket + connect + on + send 的自然结果。代码量比 HTTP 轮询少一半,实时性高九成。

代码已经给你了,仓库链接在上面。现在关掉这篇文章,打开 DevEco Studio,把 demo 跑起来,亲手点建连接发消息感受下全双工长连接。

跑通了,回来评论区打个「1」,我看看有多少人真的动手了。🚀


作者:JaneConan
仓库:https://atomgit.com/JaneConan/arkui-websocket
协议:Apache-2.0,随便用,别告我

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐