• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 使用WebSocket访问网络(C/C++)
2<!--Kit: Network Kit-->
3<!--Subsystem: Communication-->
4<!--Owner: @wmyao_mm-->
5<!--Designer: @guo-min_net-->
6<!--Tester: @tongxilin-->
7<!--Adviser: @zhang_yixin13-->
8
9## 场景介绍
10
11通过WebSocket模块可以建立服务器与客户端的双向连接。
12
13## 接口说明
14
15WebSocket常用接口如下表所示,详细的接口说明请参考[net_websocket.h](../reference/apis-network-kit/capi-net-websocket-h.md)。
16
17
18| 接口名 | 描述 |
19| -------- | -------- |
20| OH_WebSocketClient_Constructor(WebSocket_OnOpenCallback onOpen, WebSocket_OnMessageCallback onMessage, WebSocket_OnErrorCallback onError, WebSocket_OnCloseCallback onclose) | WebSocket客户端的构造函数。  |
21| OH_WebSocketClient_AddHeader(struct WebSocket \*client, struct WebSocket_Header header) | 将header头信息添加到client客户端request中。  |
22| OH_WebSocketClient_Connect(struct WebSocket \*client, const char \*url, struct WebSocket_RequestOptions options) | 客户端连接服务端。  |
23| OH_WebSocketClient_Send(struct WebSocket \*client, char \*data, size_t length) | 客户端向服务端发送数据。  |
24| OH_WebSocketClient_Close(struct WebSocket \*client, struct WebSocket_CloseOption options) | 客户端主动关闭websocket连接。  |
25| OH_WebSocketClient_Destroy(struct WebSocket \*client) | 释放websocket连接上下文和资源。  |
26
27## WebSocket接口开发示例
28
29### 开发步骤
30
31使用本文档涉及接口创建并连接到WebSocket服务器时,需先创建Native C++工程,在源文件中封装相关接口,然后在ArkTS层调用封装好的接口,使用hilog或console.info等方法将日志打印到控制台或生成设备日志。
32
33本文以建立与WebSocket服务器的连接、向WebSocket服务器发送消息、关闭WebSocket连接为例,提供具体的开发指导。
34
35### 添加开发依赖
36
37**添加动态链接库**
38
39CMakeLists.txt中添加以下lib:
40
41```txt
42libace_napi.z.so
43libnet_websocket.so
44```
45
46**头文件**
47
48```c
49#include "napi/native_api.h"
50#include "network/netstack/net_websocket.h"
51#include "network/netstack/net_websocket_type.h"
52```
53
54### 构建工程
55
561、在源文件中编写调用该API的代码,接受ArkTS传递过来的url字符串参数,创建WebSocket对象指针后,检查连接到服务器是否成功。
57
58```cpp
59#include "napi/native_api.h"
60#include "network/netstack/net_websocket.h"
61#include "network/netstack/net_websocket_type.h"
62#include "hilog/log.h"
63
64#include <cstring>
65
66#undef LOG_DOMAIN
67#undef LOG_TAG
68#define LOG_DOMAIN 0x3200  // 全局domain宏,标识业务领域
69#define LOG_TAG "WSDEMO"   // 全局tag宏,标识模块日志tag
70
71// WebSocket客户端全局变量
72static struct WebSocket *client = nullptr;
73
74static void onOpen(struct WebSocket *client, WebSocket_OpenResult openResult)
75{
76    (void)client;
77    OH_LOG_INFO(LOG_APP, "onOpen: code: %{public}u, reason: %{public}s",
78        openResult.code, openResult.reason);
79}
80
81static void onMessage(struct WebSocket *client, char *data, uint32_t length)
82{
83    (void)client;
84    char *tmp = new char[length + 1];
85    for (uint32_t i = 0; i < length; i++) {
86        tmp[i] = data[i];
87    }
88    tmp[length] = '\0';
89    OH_LOG_INFO(LOG_APP, "onMessage: len: %{public}u, data: %{public}s",
90        length, tmp);
91}
92
93static void onError(struct WebSocket *client, WebSocket_ErrorResult errorResult)
94{
95    (void)client;
96    OH_LOG_INFO(LOG_APP, "onError: code: %{public}u, message: %{public}s",
97        errorResult.errorCode, errorResult.errorMessage);
98}
99
100static void onClose(struct WebSocket *client, WebSocket_CloseResult closeResult)
101{
102    (void)client;
103    OH_LOG_INFO(LOG_APP, "onClose: code: %{public}u, reason: %{public}s",
104        closeResult.code, closeResult.reason);
105}
106
107static napi_value ConnectWebsocket(napi_env env, napi_callback_info info)
108{
109    size_t argc = 2;
110    napi_value args[2] = {nullptr};
111    napi_value result;
112
113    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
114
115    size_t length = 0;
116    napi_status status = napi_get_value_string_utf8(env, args[0], nullptr, 0, &length);
117    if (status != napi_ok) {
118        napi_get_boolean(env, false, &result);
119        return result;
120    }
121
122    if (client != nullptr) {
123        OH_LOG_INFO(LOG_APP, "there is already one websocket client running.");
124        napi_get_boolean(env, false, &result);
125        return result;
126    }
127    char *buf = new char[length + 1];
128    std::memset(buf, 0, length + 1);
129    napi_get_value_string_utf8(env, args[0], buf, length + 1, &length);
130	// 创建WebSocket Client对象指针
131    client = OH_WebSocketClient_Constructor(onOpen, onMessage, onError, onClose);
132    if (client == nullptr) {
133        delete[] buf;
134        napi_get_boolean(env, false, &result);
135        return result;
136    }
137	// 连接buf存放的URL对应的WebSocket服务器
138    int connectRet = OH_WebSocketClient_Connect(client, buf, {});
139
140    delete[] buf;
141    napi_get_boolean(env, connectRet == 0, &result);
142    return result;
143}
144
145static napi_value SendMessage(napi_env env, napi_callback_info info)
146{
147    size_t argc = 1;
148    napi_value args[1] = {nullptr};
149    napi_value result;
150
151    napi_get_cb_info(env, info, &argc, args , nullptr, nullptr);
152
153    size_t length = 0;
154    napi_status status = napi_get_value_string_utf8(env, args[0], nullptr, 0, &length);
155    if (status != napi_ok) {
156        napi_create_int32(env, -1, &result);
157        return result;
158    }
159
160    if (client == nullptr) {
161        OH_LOG_INFO(LOG_APP, "websocket client not connected.");
162        napi_create_int32(env, WebSocket_ErrCode::WEBSOCKET_CLIENT_NULL, &result);
163        return result;
164    }
165    char *buf = new char[length + 1];
166    std::memset(buf, 0, length + 1);
167    napi_get_value_string_utf8(env, args[0], buf, length + 1, &length);
168	// 发送buf中的消息给服务器
169    int ret = OH_WebSocketClient_Send(client, buf, length);
170
171    delete[] buf;
172    napi_create_int32(env, ret, &result);
173    return result;
174}
175
176static napi_value CloseWebsocket(napi_env env, napi_callback_info info)
177{
178    napi_value result;
179    if (client == nullptr) {
180        OH_LOG_INFO(LOG_APP, "websocket client not connected.");
181        napi_create_int32(env, -1, &result);
182        return result;
183    }
184	// 关闭WebSocket连接
185    int ret = OH_WebSocketClient_Close(client, {
186        .code = 0,
187        .reason = "Actively Close",
188    });
189	// 释放WebSocket资源并置空
190    OH_WebSocketClient_Destroy(client);
191    client = nullptr;
192    napi_create_int32(env, ret, &result);
193    return result;
194}
195
196```
197
198ConnectWebsocket函数接收一个WebSocket URL并尝试连接,连接成功返回true,否则返回false。在创建代表WebSocket客户端的WebSocket结构体指针前,需要定义以下回调函数:连接开启时的onOpen回调、接收普通消息的onMessage回调、接收错误消息的onError回调、接收关闭消息的onClose回调。在示例代码中,还调用了`OH_WebSocketClient_Send`、`OH_WebSocketClient_Close`等函数向服务器发送消息,主动关闭WebSocket连接。
199
200
2012、将通过napi封装好的`napi_value`类型对象初始化导出,通过外部函数接口,将函数暴露给JavaScript使用。示例代码中,ConnectWebsocket函数就会作为外部函数Connect暴露出去;SendMessage函数作为外部函数Send暴露出去;CloseWebsocket函数作为外部函数Close暴露出去。
202
203```C
204EXTERN_C_START
205static napi_value Init(napi_env env, napi_value exports) {
206    napi_property_descriptor desc[] = {
207        {"Connect", nullptr, ConnectWebsocket, nullptr, nullptr, nullptr, napi_default, nullptr },
208        {"Send", nullptr, SendMessage, nullptr, nullptr, nullptr, napi_default, nullptr },
209        {"Close", nullptr, CloseWebsocket, nullptr, nullptr, nullptr, napi_default, nullptr},
210    };
211    napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
212    return exports;
213}
214EXTERN_C_END
215```
216
2173、将上一步中初始化成功的对象通过`RegisterEntryModule`函数,使用`napi_module_register`函数将模块注册到 Node.js 中。
218
219```C
220static napi_module demoModule = {
221    .nm_version = 1,
222    .nm_flags = 0,
223    .nm_filename = nullptr,
224    .nm_register_func = Init,
225    .nm_modname = "entry",
226    .nm_priv = ((void*)0),
227    .reserved = { 0 },
228};
229
230extern "C" __attribute__((constructor)) void RegisterEntryModule(void)
231{
232    napi_module_register(&demoModule);
233}
234```
235
2364、在工程的index.d.ts文件中定义函数的类型。比如,Connect函数接受一个string参数作为入参,并返回boolean值指示WebSocket连接是否能成功建立。
237
238```ts
239export const Connect: (url: string) => boolean;
240export const Send: (data: string) => number;
241export const Close: () => number;
242```
243
2445、在index.ets文件中对上述封装好的接口进行调用。
245
246```ts
247import testWebsocket from 'libentry.so'
248
249@Entry
250@Component
251struct Index {
252  @State wsUrl: string = ''
253  @State content: string = ''
254  @State connecting: boolean = false
255
256  build() {
257    Navigation() {
258      Column() {
259        Column() {
260          Text("WebSocket address: ")
261            .fontColor(Color.Gray)
262            .textAlign(TextAlign.Start)
263            .width('100%')
264          TextInput()
265            .width('100%')
266            .onChange((value) => {
267              this.wsUrl = value
268            })
269        }
270        .margin({
271          bottom: 16
272        })
273        .padding({
274          left: 16,
275          right: 16
276        })
277
278        Column() {
279          Text("Content: ")
280            .fontColor(Color.Gray)
281            .textAlign(TextAlign.Start)
282            .width('100%')
283          TextInput()
284            .width('100%')
285            .enabled(this.connecting)
286            .onChange((value) => {
287              this.content = value
288            })
289        }
290        .margin({
291          bottom: 16
292        })
293        .padding({
294          left: 16,
295          right: 16
296        })
297
298        Blank()
299
300        Column({ space: 12 }) {
301          Button('Connect')
302            .enabled(!this.connecting)
303            .onClick(() => {
304              let connRet = testWebsocket.Connect(this.wsUrl)
305              if (connRet) {
306                this.connecting = true;
307              }
308            })
309          Button('Send')
310            .enabled(this.connecting)
311            .onClick(() => {
312              testWebsocket.Send(this.content)
313            })
314          Button('Close')
315            .enabled(this.connecting)
316            .onClick(() => {
317              let closeResult = testWebsocket.Close()
318              if (closeResult != -1) {
319                this.connecting = false
320              }
321            })
322        }
323      }
324    }
325  }
326}
327```
328
3296、配置`CMakeLists.txt`,本模块需要用到的共享库是`libnet_websocket.so`,在工程自动生成的`CMakeLists.txt`中的`target_link_libraries`中添加此共享库。
330
331注意:如图所示,在`add_library`中的`entry`是工程自动生成的`modename`,若要做修改,需和步骤3中`.nm_modname`保持一致。
332
333![netmanager-4.png](./figures/websocket-notemod.png)
334
3357、调用WebSocket C API接口要求应用拥有`ohos.permission.INTERNET`权限,在`module.json5`中的`requestPermissions`项添加该权限。
336
337经过以上步骤,整个工程的搭建已经完成,接下来就可以连接设备运行工程进行日志查看了。
338
339## 测试步骤
340
3411、连接设备,使用DevEco Studio打开搭建好的工程。
342
3432、运行工程,设备上会弹出以下图片所示界面:
344
345![demo初始画面](./figures/websocket-demo-1.jpg)
346
347简要说明:
348
349- 在第一行的输入框中,输入`ws://`或`wss://`开头的WebSocket URL。
350
351- 在输入完WebSocket URL,点击`Connect`按钮后,如果访问成功,会触发onOpen的回调,打印日志。
352
353- 在Content输入框里输入要发送给服务器的内容,点击`Send`按钮发送。如果服务器返回消息,会触发onMessage回调,打印日志。
354
355- 点击`Close`按钮,WebSocket连接释放,可以重新输入新的WebSocket URL。
356
357![demo输入界面](./figures/websocket-demo-2.jpg)
358
359![demo日志输出](./figures/websocket-demo-log.png)
360
361## 相关实例
362
363针对WebSocket连接的开发,有以下相关实例可供参考:
364
365- [WebSocket连接(C/C++)](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/DocsSample/NetWork_Kit/NetWorkKit_Datatransmission/WebSocket_C)
366