文档管理中心

连接和传输数据

简介

本指南主要提供了基于串口通信协议(Serial Port Profile,SPP)实现设备间连接和传输数据的开发指导。当两个设备间进行SPP通信交互时,依据设备功能的不同,可区分为客户端与服务端,本指南将分别介绍客户端与服务端的实现方法。

实现原理

客户端获取到服务端的设备地址后,即可向服务端特定的UUID发起连接。服务端设备地址可以通过查找设备流程获取,详见查找设备。待两端连接成功后,可向服务端发送数据或者接收服务端的数据。

服务端需要支持客户端连接的UUID服务,保持连接状态监听即可。待两端连接成功后,即可接收客户端数据或者向客户端发送数据。

客户端和服务端都可以主动断开连接,应用需要根据实际场景决定由哪一端执行断开操作。

开发步骤

申请蓝牙权限

需要申请权限ohos.permission.ACCESS_BLUETOOTH。如何配置和申请权限,请参考声明权限向用户申请授权

导入所需API模块

导入socket和错误码模块。

收起
自动换行
深色代码主题
复制
  1. import { socket } from '@kit.ConnectivityKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';

客户端

1. 发起连接

客户端通过查找设备流程搜索到目标设备后,即可发起连接。需要连接的UUID服务,必须与服务端创建socket时构造的UUID服务一致。在连接过程中,蓝牙子系统会去查询服务端是否支持该UUID服务,若不支持,则会连接失败。因此应用需要确保目标设备是否支持需要的UUID服务,否则发起的是无效连接。

收起
自动换行
深色代码主题
复制
  1. // 设备地址可以通过查找设备流程获取
  2. let peerDevice = 'XX:XX:XX:XX:XX:XX';
  3. // 定义客户端socket id
  4. let clientNumber = -1;
  5. // 配置连接参数
  6. let option: socket.SppOptions = {
  7. uuid: '00009999-0000-1000-8000-00805F9B34FB', // 需要连接的服务端UUID服务,确保服务端支持
  8. secure: false,
  9. type: socket.SppType.SPP_RFCOMM
  10. };
  11. console.info('startConnect ' + peerDevice);
  12. socket.sppConnect(peerDevice, option, (err, num: number) => {
  13. if (err) {
  14. console.error('startConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  15. } else {
  16. console.info('startConnect clientNumber: ' + num);
  17. clientNumber = num;
  18. }
  19. });
  20. console.info('startConnect after ' + peerDevice);

2. 传输数据

2.1 发送数据

待客户端和服务端连接建立成功后,即可向服务端发送数据。

收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. let arrayBuffer = new ArrayBuffer(2);
  3. let data = new Uint8Array(arrayBuffer);
  4. data[0] = 3;
  5. data[1] = 4;
  6. try {
  7. socket.sppWrite(clientNumber, arrayBuffer);
  8. } catch (err) {
  9. console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  10. }

2.2 接收数据

待客户端和服务端连接建立成功后,即可接收服务端的数据。通过订阅读取数据接口socket.on('sppRead')实现。

收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. // 定义接收数据的回调函数
  3. function read(dataBuffer: ArrayBuffer) {
  4. let data = new Uint8Array(dataBuffer);
  5. console.info('client data: ' + JSON.stringify(data));
  6. }
  7. try {
  8. // 发起订阅
  9. socket.on('sppRead', clientNumber, read);
  10. } catch (err) {
  11. console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  12. }

3. 断开连接

当应用不再需要已建立的连接时,可以通过客户端主动断开连接。需要先取消读取数据的订阅,再断开连接。

收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是客户端发起连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. // 定义接收数据的回调函数
  3. function read(dataBuffer: ArrayBuffer) {
  4. let data = new Uint8Array(dataBuffer);
  5. console.info('client data: ' + JSON.stringify(data));
  6. }
  7. try {
  8. // 取消接收数据订阅
  9. socket.off('sppRead', clientNumber, read);
  10. } catch (err) {
  11. console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  12. }
  13. try {
  14. // 从client端断开连接
  15. socket.sppCloseClientSocket(clientNumber);
  16. } catch (err) {
  17. console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  18. }

服务端

1. 创建服务端套接字

服务端需通过创建套接字的方式,在蓝牙子系统中注册指定的UUID服务。该UUID服务的名称无限制,可使用应用名称。当客户端发起连接请求时,会携带一个UUID以表示所需连接的服务。只有服务端与客户端的UUID一致时,连接才能成功建立。

收起
自动换行
深色代码主题
复制
  1. // 定义服务端socket id
  2. let serverNumber = -1;
  3. // 配置监听参数
  4. let option: socket.SppOptions = {
  5. uuid: '00009999-0000-1000-8000-00805F9B34FB',
  6. secure: false,
  7. type: socket.SppType.SPP_RFCOMM
  8. };
  9. // 创建服务端监听socket,将在蓝牙子系统中注册该UUID服务
  10. socket.sppListen("demonstration", option, (err, num: number) => {
  11. if (err) {
  12. console.error('sppListen errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  13. } else {
  14. console.info('sppListen serverNumber: ' + num);
  15. serverNumber = num;
  16. }
  17. });

2. 监听客户端连接

创建好服务端套接字后,服务端即可监听连接。待收到客户端连接后,会获取到标识此次客户端的socket id,此时也表示服务端和客户端的连接已建立成功。

收起
自动换行
深色代码主题
复制
  1. let serverNumber = 1; // 注意:该值需要的是创建服务端套接字时,异步callback获取到的服务端socket id,此处是伪代码id
  2. // 定义客户端socket id
  3. let clientNumber = -1;
  4. socket.sppAccept(serverNumber, (err, num: number) => {
  5. if (err) {
  6. console.error('accept errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  7. } else {
  8. console.info('accept clientNumber: ' + num);
  9. clientNumber = num;
  10. }
  11. });

3. 传输数据

3.1 发送数据

待服务端和客户端的连接建立成功后,即可向客户端发送数据。

收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. let arrayBuffer = new ArrayBuffer(2);
  3. let data = new Uint8Array(arrayBuffer);
  4. data[0] = 9;
  5. data[1] = 8;
  6. try {
  7. socket.sppWrite(clientNumber, arrayBuffer);
  8. } catch (err) {
  9. console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  10. }

3.2 接收数据

待服务端和客户端的连接建立成功后,即可接收客户端的数据。通过订阅读取数据接口socket.on('sppRead')实现。

收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. // 定义接收数据的回调函数
  3. function read(dataBuffer: ArrayBuffer) {
  4. let data = new Uint8Array(dataBuffer);
  5. console.info('client data: ' + JSON.stringify(data));
  6. }
  7. try {
  8. // 发起订阅
  9. socket.on('sppRead', clientNumber, read);
  10. } catch (err) {
  11. console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  12. }

4. 断开连接

当应用不再需要已建立的连接时,可以通过服务端主动断开连接。

  • 需要先取消读取数据的订阅,再断开连接。
收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. // 定义接收数据的回调函数
  3. function read(dataBuffer: ArrayBuffer) {
  4. let data = new Uint8Array(dataBuffer);
  5. console.info('client data: ' + JSON.stringify(data));
  6. }
  7. try {
  8. // 取消订阅
  9. socket.off('sppRead', clientNumber, read);
  10. } catch (err) {
  11. console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  12. }
  13. try {
  14. // 从server断开连接
  15. socket.sppCloseClientSocket(clientNumber);
  16. } catch (err) {
  17. console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  18. }

5. 删除服务端套接字

当应用不再需要该服务端套接字时,需要主动关闭创建时获取到的套接字,蓝牙子系统会删除此前注册的UUID服务。如果此时客户端发起连接,就会连接失败。

  • 应用也可以通过删除套接字时,实现断开连接。在此之前,需要先取消读取数据的订阅。
收起
自动换行
深色代码主题
复制
  1. let clientNumber = 1; // 注意:该值需要的是服务端监听连接时,异步callback获取到的客户端socket id,此处是伪代码id
  2. let serverNumber = 1; // 注意:该值需要的是创建服务端套接字时,异步callback获取到的服务端socket id,此处是伪代码id
  3. // 定义接收数据的回调函数
  4. function read(dataBuffer: ArrayBuffer) {
  5. let data = new Uint8Array(dataBuffer);
  6. console.info('client data: ' + JSON.stringify(data));
  7. }
  8. try {
  9. // 取消订阅
  10. socket.off('sppRead', clientNumber, read);
  11. } catch (err) {
  12. console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  13. }
  14. try {
  15. // 若应用不再需要此能力,则主动删除
  16. socket.sppCloseServerSocket(serverNumber);
  17. } catch (err) {
  18. console.error('errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  19. }

完整示例

客户端

收起
自动换行
深色代码主题
复制
  1. import { socket } from '@kit.ConnectivityKit'
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. class SppClientManager {
  4. // 定义客户端的socket id
  5. clientNumber: number = -1;
  6. // 发起连接
  7. public startConnect(peerDevice: string): void {
  8. // 配置连接参数
  9. let option: socket.SppOptions = {
  10. uuid: '00009999-0000-1000-8000-00805F9B34FB', // 需要连接的服务端UUID服务,确保服务端支持
  11. secure: false,
  12. type: socket.SppType.SPP_RFCOMM
  13. };
  14. console.info('startConnect ' + peerDevice);
  15. socket.sppConnect(peerDevice, option, (err, num: number) => {
  16. if (err) {
  17. console.error('startConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  18. } else {
  19. console.info('startConnect clientNumber: ' + num);
  20. this.clientNumber = num;
  21. }
  22. });
  23. console.info('startConnect after ' + peerDevice);
  24. }
  25. // 发送数据
  26. public sendData() {
  27. console.info('sendData ' + this.clientNumber);
  28. if (this.clientNumber == -1) {
  29. console.error('invalid clientNumber');
  30. return;
  31. }
  32. let arrayBuffer = new ArrayBuffer(2);
  33. let data = new Uint8Array(arrayBuffer);
  34. data[0] = 3;
  35. data[1] = 4;
  36. try {
  37. socket.sppWrite(this.clientNumber, arrayBuffer);
  38. } catch (err) {
  39. console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  40. }
  41. }
  42. // 定义接收数据的回调函数
  43. read = (dataBuffer: ArrayBuffer) => {
  44. let data = new Uint8Array(dataBuffer);
  45. console.info('client data: ' + JSON.stringify(data));
  46. };
  47. // 接收数据
  48. public readData() {
  49. try {
  50. // 发起订阅
  51. if (this.clientNumber == -1) {
  52. console.error('invalid clientNumber');
  53. return;
  54. }
  55. socket.on('sppRead', this.clientNumber, this.read);
  56. } catch (err) {
  57. console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  58. }
  59. }
  60. // 断开连接
  61. public stopConnect() {
  62. console.info('closeSppClient ' + this.clientNumber);
  63. if (this.clientNumber == -1) {
  64. console.error('invalid clientNumber');
  65. return;
  66. }
  67. try {
  68. // 取消接收数据订阅
  69. socket.off('sppRead', this.clientNumber, this.read);
  70. } catch (err) {
  71. console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  72. }
  73. try {
  74. // 从client端断开连接
  75. socket.sppCloseClientSocket(this.clientNumber);
  76. } catch (err) {
  77. console.error('stopConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  78. }
  79. }
  80. }
  81. let sppClientManager = new SppClientManager();
  82. export default sppClientManager as SppClientManager;

服务端

收起
自动换行
深色代码主题
复制
  1. import { socket } from '@kit.ConnectivityKit'
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. class SppServerManager {
  4. serverNumber: number = -1;
  5. clientNumber: number = -1;
  6. // 创建服务端监听socket
  7. public startListen(): void {
  8. console.info('startListen');
  9. // 配置监听参数
  10. let option: socket.SppOptions = {
  11. uuid: '00009999-0000-1000-8000-00805F9B34FB',
  12. secure: false,
  13. type: socket.SppType.SPP_RFCOMM
  14. };
  15. // 创建服务端监听socket,将在蓝牙子系统中注册该UUID服务
  16. socket.sppListen("demonstration", option, (err, num: number) => {
  17. if (err) {
  18. console.error('sppListen errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  19. } else {
  20. console.info('sppListen serverNumber: ' + num);
  21. this.serverNumber = num;
  22. }
  23. });
  24. }
  25. // 监听连接请求,等待连接
  26. public accept() {
  27. console.info('accept ' + this.serverNumber);
  28. if (this.serverNumber == -1) {
  29. console.error('invalid serverNumber');
  30. return;
  31. }
  32. socket.sppAccept(this.serverNumber, (err, num: number) => {
  33. if (err) {
  34. console.error('accept errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  35. } else {
  36. console.info('accept clientNumber: ' + num);
  37. this.clientNumber = num;
  38. }
  39. });
  40. }
  41. // 发送数据
  42. public sendData() {
  43. console.info('sendData serverNumber: ' + this.serverNumber + ' clientNumber: ' + this.clientNumber);
  44. if (this.clientNumber == -1) {
  45. console.error('invalid clientNumber');
  46. return;
  47. }
  48. let arrayBuffer = new ArrayBuffer(2);
  49. let data = new Uint8Array(arrayBuffer);
  50. data[0] = 9;
  51. data[1] = 8;
  52. try {
  53. socket.sppWrite(this.clientNumber, arrayBuffer);
  54. } catch (err) {
  55. console.error('sppWrite errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  56. }
  57. }
  58. // 定义接收数据的回调函数
  59. read = (dataBuffer: ArrayBuffer) => {
  60. let data = new Uint8Array(dataBuffer);
  61. console.info('client data: ' + JSON.stringify(data));
  62. };
  63. // 接收数据
  64. public readData() {
  65. try {
  66. // 发起订阅
  67. if (this.clientNumber == -1) {
  68. console.error('invalid clientNumber');
  69. return;
  70. }
  71. socket.on('sppRead', this.clientNumber, this.read);
  72. } catch (err) {
  73. console.error('readData errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  74. }
  75. }
  76. // 停止连接
  77. public stopConnect() {
  78. console.info('stopConnect');
  79. try {
  80. // 取消订阅
  81. if (this.clientNumber == -1) {
  82. console.error('invalid clientNumber');
  83. return;
  84. }
  85. socket.off('sppRead', this.clientNumber, this.read);
  86. } catch (err) {
  87. console.error('off sppRead errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  88. }
  89. try {
  90. // 从server断开连接
  91. socket.sppCloseClientSocket(this.clientNumber);
  92. } catch (err) {
  93. console.error('stopConnect errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  94. }
  95. }
  96. // 删除能力
  97. public closeSppServer() {
  98. console.info('closeSppServer');
  99. try {
  100. // 若应用不再需要此能力,则主动删除
  101. if (this.serverNumber == -1) {
  102. console.error('invalid serverNumber');
  103. return;
  104. }
  105. socket.sppCloseServerSocket(this.serverNumber);
  106. } catch (err) {
  107. console.error('sppCloseServerSocket errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);
  108. }
  109. }
  110. }
  111. let sppServerManager = new SppServerManager();
  112. export default sppServerManager as SppServerManager;
在 指南 中进行搜索
请输入您想要搜索的关键词