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 334 3357、调用WebSocket C API接口要求应用拥有`ohos.permission.INTERNET`权限,在`module.json5`中的`requestPermissions`项添加该权限。 336 337经过以上步骤,整个工程的搭建已经完成,接下来就可以连接设备运行工程进行日志查看了。 338 339## 测试步骤 340 3411、连接设备,使用DevEco Studio打开搭建好的工程。 342 3432、运行工程,设备上会弹出以下图片所示界面: 344 345 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 358 359 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