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