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