• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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```