1# 连接和传输数据 2 3<!--Kit: Connectivity Kit--> 4<!--Subsystem: Communication--> 5<!--Owner: @enjoy_sunshine--> 6<!--Designer: @chengguohong; @tangjia15--> 7<!--Tester: @wangfeng517--> 8<!--Adviser: @zhang_yixin13--> 9 10## 简介 11本指南主要提供了基于串口通信协议(Serial Port Profile,SPP)实现设备间连接和传输数据的开发指导。当两个设备间进行SPP通信交互时,依据设备功能的不同,可区分为客户端与服务端,本指南将分别介绍客户端与服务端的实现方法。 12 13## 实现原理 14客户端获取到服务端的设备地址后,即可向服务端特定的UUID发起连接。服务端设备地址可以通过查找设备流程获取,详见[查找设备](br-discovery-development-guide.md)。待两端连接成功后,可向服务端发送数据或者接收服务端的数据。 15 16服务端需要支持客户端连接的UUID服务,保持连接状态监听即可。待两端连接成功后,即可接收客户端数据或者向客户端发送数据。 17 18客户端和服务端都可以主动断开连接,应用需要根据实际场景决定由哪一端执行断开操作。 19 20## 开发步骤 21 22### 申请蓝牙权限 23需要申请权限ohos.permission.ACCESS_BLUETOOTH。如何配置和申请权限,请参考[声明权限](../../security/AccessToken/declare-permissions.md)和[向用户申请授权](../../security/AccessToken/request-user-authorization.md)。 24 25### 导入所需API模块 26导入socket和错误码模块。 27```ts 28import { socket } from '@kit.ConnectivityKit'; 29import { BusinessError } from '@kit.BasicServicesKit'; 30``` 31 32### 客户端 33 34**1. 发起连接**<br> 35客户端通过查找设备流程搜索到目标设备后,即可发起连接。需要连接的UUID服务,必须与服务端创建socket时构造的UUID服务一致。在连接过程中,蓝牙子系统会去查询服务端是否支持该UUID服务,若不支持,则会连接失败。因此应用需要确保目标设备是否支持需要的UUID服务,否则发起的是无效连接。 36```ts 37// 设备地址可以通过查找设备流程获取 38let peerDevice = 'XX:XX:XX:XX:XX:XX'; 39 40// 定义客户端socket id 41let clientNumber = -1; 42 43// 配置连接参数 44let option: socket.SppOptions = { 45 uuid: '00009999-0000-1000-8000-00805F9B34FB', // 需要连接的服务端UUID服务,确保服务端支持 46 secure: false, 47 type: socket.SppType.SPP_RFCOMM 48}; 49console.info('startConnect ' + peerDevice); 50socket.sppConnect(peerDevice, option, (err, num: number) => { 51 if (err) { 52 console.error('startConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 53 } else { 54 console.info('startConnect clientNumber: ' + num); 55 clientNumber = num; 56 } 57}); 58console.info('startConnect after ' + peerDevice); 59``` 60 61**2. 传输数据**<br> 62 63**2.1 发送数据**<br> 64待客户端和服务端建立的连接建立成功后,即可向服务端发送数据。 65```ts 66let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id 67let arrayBuffer = new ArrayBuffer(2); 68let data = new Uint8Array(arrayBuffer); 69data[0] = 3; 70data[1] = 4; 71try { 72 socket.sppWrite(clientNumber, arrayBuffer); 73} catch (err) { 74 console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 75} 76``` 77 78**2.2 接收数据**<br> 79待客户端和服务端建立的连接建立成功后,即可接收服务端的数据。通过订阅读取数据接口[socket.on('sppRead')](../../reference/apis-connectivity-kit/js-apis-bluetooth-socket.md#socketonsppread)实现。 80```ts 81let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id 82 83// 定义接收数据的回调函数 84function read(dataBuffer: ArrayBuffer) { 85 let data = new Uint8Array(dataBuffer); 86 console.info('client data: ' + JSON.stringify(data)); 87} 88 89try { 90 // 发起订阅 91 socket.on('sppRead', clientNumber, read); 92} catch (err) { 93 console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 94} 95``` 96 97**3. 断开连接**<br> 98当应用不再需要已建立的连接时,可以通过客户端主动断开连接。需要先取消读取数据的订阅,再断开连接。 99```ts 100let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id 101 102// 定义接收数据的回调函数 103function read(dataBuffer: ArrayBuffer) { 104 let data = new Uint8Array(dataBuffer); 105 console.info('client data: ' + JSON.stringify(data)); 106} 107 108try { 109 // 取消接收数据订阅 110 socket.off('sppRead', clientNumber, read); 111} catch (err) { 112 console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 113} 114try { 115 // 从client端断开连接 116 socket.sppCloseClientSocket(clientNumber); 117} catch (err) { 118 console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 119} 120``` 121 122### 服务端 123 124**1. 创建服务端套接字**<br> 125服务端需通过创建套接字的方式,在蓝牙子系统中注册指定的UUID服务。该UUID服务的名称无限制,可使用应用名称。当客户端发起连接请求时,会携带一个UUID以表示所需连接的服务。只有服务端与客户端的UUID一致时,连接才能成功建立。 126```ts 127// 定义服务端socket id 128let serverNumber = -1; 129 130// 配置监听参数 131let option: socket.SppOptions = { 132 uuid: '00009999-0000-1000-8000-00805F9B34FB', 133 secure: false, 134 type: socket.SppType.SPP_RFCOMM 135}; 136 137// 创建服务端监听socket,将在蓝牙子系统中注册该UUID服务 138socket.sppListen("demonstration", option, (err, num: number) => { 139 if (err) { 140 console.error('sppListen errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 141 } else { 142 console.info('sppListen serverNumber: ' + num); 143 serverNumber = num; 144 } 145}); 146``` 147 148**2. 监听客户端连接**<br> 149创建好服务端套接字后,服务端即可监听连接。待收到客户端连接后,会获取到标识此次客户端的socket id,此时也表示服务端和客户端的连接已建立成功。 150```ts 151let serverNumber = 1; // 注意:该值需要的是创建服务端套接字时,异步callback获取到的服务端socket id,此处是伪代码id 152 153// 定义客户端socket id 154let clientNumber = -1; 155 156socket.sppAccept(serverNumber, (err, num: number) => { 157 if (err) { 158 console.error('accept errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 159 } else { 160 console.info('accept clientNumber: ' + num); 161 clientNumber = num; 162 } 163}); 164``` 165 166**3. 传输数据**<br> 167 168**3.1 发送数据**<br> 169待服务端和客户端的连接建立成功后,即可向客户端发送数据。 170```ts 171let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id 172 173let arrayBuffer = new ArrayBuffer(2); 174let data = new Uint8Array(arrayBuffer); 175data[0] = 9; 176data[1] = 8; 177try { 178 socket.sppWrite(clientNumber, arrayBuffer); 179} catch (err) { 180 console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 181} 182``` 183 184**3.2 接收数据**<br> 185待服务端和客户端的连接建立成功后,即可接收客户端的数据。通过订阅读取数据接口[socket.on('sppRead')](../../reference/apis-connectivity-kit/js-apis-bluetooth-socket.md#socketonsppread)实现。 186```ts 187let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id 188 189// 定义接收数据的回调函数 190function read(dataBuffer: ArrayBuffer) { 191 let data = new Uint8Array(dataBuffer); 192 console.info('client data: ' + JSON.stringify(data)); 193} 194 195try { 196 // 发起订阅 197 socket.on('sppRead', clientNumber, read); 198} catch (err) { 199 console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 200} 201``` 202**4. 断开连接**<br> 203当应用不再需要已建立的连接时,可以通过服务端主动断开连接。 204 205- 需要先取消读取数据的订阅,再断开连接。 206```ts 207let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id 208 209// 定义接收数据的回调函数 210function read(dataBuffer: ArrayBuffer) { 211 let data = new Uint8Array(dataBuffer); 212 console.info('client data: ' + JSON.stringify(data)); 213} 214 215try { 216 // 取消订阅 217 socket.off('sppRead', clientNumber, read); 218} catch (err) { 219 console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 220} 221try { 222 // 从server断开连接 223 socket.sppCloseClientSocket(clientNumber); 224} catch (err) { 225 console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 226} 227``` 228 229**5. 删除服务端套接字**<br> 230当应用不再需要该服务端套接字时,需要主动关闭创建时获取到的套接字,蓝牙子系统会删除此前注册的UUID服务。如果此时客户端发起连接,就会连接失败。 231 232- 应用也可以通过删除套接字时,实现断开连接。在此之前,需要先取消读取数据的订阅。 233```ts 234let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id 235let serverNumber = 1; // 注意:该值需要的是创建服务端套接字时,异步callback获取到的服务端socket id,此处是伪代码id 236 237// 定义接收数据的回调函数 238function read(dataBuffer: ArrayBuffer) { 239 let data = new Uint8Array(dataBuffer); 240 console.info('client data: ' + JSON.stringify(data)); 241} 242 243try { 244 // 取消订阅 245 socket.off('sppRead', clientNumber, read); 246} catch (err) { 247 console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 248} 249 250try { 251 // 若应用不再需要此能力,则主动删除 252 socket.sppCloseServerSocket(serverNumber); 253} catch (err) { 254 console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 255} 256``` 257 258## 完整示例 259 260### 客户端 261```ts 262import { socket } from '@kit.ConnectivityKit' 263import { BusinessError } from '@kit.BasicServicesKit'; 264 265class SppClientManager { 266 // 定义客户端的socket id 267 clientNumber: number = -1; 268 269 // 发起连接 270 public startConnect(peerDevice: string): void { 271 // 配置连接参数 272 let option: socket.SppOptions = { 273 uuid: '00009999-0000-1000-8000-00805F9B34FB', // 需要连接的服务端UUID服务,确保服务端支持 274 secure: false, 275 type: socket.SppType.SPP_RFCOMM 276 }; 277 console.info('startConnect ' + peerDevice); 278 socket.sppConnect(peerDevice, option, (err, num: number) => { 279 if (err) { 280 console.error('startConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 281 } else { 282 console.info('startConnect clientNumber: ' + num); 283 this.clientNumber = num; 284 } 285 }); 286 console.info('startConnect after ' + peerDevice); 287 } 288 289 // 发送数据 290 public sendData() { 291 console.info('sendData ' + this.clientNumber); 292 if (this.clientNumber == -1) { 293 console.error('invalid clientNumber'); 294 return; 295 } 296 let arrayBuffer = new ArrayBuffer(2); 297 let data = new Uint8Array(arrayBuffer); 298 data[0] = 3; 299 data[1] = 4; 300 try { 301 socket.sppWrite(this.clientNumber, arrayBuffer); 302 } catch (err) { 303 console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 304 } 305 } 306 307 // 定义接收数据的回调函数 308 read = (dataBuffer: ArrayBuffer) => { 309 let data = new Uint8Array(dataBuffer); 310 console.info('client data: ' + JSON.stringify(data)); 311 }; 312 313 // 接收数据 314 public readData() { 315 try { 316 // 发起订阅 317 if (this.clientNumber == -1) { 318 console.error('invalid clientNumber'); 319 return; 320 } 321 socket.on('sppRead', this.clientNumber, this.read); 322 } catch (err) { 323 console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 324 } 325 } 326 327 // 断开连接 328 public stopConnect() { 329 console.info('closeSppClient ' + this.clientNumber); 330 if (this.clientNumber == -1) { 331 console.error('invalid clientNumber'); 332 return; 333 } 334 try { 335 // 取消接收数据订阅 336 socket.off('sppRead', this.clientNumber, this.read); 337 } catch (err) { 338 console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 339 } 340 try { 341 // 从client端断开连接 342 socket.sppCloseClientSocket(this.clientNumber); 343 } catch (err) { 344 console.error('stopConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 345 } 346 } 347} 348 349let sppClientManager = new SppClientManager(); 350export default sppClientManager as SppClientManager; 351``` 352 353### 服务端 354```ts 355import { socket } from '@kit.ConnectivityKit' 356import { BusinessError } from '@kit.BasicServicesKit'; 357 358class SppServerManager { 359 serverNumber: number = -1; 360 clientNumber: number = -1; 361 362 // 创建服务端监听socket 363 public startListen(): void { 364 console.info('startListen'); 365 366 // 配置监听参数 367 let option: socket.SppOptions = { 368 uuid: '00009999-0000-1000-8000-00805F9B34FB', 369 secure: false, 370 type: socket.SppType.SPP_RFCOMM 371 }; 372 373 // 创建服务端监听socket,将在蓝牙子系统中注册该UUID服务 374 socket.sppListen("demonstration", option, (err, num: number) => { 375 if (err) { 376 console.error('sppListen errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 377 } else { 378 console.info('sppListen serverNumber: ' + num); 379 this.serverNumber = num; 380 } 381 }); 382 } 383 384 // 监听连接请求,等待连接 385 public accept() { 386 console.info('accept ' + this.serverNumber); 387 if (this.serverNumber == -1) { 388 console.error('invalid serverNumber'); 389 return; 390 } 391 socket.sppAccept(this.serverNumber, (err, num: number) => { 392 if (err) { 393 console.error('accept errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 394 } else { 395 console.info('accept clientNumber: ' + num); 396 this.clientNumber = num; 397 } 398 }); 399 } 400 401 // 发送数据 402 public sendData() { 403 console.info('sendData serverNumber: ' + this.serverNumber + ' clientNumber: ' + this.clientNumber); 404 if (this.clientNumber == -1) { 405 console.error('invalid clientNumber'); 406 return; 407 } 408 let arrayBuffer = new ArrayBuffer(2); 409 let data = new Uint8Array(arrayBuffer); 410 data[0] = 9; 411 data[1] = 8; 412 try { 413 socket.sppWrite(this.clientNumber, arrayBuffer); 414 } catch (err) { 415 console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 416 } 417 } 418 419 // 定义接收数据的回调函数 420 read = (dataBuffer: ArrayBuffer) => { 421 let data = new Uint8Array(dataBuffer); 422 console.info('client data: ' + JSON.stringify(data)); 423 }; 424 425 // 接收数据 426 public readData() { 427 try { 428 // 发起订阅 429 if (this.clientNumber == -1) { 430 console.error('invalid clientNumber'); 431 return; 432 } 433 socket.on('sppRead', this.clientNumber, this.read); 434 } catch (err) { 435 console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 436 } 437 } 438 439 // 停止连接 440 public stopConnect() { 441 console.info('stopConnect'); 442 try { 443 // 取消订阅 444 if (this.clientNumber == -1) { 445 console.error('invalid clientNumber'); 446 return; 447 } 448 socket.off('sppRead', this.clientNumber, this.read); 449 } catch (err) { 450 console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 451 } 452 try { 453 // 从server断开连接 454 socket.sppCloseClientSocket(this.clientNumber); 455 } catch (err) { 456 console.error('stopConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 457 } 458 } 459 460 // 删除能力 461 public closeSppServer() { 462 console.info('closeSppServer'); 463 try { 464 // 若应用不再需要此能力,则主动删除 465 if (this.serverNumber == -1) { 466 console.error('invalid serverNumber'); 467 return; 468 } 469 socket.sppCloseServerSocket(this.serverNumber); 470 } catch (err) { 471 console.error('sppCloseServerSocket errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message); 472 } 473 } 474} 475 476let sppServerManager = new SppServerManager(); 477export default sppServerManager as SppServerManager; 478```