【鸿蒙开发】第二十五章 Network 网络服务

1 简介

Network Kit(网络服务)主要提供以下功能:

  • HTTP数据请求:通过HTTP发起一个数据请求。
  • WebSocket连接:使用WebSocket建立服务器与客户端的双向连接。
  • Socket连接:通过Socket进行数据传输。
  • 网络连接管理:网络连接管理提供管理网络一些基础能力,包括WiFi/蜂窝/Ethernet等多网络连接优先级管理、网络质量评估、订阅默认/指定网络连接状态变化、查询网络连接信息、DNS解析等功能。
  • MDNS管理:MDNS即多播DNS(Multicast DNS),提供局域网内的本地服务添加、移除、发现、解析等能力。

1.1 约束与限制

使用网络管理模块的相关功能时,需要请求相应的权限。
在申请权限前,请保证符合权限使用的基本原则。然后参考访问控制-声明权限声明对应权限。

权限名说明
ohos.permission.GET_NETWORK_INFO获取网络连接信息。
ohos.permission.INTERNET允许程序打开网络套接字,进行网络连接。

2 HTTP数据请求

通过HTTP发起数据请求,支持常见的GETPOSTOPTIONSHEADPUTDELETETRACECONNECT方法。
HTTP数据请求功能主要由http模块提供。
使用该功能需要申请ohos.permission.INTERNET权限。

接口名描述
createHttp()创建一个http请求。
request()根据URL地址,发起HTTP网络请求。
requestInStream()根据URL地址,发起HTTP网络请求并返回流式响应。
destroy()中断请求任务。
on(type: ‘headersReceive’)订阅HTTP Response Header 事件。
off(type: ‘headersReceive’)取消订阅HTTP Response Header 事件。
once(‘headersReceive’)订阅HTTP Response Header 事件,但是只触发一次。
on(‘dataReceive’)订阅HTTP流式响应数据接收事件。
off(‘dataReceive’)取消订阅HTTP流式响应数据接收事件。
on(‘dataEnd’)订阅HTTP流式响应数据接收完毕事件。
off(‘dataEnd’)取消订阅HTTP流式响应数据接收完毕事件。
on(‘dataReceiveProgress’)订阅HTTP流式响应数据接收进度事件。
off(‘dataReceiveProgress’)取消订阅HTTP流式响应数据接收进度事件。
on(‘dataSendProgress’)订阅HTTP网络请求数据发送进度事件。
off(‘dataSendProgress’)取消订阅HTTP网络请求数据发送进度事件。

2.1 request接口开发步骤

  1. 从@kit.NetworkKit中导入http命名空间。
  2. 调用createHttp()方法,创建一个HttpRequest对象。
  3. 调用该对象的on()方法,订阅http响应头事件,此接口会比request请求先返回。可以根据业务需要订阅此消息。
  4. 调用该对象的request()方法,传入http请求的url地址和可选参数,发起网络请求。
  5. 按照实际业务需要,解析返回结果。
  6. 调用该对象的off()方法,取消订阅http响应头事件。
  7. 当该请求使用完毕时,调用destroy()方法主动销毁。
// 引入包名
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头,此接口会比request请求先返回。可以根据业务需要订阅此消息
// 从API 8开始,使用on('headersReceive', Callback)替代on('headerReceive', AsyncCallback)。 8+
httpRequest.on('headersReceive', (header) => {console.info('header: ' + JSON.stringify(header));
});
httpRequest.request(// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定"EXAMPLE_URL",{method: http.RequestMethod.POST, // 可选,默认为http.RequestMethod.GET// 开发者根据自身业务需要添加header字段header: {'Content-Type': 'application/json'},// 当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定extraData: "data to send",expectDataType: http.HttpDataType.STRING, // 可选,指定返回数据的类型usingCache: true, // 可选,默认为truepriority: 1, // 可选,默认为1connectTimeout: 60000, // 可选,默认为60000msreadTimeout: 60000, // 可选,默认为60000msusingProtocol: http.HttpProtocol.HTTP1_1, // 可选,协议类型默认值由系统自动指定usingProxy: false, // 可选,默认不使用网络代理,自API 10开始支持该属性caPath:'/path/to/cacert.pem', // 可选,默认使用系统预制证书,自API 10开始支持该属性clientCert: { // 可选,默认不使用客户端证书,自API 11开始支持该属性certPath: '/path/to/client.pem', // 默认不使用客户端证书,自API 11开始支持该属性keyPath: '/path/to/client.key', // 若证书包含Key信息,传入空字符串,自API 11开始支持该属性certType: http.CertType.PEM, // 可选,默认使用PEM,自API 11开始支持该属性keyPassword: "passwordToKey" // 可选,输入key文件的密码,自API 11开始支持该属性},multiFormDataList: [ // 可选,仅当Header中,'content-Type'为'multipart/form-data'时生效,自API 11开始支持该属性{name: "Part1", // 数据名,自API 11开始支持该属性contentType: 'text/plain', // 数据类型,自API 11开始支持该属性data: 'Example data', // 可选,数据内容,自API 11开始支持该属性remoteFileName: 'example.txt' // 可选,自API 11开始支持该属性}, {name: "Part2", // 数据名,自API 11开始支持该属性contentType: 'text/plain', // 数据类型,自API 11开始支持该属性// data/app/el2/100/base/com.example.myapplication/haps/entry/files/fileName.txtfilePath: `${getContext(this).filesDir}/fileName.txt`, // 可选,传入文件路径,自API 11开始支持该属性remoteFileName: 'fileName.txt' // 可选,自API 11开始支持该属性}]}, (err: BusinessError, data: http.HttpResponse) => {if (!err) {// data.result为HTTP响应内容,可根据业务需要进行解析console.info('Result:' + JSON.stringify(data.result));console.info('code:' + JSON.stringify(data.responseCode));// data.header为HTTP响应头,可根据业务需要进行解析console.info('header:' + JSON.stringify(data.header));console.info('cookies:' + JSON.stringify(data.cookies)); // 8+// 当该请求使用完毕时,调用destroy方法主动销毁httpRequest.destroy();} else {console.error('error:' + JSON.stringify(err));// 取消订阅HTTP响应头事件httpRequest.off('headersReceive');// 当该请求使用完毕时,调用destroy方法主动销毁httpRequest.destroy();}}
);

2.2 requestInStream接口开发步骤

  1. 从@kit.NetworkKit中导入http命名空间。
  2. 调用createHttp()方法,创建一个HttpRequest对象。
  3. 调用该对象的on()方法,可以根据业务需要订阅HTTP响应头事件HTTP流式响应数据接收事件HTTP流式响应数据接收进度事件HTTP流式响应数据接收完毕事件
  4. 调用该对象的requestInStream()方法,传入http请求的url地址和可选参数,发起网络请求。
  5. 按照实际业务需要,可以解析返回的响应码。
  6. 调用该对象的off()方法,取消订阅响应事件。
  7. 当该请求使用完毕时,调用destroy()方法主动销毁。
// 引入包名
// 引入包名
import { http } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';// 每一个httpRequest对应一个HTTP请求任务,不可复用
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头事件
httpRequest.on('headersReceive', (header: Object) => {console.info('header: ' + JSON.stringify(header));
});
// 用于订阅HTTP流式响应数据接收事件
let res = new ArrayBuffer(0);
httpRequest.on('dataReceive', (data: ArrayBuffer) => {const newRes = new ArrayBuffer(res.byteLength + data.byteLength);const resView = new Uint8Array(newRes);resView.set(new Uint8Array(res));resView.set(new Uint8Array(data), res.byteLength);res = newRes;console.info('res length: ' + res.byteLength);
});
// 用于订阅HTTP流式响应数据接收完毕事件
httpRequest.on('dataEnd', () => {console.info('No more data in response, data receive end');
});
// 用于订阅HTTP流式响应数据接收进度事件
class Data {receiveSize: number = 0;totalSize: number = 0;
}
httpRequest.on('dataReceiveProgress', (data: Data) => {console.log("dataReceiveProgress receiveSize:" + data.receiveSize + ", totalSize:" + data.totalSize);
});let streamInfo: http.HttpRequestOptions = {method: http.RequestMethod.POST,  // 可选,默认为http.RequestMethod.GET// 开发者根据自身业务需要添加header字段header: {'Content-Type': 'application/json'},// 当使用POST请求时此字段用于传递请求体内容,具体格式与服务端协商确定extraData: "data to send",expectDataType:  http.HttpDataType.STRING,// 可选,指定返回数据的类型usingCache: true, // 可选,默认为truepriority: 1, // 可选,默认为1connectTimeout: 60000, // 可选,默认为60000msreadTimeout: 60000, // 可选,默认为60000ms。若传输的数据较大,需要较长的时间,建议增大该参数以保证数据传输正常终止usingProtocol: http.HttpProtocol.HTTP1_1 // 可选,协议类型默认值由系统自动指定
}// 填写HTTP请求的URL地址,可以带参数也可以不带参数。URL地址需要开发者自定义。请求的参数可以在extraData中指定
httpRequest.requestInStream("EXAMPLE_URL", streamInfo).then((data: number) => {console.info("requestInStream OK!");console.info('ResponseCode :' + JSON.stringify(data));// 取消订阅HTTP响应头事件httpRequest.off('headersReceive');// 取消订阅HTTP流式响应数据接收事件httpRequest.off('dataReceive');// 取消订阅HTTP流式响应数据接收进度事件httpRequest.off('dataReceiveProgress');// 取消订阅HTTP流式响应数据接收完毕事件httpRequest.off('dataEnd');// 当该请求使用完毕时,调用destroy方法主动销毁httpRequest.destroy();
}).catch((err: Error) => {console.info("requestInStream ERROR : err = " + JSON.stringify(err));
});

3 WebSocket连接

使用WebSocket建立服务器与客户端的双向连接,流程如下:

  1. 需要先通过createWebSocket()方法创建WebSocket对象,
  2. 然后通过connect()方法连接到服务器。
  3. 当连接成功后,客户端会收到open事件的回调,之后客户端就可以通过send()方法与服务器进行通信。
  4. 当服务器发信息给客户端时,客户端会收到message事件的回调。
  5. 当客户端不要此连接时,可以通过调用close()方法主动断开连接,
  6. 之后客户端会收到close事件的回调。

若在上述任一过程中发生错误,客户端会收到error事件的回调。

WebSocket连接功能主要由webSocket模块提供。使用该功能需要申请ohos.permission.INTERNET权限。具体接口说明如下表。

接口名描述
createWebSocket()创建一个WebSocket连接。
connect()根据URL地址,建立一个WebSocket连接。
send()通过WebSocket连接发送数据。
close()关闭WebSocket连接。
on(type: ‘open’)订阅WebSocket的打开事件。
off(type: ‘open’)取消订阅WebSocket的打开事件。
on(type: ‘message’)订阅WebSocket的接收到服务器消息事件。
off(type: ‘message’)取消订阅WebSocket的接收到服务器消息事件。
on(type: ‘close’)订阅WebSocket的关闭事件。
off(type: ‘close’)取消订阅WebSocket的关闭事件
on(type: ‘error’)订阅WebSocket的Error事件。
off(type: ‘error’)取消订阅WebSocket的Error事件。

3.1 开发步骤

  1. 导入需要的webSocket模块
  2. 创建一个WebSocket连接,返回一个WebSocket对象。
  3. (可选)订阅WebSocket的打开、消息接收、关闭、Error事件。
  4. 根据URL地址,发起WebSocket连接。
  5. 使用完WebSocket连接之后,主动断开连接。
import { webSocket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';let defaultIpAddress = "ws://";
let ws = webSocket.createWebSocket();
ws.on('open', (err: BusinessError, value: Object) => {console.log("on open, status:" + JSON.stringify(value));// 当收到on('open')事件时,可以通过send()方法与服务器进行通信ws.send("Hello, server!", (err: BusinessError, value: boolean) => {if (!err) {console.log("Message sent successfully");} else {console.log("Failed to send the message. Err:" + JSON.stringify(err));}});
});
ws.on('message', (err: BusinessError, value: string | ArrayBuffer) => {console.log("on message, message:" + value);// 当收到服务器的`bye`消息时(此消息字段仅为示意,具体字段需要与服务器协商),主动断开连接if (value === 'bye') {ws.close((err: BusinessError, value: boolean) => {if (!err) {console.log("Connection closed successfully");} else {console.log("Failed to close the connection. Err: " + JSON.stringify(err));}});}
});
ws.on('close', (err: BusinessError, value: webSocket.CloseResult) => {console.log("on close, code is " + value.code + ", reason is " + value.reason);
});
ws.on('error', (err: BusinessError) => {console.log("on error, error:" + JSON.stringify(err));
});
ws.connect(defaultIpAddress, (err: BusinessError, value: boolean) => {if (!err) {console.log("Connected successfully");} else {console.log("Connection failed. Err:" + JSON.stringify(err));}
});

4 Socket 连接

Socket 连接主要是通过 Socket 进行数据传输,支持 TCP/UDP/Multicast/TLS 协议。

  • Socket:套接字,就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
  • TCP:传输控制协议(Transmission Control Protocol)。是一种面向连接的、可靠的、基于字节流的传输层通信协议。
  • UDP:用户数据报协议(User Datagram Protocol)。是一个简单的面向消息的传输层,不需要连接。
  • Multicast:多播,基于UDP的一种通信模式,用于实现组内所有设备之间广播形式的通信。
  • LocalSocket:本地套接字,IPC(Inter-Process Communication)进程间通信的一种,实现设备内进程之间相互通信,无需网络。
  • TLS:安全传输层协议(Transport Layer Security)。用于在两个通信应用程序之间提供保密性和数据完整性。

Socket 连接主要由 socket 模块提供。具体接口说明如下表。

接口名描述
constructUDPSocketInstance()创建一个 UDPSocket 对象。
constructTCPSocketInstance()创建一个 TCPSocket 对象。
constructTCPSocketServerInstance()创建一个 TCPSocketServer 对象。
constructMulticastSocketInstance()创建一个 MulticastSocket 对象。
constructLocalSocketInstance()创建一个 LocalSocket 对象。
constructLocalSocketServerInstance()创建一个 LocalSocketServer 对象。
listen()绑定、监听并启动服务,接收客户端的连接请求。(仅 TCP/LocalSocket 支持)。
bind()绑定 IP 地址和端口,或是绑定本地套接字路径。
send()发送数据。
close()关闭连接。
getState()获取 Socket 状态。
connect()连接到指定的 IP 地址和端口,或是连接到本地套接字(仅 TCP/LocalSocket 支持)。
getRemoteAddress()获取对端 Socket 地址(仅 TCP 支持,需要先调用 connect 方法)。
setExtraOptions()设置 Socket 连接的其他属性。
getExtraOptions()获取 Socket 连接的其他属性(仅 LocalSocket 支持)。
addMembership()加入到指定的多播组 IP 中 (仅 Multicast 支持)。
dropMembership()从指定的多播组 IP 中退出 (仅 Multicast 支持)。
setMulticastTTL()设置数据传输跳数 TTL (仅 Multicast 支持)。
getMulticastTTL()获取数据传输跳数 TTL (仅 Multicast 支持)。
setLoopbackMode()设置回环模式,允许主机在本地循环接收自己发送的多播数据包 (仅 Multicast 支持)。
getLoopbackMode()获取回环模式开启或关闭的状态 (仅 Multicast 支持)。
on(type: ‘message’)订阅 Socket 连接的接收消息事件。
off(type: ‘message’)取消订阅 Socket 连接的接收消息事件。
on(type: ‘close’)订阅 Socket 连接的关闭事件。
off(type: ‘close’)取消订阅 Socket 连接的关闭事件。
on(type: ‘error’)订阅 Socket 连接的 Error 事件。
off(type: ‘error’)取消订阅 Socket 连接的 Error 事件。
on(type: ‘listening’)订阅 UDPSocket 连接的数据包消息事件(仅 UDP 支持)。
off(type: ‘listening’)取消订阅 UDPSocket 连接的数据包消息事件(仅 UDP 支持)。
on(type: ‘connect’)订阅 Socket 的连接事件(仅 TCP/LocalSocket 支持)。
off(type: ‘connect’)取消订阅 Socket 的连接事件(仅 TCP/LocalSocket 支持)。

TLS Socket 连接主要由 tls_socket 模块提供。具体接口说明如下表。

接口名功能描述
constructTLSSocketInstance()创建一个 TLSSocket 对象。
bind()绑定 IP 地址和端口号。
close(type: ‘error’)关闭连接。
connect()连接到指定的 IP 地址和端口。
getCertificate()返回表示本地证书的对象。
getCipherSuite()返回包含协商的密码套件信息的列表。
getProtocol()返回包含当前连接协商的 SSL/TLS 协议版本的字符串。
getRemoteAddress()获取 TLSSocket 连接的对端地址。
getRemoteCertificate()返回表示对等证书的对象。
getSignatureAlgorithms()在服务器和客户端之间共享的签名算法列表,按优先级降序排列。
getState()获取 TLSSocket 连接的状态。
off(type: ‘close’)取消订阅 TLSSocket 连接的关闭事件。
off(type: ‘error’)取消订阅 TLSSocket 连接的 Error 事件。
off(type: ‘message’)取消订阅 TLSSocket 连接的接收消息事件。
on(type: ‘close’)订阅 TLSSocket 连接的关闭事件。
on(type: ‘error’)订阅 TLSSocket 连接的 Error 事件。
on(type: ‘message’)订阅 TLSSocket 连接的接收消息事件。
send() 发送数据。
setExtraOptions()设置 TLSSocket 连接的其他属性。

4.1 Socket 连接主要场景

应用通过 Socket 进行数据传输,支持 TCP/UDP/Multicast/TLS 协议。主要场景有:

  1. 应用通过 TCP/UDP Socket进行数据传输
  2. 应用通过 TCP Socket Server 进行数据传输
  3. 应用通过 Multicast Socket 进行数据传输
  4. 应用通过 Local Socket进行数据传输
  5. 应用通过 Local Socket Server 进行数据传输
  6. 应用通过 TLS Socket 进行加密数据传输
  7. 应用通过 TLS Socket Server 进行加密数据传输

4.1.1 TCP/UDP Socket进行数据传输

UDP 与 TCP 流程大体类似,下面以 TCP 为例:

  1. import 需要的 socket 模块。
  2. 创建一个 TCPSocket 连接,返回一个 TCPSocket 对象。
  3. (可选)订阅 TCPSocket 相关的订阅事件。
  4. 绑定 IP 地址和端口,端口可以指定或由系统随机分配。
  5. 连接到指定的 IP 地址和端口。
  6. 发送数据。
  7. Socket 连接使用完毕后,主动关闭。
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
// 创建一个TCPSocket连接,返回一个TCPSocket对象。
let tcp: socket.TCPSocket = socket.constructTCPSocketInstance();
tcp.on('message', (value: SocketInfo) => {console.log("on message");let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("on connect received:" + str);
});
tcp.on('connect', () => {console.log("on connect");
});
tcp.on('close', () => {console.log("on close");
});// 绑定本地IP地址和端口。
let ipAddress : socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 1234;
tcp.bind(ipAddress, (err: BusinessError) => {if (err) {console.log('bind fail');return;}console.log('bind success');// 连接到指定的IP地址和端口。ipAddress.address = "192.168.xxx.xxx";ipAddress.port = 5678;let tcpConnect : socket.TCPConnectOptions = {} as socket.TCPConnectOptions;tcpConnect.address = ipAddress;tcpConnect.timeout = 6000;tcp.connect(tcpConnect).then(() => {console.log('connect success');let tcpSendOptions: socket.TCPSendOptions = {data: 'Hello, server!'}tcp.send(tcpSendOptions).then(() => {console.log('send success');}).catch((err: BusinessError) => {console.log('send fail');});}).catch((err: BusinessError) => {console.log('connect fail');});
});// 连接使用完毕后,主动关闭。取消相关事件的订阅。
setTimeout(() => {tcp.close().then(() => {console.log('close success');}).catch((err: BusinessError) => {console.log('close fail');});tcp.off('message');tcp.off('connect');tcp.off('close');
}, 30 * 1000);

4.1.2 TCP Socket Server 进行数据传输

服务端 TCP Socket 流程:

  1. import 需要的 socket 模块。
  2. 创建一个 TCPSocketServer 连接,返回一个 TCPSocketServer 对象。
  3. 绑定本地 IP 地址和端口,监听并接受与此套接字建立的客户端 TCPSocket 连接。
  4. 订阅 TCPSocketServer 的 connect 事件,用于监听客户端的连接状态。
  5. 客户端与服务端建立连接后,返回一个 TCPSocketConnection 对象,用于与客户端通信。
  6. 订阅 TCPSocketConnection 相关的事件,通过 TCPSocketConnection 向客户端发送数据。
  7. 主动关闭与客户端的连接。
  8. 取消 TCPSocketConnection 和 TCPSocketServer 相关事件的订阅。
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';// 创建一个TCPSocketServer连接,返回一个TCPSocketServer对象。
let tcpServer: socket.TCPSocketServer = socket.constructTCPSocketServerInstance();
// 绑定本地IP地址和端口,进行监听let ipAddress : socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 4651;
tcpServer.listen(ipAddress).then(() => {console.log('listen success');
}).catch((err: BusinessError) => {console.log('listen fail');
});class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
// 订阅TCPSocketServer的connect事件
tcpServer.on("connect", (client: socket.TCPSocketConnection) => {// 订阅TCPSocketConnection相关的事件client.on("close", () => {console.log("on close success");});client.on("message", (value: SocketInfo) => {let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("received message--:" + str);console.log("received address--:" + value.remoteInfo.address);console.log("received family--:" + value.remoteInfo.family);console.log("received port--:" + value.remoteInfo.port);console.log("received size--:" + value.remoteInfo.size);});// 向客户端发送数据let tcpSendOptions : socket.TCPSendOptions = {} as socket.TCPSendOptions;tcpSendOptions.data = 'Hello, client!';client.send(tcpSendOptions).then(() => {console.log('send success');}).catch((err: Object) => {console.error('send fail: ' + JSON.stringify(err));});// 关闭与客户端的连接client.close().then(() => {console.log('close success');}).catch((err: BusinessError) => {console.log('close fail');});// 取消TCPSocketConnection相关的事件订阅setTimeout(() => {client.off("message");client.off("close");}, 10 * 1000);
});// 取消TCPSocketServer相关的事件订阅
setTimeout(() => {tcpServer.off("connect");
}, 30 * 1000);

4.1.3 Multicast Socket 进行数据传输

  1. import 需要的 socket 模块。
  2. 创建 multicastSocket 多播对象。
  3. 指定多播 IP 与端口,加入多播组。
  4. 开启消息 message 监听。
  5. 发送数据,数据以广播的形式传输,同一多播组中已经开启消息 message 监听的多播对象都会接收到数据。
  6. 关闭 message 消息的监听。
  7. 退出多播组。
import { socket } from '@kit.NetworkKit';// 创建Multicast对象
let multicast: socket.MulticastSocket = socket.constructMulticastSocketInstance();let addr : socket.NetAddress = {address: '239.255.0.1',port: 32123,family: 1
}// 加入多播组
multicast.addMembership(addr).then(() => {console.log('addMembership success');
}).catch((err: Object) => {console.log('addMembership fail');
});// 开启监听消息数据,将接收到的ArrayBuffer类型数据转换为String
class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
multicast.on('message', (data: SocketInfo) => {console.info('接收的数据: ' + JSON.stringify(data))const uintArray = new Uint8Array(data.message)let str = ''for (let i = 0; i < uintArray.length; ++i) {str += String.fromCharCode(uintArray[i])}console.info(str)
})// 发送数据
multicast.send({ data:'Hello12345', address: addr }).then(() => {console.log('send success');
}).catch((err: Object) => {console.log('send fail, ' + JSON.stringify(err));
});// 关闭消息的监听
multicast.off('message')// 退出多播组
multicast.dropMembership(addr).then(() => {console.log('drop membership success');
}).catch((err: Object) => {console.log('drop membership fail');
});

4.1.4 Local Socket进行数据传输

  1. import 需要的 socket 模块。
  2. 使用 constructLocalSocketInstance 接口,创建一个 LocalSocket 客户端对象。
  3. 注册 LocalSocket 的消息(message)事件,以及一些其它事件(可选)。
  4. 连接到指定的本地套接字文件路径。
  5. 发送数据。
  6. Socket 连接使用完毕后,取消事件的注册,并关闭套接字。
import { socket } from '@kit.NetworkKit';// 创建一个LocalSocket连接,返回一个LocalSocket对象。
let client: socket.LocalSocket = socket.constructLocalSocketInstance();
client.on('message', (value: socket.LocalSocketMessageInfo) => {const uintArray = new Uint8Array(value.message)let messageView = '';for (let i = 0; i < uintArray.length; i++) {messageView += String.fromCharCode(uintArray[i]);}console.log('total receive: ' + JSON.stringify(value));console.log('message information: ' + messageView);
});
client.on('connect', () => {console.log("on connect");
});
client.on('close', () => {console.log("on close");
});// 传入指定的本地套接字路径,连接服务端。
let sandboxPath: string = getContext(this).filesDir + '/testSocket'
let localAddress : socket.LocalAddress = {address: sandboxPath
}
let connectOpt: socket.LocalConnectOptions = {address: localAddress,timeout: 6000
}
let sendOpt: socket.LocalSendOptions = {data: 'Hello world!'
}
client.connect(connectOpt).then(() => {console.log('connect success')client.send(sendOpt).then(() => {console.log('send success')}).catch((err: Object) => {console.log('send failed: ' + JSON.stringify(err))})
}).catch((err: Object) => {console.log('connect fail: ' + JSON.stringify(err));
});// 当不需要再连接服务端,需要断开且取消事件的监听时
client.off('message');
client.off('connect');
client.off('close');
client.close().then(() => {console.log('close client success')
}).catch((err: Object) => {console.log('close client err: ' + JSON.stringify(err))
})

4.1.5 Local Socket Server 进行数据传输

服务端 LocalSocket Server 流程:

  1. import 需要的 socket 模块。
  2. 使用 constructLocalSocketServerInstance 接口,创建一个 LocalSocketServer 服务端对象。
  3. 启动服务,绑定本地套接字路径,创建出本地套接字文件,监听客户端的连接请求。
  4. 注册 LocalSocket 的客户端连接(connect)事件,以及一些其它事件(可选)。
  5. 在客户端连接上来时,通过连接事件的回调函数,获取连接会话对象。
  6. 给会话对象 LocalSocketConnection 注册消息(message)事件,以及一些其它事件(可选)。
  7. 通过会话对象主动向客户端发送消息。
  8. 结束与客户端的通信,主动断开与客户端的连接。
  9. 取消 LocalSocketConnection 和 LocalSocketServer 相关事件的订阅。
import { socket } from '@kit.NetworkKit';// 创建一个LocalSocketServer连接,返回一个LocalSocketServer对象。
let server: socket.LocalSocketServer = socket.constructLocalSocketServerInstance();
// 创建并绑定本地套接字文件testSocket,进行监听
let sandboxPath: string = getContext(this).filesDir + '/testSocket'
let listenAddr: socket.LocalAddress = {address: sandboxPath
}
server.listen(listenAddr).then(() => {console.log("listen success");
}).catch((err: Object) => {console.log("listen fail: " + JSON.stringify(err));
});// 订阅LocalSocketServer的connect事件
server.on('connect', (connection: socket.LocalSocketConnection) => {// 订阅LocalSocketConnection相关的事件connection.on('error', (err: Object) => {console.log("on error success");});connection.on('message', (value: socket.LocalSocketMessageInfo) => {const uintArray = new Uint8Array(value.message);let messageView = '';for (let i = 0; i < uintArray.length; i++) {messageView += String.fromCharCode(uintArray[i]);}console.log('total: ' + JSON.stringify(value));console.log('message information: ' + messageView);});connection.on('error', (err: Object) => {console.log("err:" + JSON.stringify(err));})// 向客户端发送数据let sendOpt : socket.LocalSendOptions = {data: 'Hello world!'};connection.send(sendOpt).then(() => {console.log('send success');}).catch((err: Object) => {console.log('send failed: ' + JSON.stringify(err));})// 关闭与客户端的连接connection.close().then(() => {console.log('close success');}).catch((err: Object) => {console.log('close failed: ' + JSON.stringify(err));});// 取消LocalSocketConnection相关的事件订阅connection.off('message');connection.off('error');
});// 取消LocalSocketServer相关的事件订阅
server.off('connect');
server.off('error');

4.1.6 TLS Socket 进行加密数据传输

客户端 TLS Socket 流程:

  1. import 需要的 socket 模块。
  2. 绑定服务器 IP 和端口号。
  3. 双向认证上传客户端 CA 证书及数字证书;单向认证上传客户端 CA 证书。
  4. 创建一个 TLSSocket 连接,返回一个 TLSSocket 对象。
  5. (可选)订阅 TLSSocket 相关的订阅事件。
  6. 发送数据。
  7. TLSSocket 连接使用完毕后,主动关闭。
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
// 创建一个(双向认证)TLS Socket连接,返回一个TLS Socket对象。
let tlsTwoWay: socket.TLSSocket = socket.constructTLSSocketInstance();
// 订阅TLS Socket相关的订阅事件
tlsTwoWay.on('message', (value: SocketInfo) => {console.log("on message");let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("on connect received:" + str);
});
tlsTwoWay.on('connect', () => {console.log("on connect");
});
tlsTwoWay.on('close', () => {console.log("on close");
});// 绑定本地IP地址和端口。
let ipAddress : socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 4512;
tlsTwoWay.bind(ipAddress, (err: BusinessError) => {if (err) {console.log('bind fail');return;}console.log('bind success');
});ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 1234;let tlsSecureOption : socket.TLSSecureOptions = {} as socket.TLSSecureOptions;
tlsSecureOption.key = "xxxx";
tlsSecureOption.cert = "xxxx";
tlsSecureOption.ca = ["xxxx"];
tlsSecureOption.password = "xxxx";
tlsSecureOption.protocols = [socket.Protocol.TLSv12];
tlsSecureOption.useRemoteCipherPrefer = true;
tlsSecureOption.signatureAlgorithms = "rsa_pss_rsae_sha256:ECDSA+SHA256";
tlsSecureOption.cipherSuite = "AES256-SHA256";let tlsTwoWayConnectOption : socket.TLSConnectOptions = {} as socket.TLSConnectOptions;
tlsSecureOption.key = "xxxx";
tlsTwoWayConnectOption.address = ipAddress;
tlsTwoWayConnectOption.secureOptions = tlsSecureOption;
tlsTwoWayConnectOption.ALPNProtocols = ["spdy/1", "http/1.1"];// 建立连接
tlsTwoWay.connect(tlsTwoWayConnectOption).then(() => {console.log("connect successfully");
}).catch((err: BusinessError) => {console.log("connect failed " + JSON.stringify(err));
});// 连接使用完毕后,主动关闭。取消相关事件的订阅。
tlsTwoWay.close((err: BusinessError) => {if (err) {console.log("close callback error = " + err);} else {console.log("close success");}tlsTwoWay.off('message');tlsTwoWay.off('connect');tlsTwoWay.off('close');
});// 创建一个(单向认证)TLS Socket连接,返回一个TLS Socket对象。
let tlsOneWay: socket.TLSSocket = socket.constructTLSSocketInstance(); // One way authentication// 订阅TLS Socket相关的订阅事件
tlsTwoWay.on('message', (value: SocketInfo) => {console.log("on message");let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("on connect received:" + str);
});
tlsTwoWay.on('connect', () => {console.log("on connect");
});
tlsTwoWay.on('close', () => {console.log("on close");
});// 绑定本地IP地址和端口。
ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 5445;
tlsOneWay.bind(ipAddress, (err:BusinessError) => {if (err) {console.log('bind fail');return;}console.log('bind success');
});ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 8789;
let tlsOneWaySecureOption : socket.TLSSecureOptions = {} as socket.TLSSecureOptions;
tlsOneWaySecureOption.ca = ["xxxx", "xxxx"];
tlsOneWaySecureOption.cipherSuite = "AES256-SHA256";let tlsOneWayConnectOptions: socket.TLSConnectOptions = {} as socket.TLSConnectOptions;
tlsOneWayConnectOptions.address = ipAddress;
tlsOneWayConnectOptions.secureOptions = tlsOneWaySecureOption;// 建立连接
tlsOneWay.connect(tlsOneWayConnectOptions).then(() => {console.log("connect successfully");
}).catch((err: BusinessError) => {console.log("connect failed " + JSON.stringify(err));
});// 连接使用完毕后,主动关闭。取消相关事件的订阅。
tlsTwoWay.close((err: BusinessError) => {if (err) {console.log("close callback error = " + err);} else {console.log("close success");}tlsTwoWay.off('message');tlsTwoWay.off('connect');tlsTwoWay.off('close');
});

4.1.7 TCP Socket 升级为 TLS Socket 进行加密数据传输

客户端 TCP Socket 升级为 TLS Socket 流程:

  1. import 需要的 socket 模块。
  2. 参考应用 TCP/UDP 协议进行通信,创建一个 TCPSocket 连接。
  3. 确保 TCPSocket 已连接后,使用该 TCPSocket 对象创建 TLSSocket 连接,返回一个 TLSSocket 对象。
  4. 双向认证上传客户端 CA 证书及数字证书;单向认证上传客户端 CA 证书。
  5. (可选)订阅 TLSSocket 相关的订阅事件。
  6. 发送数据。
  7. TLSSocket 连接使用完毕后,主动关闭。
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}// 创建一个TCPSocket连接,返回一个TCPSocket对象。
let tcp: socket.TCPSocket = socket.constructTCPSocketInstance();
tcp.on('message', (value: SocketInfo) => {console.log("on message");let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("on connect received:" + str);
});
tcp.on('connect', () => {console.log("on connect");
});// 绑定本地IP地址和端口。
let ipAddress: socket.NetAddress = {} as socket.NetAddress;
ipAddress.address = "192.168.xxx.xxx";
ipAddress.port = 1234;
tcp.bind(ipAddress, (err: BusinessError) => {if (err) {console.log('bind fail');return;}console.log('bind success');// 连接到指定的IP地址和端口。ipAddress.address = "192.168.xxx.xxx";ipAddress.port = 443;let tcpConnect: socket.TCPConnectOptions = {} as socket.TCPConnectOptions;tcpConnect.address = ipAddress;tcpConnect.timeout = 6000;tcp.connect(tcpConnect, (err: BusinessError) => {if (err) {console.log('connect fail');return;}console.log('connect success');// 确保TCPSocket已连接后,将其升级为TLSSocket连接。let tlsTwoWay: socket.TLSSocket = socket.constructTLSSocketInstance(tcp);// 订阅TLSSocket相关的订阅事件。tlsTwoWay.on('message', (value: SocketInfo) => {console.log("tls on message");let buffer = value.message;let dataView = new DataView(buffer);let str = "";for (let i = 0; i < dataView.byteLength; ++i) {str += String.fromCharCode(dataView.getUint8(i));}console.log("tls on connect received:" + str);});tlsTwoWay.on('connect', () => {console.log("tls on connect");});tlsTwoWay.on('close', () => {console.log("tls on close");});// 配置TLSSocket目的地址、证书等信息。ipAddress.address = "192.168.xxx.xxx";ipAddress.port = 1234;let tlsSecureOption: socket.TLSSecureOptions = {} as socket.TLSSecureOptions;tlsSecureOption.key = "xxxx";tlsSecureOption.cert = "xxxx";tlsSecureOption.ca = ["xxxx"];tlsSecureOption.password = "xxxx";tlsSecureOption.protocols = [socket.Protocol.TLSv12];tlsSecureOption.useRemoteCipherPrefer = true;tlsSecureOption.signatureAlgorithms = "rsa_pss_rsae_sha256:ECDSA+SHA256";tlsSecureOption.cipherSuite = "AES256-SHA256";let tlsTwoWayConnectOption: socket.TLSConnectOptions = {} as socket.TLSConnectOptions;tlsSecureOption.key = "xxxx";tlsTwoWayConnectOption.address = ipAddress;tlsTwoWayConnectOption.secureOptions = tlsSecureOption;tlsTwoWayConnectOption.ALPNProtocols = ["spdy/1", "http/1.1"];// 建立TLSSocket连接tlsTwoWay.connect(tlsTwoWayConnectOption, () => {console.log("tls connect success");// 连接使用完毕后,主动关闭。取消相关事件的订阅。tlsTwoWay.close((err: BusinessError) => {if (err) {console.log("tls close callback error = " + err);} else {console.log("tls close success");}tlsTwoWay.off('message');tlsTwoWay.off('connect');tlsTwoWay.off('close');});});});
});

4.1.8 TCP Socket 升级为 TLS Socket 进行加密数据传输

服务端 TLS Socket Server 流程:

  1. import 需要的 socket 模块。
  2. 启动服务,绑定 IP 和端口号,监听客户端连接,创建并初始化 TLS 会话,加载证书密钥并验证。
  3. 订阅 TLSSocketServer 的连接事件。
  4. 收到客户端连接,通过回调得到 TLSSocketConnection 对象。
  5. 订阅 TLSSocketConnection 相关的事件。
  6. 发送数据。
  7. TLSSocketConnection 连接使用完毕后,断开连接。
  8. 取消订阅 TLSSocketConnection 以及 TLSSocketServer 的相关事件。
import { socket } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';let tlsServer: socket.TLSSocketServer = socket.constructTLSSocketServerInstance();let netAddress: socket.NetAddress = {address: '192.168.xx.xxx',port: 8080
}let tlsSecureOptions: socket.TLSSecureOptions = {key: "xxxx",cert: "xxxx",ca: ["xxxx"],password: "xxxx",protocols: socket.Protocol.TLSv12,useRemoteCipherPrefer: true,signatureAlgorithms: "rsa_pss_rsae_sha256:ECDSA+SHA256",cipherSuite: "AES256-SHA256"
}let tlsConnectOptions: socket.TLSConnectOptions = {address: netAddress,secureOptions: tlsSecureOptions,ALPNProtocols: ["spdy/1", "http/1.1"]
}tlsServer.listen(tlsConnectOptions).then(() => {console.log("listen callback success");
}).catch((err: BusinessError) => {console.log("failed" + err);
});class SocketInfo {message: ArrayBuffer = new ArrayBuffer(1);remoteInfo: socket.SocketRemoteInfo = {} as socket.SocketRemoteInfo;
}
let callback = (value: SocketInfo) => {let messageView = '';for (let i: number = 0; i < value.message.byteLength; i++) {let uint8Array = new Uint8Array(value.message)let messages = uint8Array[i]let message = String.fromCharCode(messages);messageView += message;}console.log('on message message: ' + JSON.stringify(messageView));console.log('remoteInfo: ' + JSON.stringify(value.remoteInfo));
}
tlsServer.on('connect', (client: socket.TLSSocketConnection) => {client.on('message', callback);// 发送数据client.send('Hello, client!').then(() => {console.log('send success');}).catch((err: BusinessError) => {console.log('send fail');});// 断开连接client.close().then(() => {console.log('close success');}).catch((err: BusinessError) => {console.log('close fail');});// 可以指定传入on中的callback取消一个订阅,也可以不指定callback清空所有订阅。client.off('message', callback);client.off('message');
});// 取消订阅tlsServer的相关事件
tlsServer.off('connect');

5 MDNS管理

MDNS即多播DNS(Multicast DNS),提供局域网内的本地服务添加、移除、发现、解析等能力。

  • 本地服务:局域网内服务的提供方,比如打印机、扫描器等。

MDNS管理的典型场景有:

  1. 管理本地服务,通过对本地服务的创建,删除和解析等管理本地服务。
  2. 发现本地服务,通过DiscoveryService对象,对指定类型的本地服务状态变化进行监听。

说明
为了保证应用的运行效率,大部分API调用都是异步的,对于异步调用的API均提供了callback和Promise两种方式,以下示例均采用promise函数,更多方式可以查阅MDNS管理-API参考。

完整的JS API说明以及实例代码请参考:MDNS管理-API参考。

接口名描述
addLocalService(context: Context, serviceInfo: LocalServiceInfo, callback: AsyncCallback): void添加一个MDNS服务,使用callback方式作为异步方法。
removeLocalService(context: Context, serviceInfo: LocalServiceInfo, callback: AsyncCallback): void移除一个MDNS服务,使用callback方式作为异步方法。
createDiscoveryService(context: Context, serviceType: string): DiscoveryService返回一个DiscoveryService对象,该对象用于发现指定服务类型的MDNS服务。
resolveLocalService(context: Context, serviceInfo: LocalServiceInfo, callback: AsyncCallback): void解析一个MDNS服务,使用callback方式作为异步方法。
startSearchingMDNS(): void开始搜索局域网内的MDNS服务。
stopSearchingMDNS(): void停止搜索局域网内的MDNS服务。
on(type: ‘discoveryStart’, callback: Callback<{serviceInfo: LocalServiceInfo, errorCode?: MdnsError}>): void订阅开启监听MDNS服务的通知。
off(type: ‘discoveryStart’, callback?: Callback<{ serviceInfo: LocalServiceInfo, errorCode?: MdnsError }>): void取消开启监听MDNS服务的通知。
on(type: ‘discoveryStop’, callback: Callback<{serviceInfo: LocalServiceInfo, errorCode?: MdnsError}>): void订阅停止监听MDNS服务的通知。
off(type: ‘discoveryStop’, callback?: Callback<{ serviceInfo: LocalServiceInfo, errorCode?: MdnsError }>): void取消停止监听MDNS服务的通知。
on(type: ‘serviceFound’, callback: Callback): void订阅发现MDNS服务的通知。
off(type: ‘serviceFound’, callback?: Callback): void取消发现MDNS服务的通知。
on(type: ‘serviceLost’, callback: Callback): void 订阅移除MDNS服务的通知。
off(type: ‘serviceLost’, callback?: Callback): void 取消移除MDNS服务的通知。

5.1 管理本地服务

  1. 设备连接WiFi。
  2. 从@kit.NetworkKit里导入mdns的命名空间。
  3. 调用addLocalService方法,添加本地服务。
  4. 通过resolveLocalService方法,解析本地网络的IP地址(非必要,根据需求使用)。
  5. 通过removeLocalService方法,移除本地服务。
// 从@kit.NetworkKit中导入mdns命名空间
import { mdns } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { featureAbility } from '@kit.AbilityKit';let context = getContext(this) as Context;class ServiceAttribute {key: string = "111"value: Array<number> = [1]
}// 建立LocalService对象
let localServiceInfo: mdns.LocalServiceInfo = {serviceType: "_print._tcp",serviceName: "servicename",port: 5555,host: {address: "10.14.**.***"},serviceAttribute: [{key: "111", value: [1]}]
}// addLocalService添加本地服务
mdns.addLocalService(context, localServiceInfo).then((data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});// resolveLocalService解析本地服务对象(非必要,根据需求使用)
mdns.resolveLocalService(context, localServiceInfo).then((data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});// removeLocalService移除本地服务
mdns.removeLocalService(context, localServiceInfo).then((data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});

5.2 发现本地服务

  1. 设备连接WiFi。
  2. 从@kit.NetworkKit里导入mdns的命名空间。
  3. 创建DiscoveryService对象,用于发现指定服务类型的MDNS服务。
  4. 订阅MDNS服务发现相关状态变化。
  5. 启动搜索局域网内的MDNS服务。
  6. 停止搜索局域网内的MDNS服务。
  7. 取消订阅的MDNS服务。
// 从@kit.NetworkKit中导入mdns命名空间
import { common, featureAbility, UIAbility } from '@kit.AbilityKit';
import { mdns } from '@kit.NetworkKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';// 构造单例对象
export class GlobalContext {private constructor() {}private static instance: GlobalContext;private _objects = new Map<string, Object>();public static getContext(): GlobalContext {if (!GlobalContext.instance) {GlobalContext.instance = new GlobalContext();}return GlobalContext.instance;}getObject(value: string): Object | undefined {return this._objects.get(value);}setObject(key: string, objectClass: Object): void {this._objects.set(key, objectClass);}
}// Stage模型获取context
class EntryAbility extends UIAbility {value:number = 0;onWindowStageCreate(windowStage: window.WindowStage): void{GlobalContext.getContext().setObject("value", this.value);}
}let context = GlobalContext.getContext().getObject("value") as common.UIAbilityContext;// 创建DiscoveryService对象,用于发现指定服务类型的MDNS服务
let serviceType = "_print._tcp";
let discoveryService = mdns.createDiscoveryService(context, serviceType);// 订阅MDNS服务发现相关状态变化
discoveryService.on('discoveryStart', (data: mdns.DiscoveryEventInfo) => {console.log(JSON.stringify(data));
});
discoveryService.on('discoveryStop', (data: mdns.DiscoveryEventInfo) => {console.log(JSON.stringify(data));
});
discoveryService.on('serviceFound', (data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});
discoveryService.on('serviceLost', (data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});// 启动搜索局域网内的MDNS服务
discoveryService.startSearchingMDNS();// 停止搜索局域网内的MDNS服务
discoveryService.stopSearchingMDNS();// 取消订阅的MDNS服务
discoveryService.off('discoveryStart', (data: mdns.DiscoveryEventInfo) => {console.log(JSON.stringify(data));
});
discoveryService.off('discoveryStop', (data: mdns.DiscoveryEventInfo) => {console.log(JSON.stringify(data));
});
discoveryService.off('serviceFound', (data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});
discoveryService.off('serviceLost', (data: mdns.LocalServiceInfo) => {console.log(JSON.stringify(data));
});

参考文献:
[1]HarmonyOS NEXT Beta1开发文档

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/150417.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【初阶数据结构】排序——插入排序

目录 前言直接插入排序希尔排序 前言 排序&#xff1a;所谓排序就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。排序算法&#xff0c;就是如何使得记录按照要求排列的方法。   例如&#xff1a;买东西时会根据销量或价…

java并发编程笔记 之 线程和进程

文章目录 前言线程线程优先级和时间片创建多线程及运行线程的状态 进程查看进程的命令进程的通信方式 线程和进程的区别从关系上疑问集锦 前言 并发 1、并发是指在同一时间段内&#xff0c;计算机系统能够处理多个任务的能力。 2、在并发编程中&#xff0c;我们可以理解为多个…

代码随想录算法训练营第三十九天 | 198.打家劫舍 ,213.打家劫舍II,337.打家劫舍III

第三十九天打卡&#xff0c;今天解决打家劫舍系列问题&#xff0c;树形dp比较难。 198.打家劫舍 题目链接 解题过程 dp[i]&#xff1a;考虑下标i&#xff08;包括i&#xff09;以内的房屋&#xff0c;最多可以偷窃的金额为dp[i]。 要么不偷这一间&#xff0c;那就是前面那间…

毕业设计选题:基于ssm+vue+uniapp的校园失物招领小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

大瓜-CSP-J/S2024第一轮认证题目涉嫌泄露。竞赛公平能否维护?

2024年全国信息学奥赛&#xff08;CSP-J/S&#xff09;泄题事件在竞赛界掀起了巨大的波澜。这场赛事本应是全国最具公信力的编程竞赛之一&#xff0c;但部分题目在考试前已被某些培训机构押中&#xff0c;这一泄题行为不仅让考生与家长感到愤怒&#xff0c;也让公众对奥赛的公平…

scp 命令:在两台主机间远程传输文件

一、命令简介 ​scp​ 命令使用 SSH ​加密的方式在本地主机和远程主机之间复制文件。 ‍ 二、命令参数 格式 scp [选项] 发送方主机和目录 接收方主机和目录注意&#xff1a;左边是发送方&#xff0c;右边是接收方。固定格式。 示例 #示例1 scp ~/test.txt soulio172.1…

豆包MarsCode体验

这个AI助手贴合做题者的思路&#xff0c;可以实时对代码进行分析&#xff0c;提出纠错、优化、规范性意见&#xff0c;非常好用。

基于数据挖掘的航空客户满意度分析预测系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 航空公司致力于提供多样化的服务以满足乘客需求&#xff0c;包括但不限于提供免费无线网络、免费食物饮品、提供网上预约服务、飞机出口位置、座椅舒适度、卫生状况等&#xff0c;并希望以此提升乘…

构造者模式多种实现方式

构造者模式 ​ 构造者模式建议将对象构造代码从产品类中抽取出来&#xff0c; 并将其放在一个名为构造者的独立对象中 ​ 构建者模式也是用来创建对象&#xff0c;但是相对于工厂模式来说&#xff0c;建造者模式适用于构建复杂对象&#xff0c;而工厂模式适用于创建对象的封装…

asp.net core日志与异常处理小结

asp.net core的webApplicationBuilder中自带了一个日志组件,无需手动注册服务就能直接在控制器中构造注入&#xff0c;本文主要介绍了net core日志与异常处理小结&#xff0c;需要的朋友可以参考下 ILogger简单使用 asp.net core的webApplicationBuilder中自带了一个日志组件…

网络安全-长亭雷池waf的sql绕过,安全狗绕过(5种绕过3+2)

目录 一、环境 二、讲解 三、绕过前思路整理 3.1 思路 3.1.1 入门思路 0x00截断filename 3.1.2 双写上传描述行(差异绕过&#xff09;【成功】 3.1.3双写整个 part 开头部分 3.1.4 构造假的 part 部分 1【成功】 3.1.5 构造假的 part 部分2【成功】 3.1.6 两个 bounda…

闲盒支持的组网方式和注意事项

1. 直连光猫拨号​ 通过光猫拨号&#xff0c;设备直连光猫的设备&#xff0c;需要对光猫开启UPNP并关闭DMZ 如果只接一个盒子&#xff0c;建议直接针对盒子IP开dmz。 2. 直连路由器​ 通过路由器拨号&#xff0c;设备直连路由器的设备&#xff0c;需要对路由器开启UPNP并关闭…

Sql Developer日期显示格式设置

默认时间格式显示 设置时间格式&#xff1a;工具->首选项->数据库->NLS->日期格式: DD-MON-RR 修改为: YYYY-MM-DD HH24:MI:SS 设置完格式显示&#xff1a;

【Java数据结构】 ---对象的比较

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 前言 上图中&#xff0c;线性表、堆…

【嵌入式linux开发】SPI设备文件操作BMI088传感器

【嵌入式linux开发】SPI设备文件操作BMI088传感器 前言一、数据手册浅读二、代码 前言 在本篇博客中&#xff0c;将从BMI088传感器的数据手册出发&#xff0c;简单了解之后&#xff0c;展示如何通过SPI设备文件与传感器进行通信。除了使用linux文件设备操作spi接口&#xff0c…

微软 Win11 24H2 RP 26100.1876 预览版发布!附详细更新日志

系统之家于9月24日发出最新报道&#xff0c;微软为Release Preview频道的Windows Insider项目成员&#xff0c;发布了适用Windows11 24H2版本更新的 KB5043178&#xff0c;更新后&#xff0c;系统版本号将升至26100.1876。此更新为用户带来了不同的新功能&#xff0c;例如打开开…

力扣每日一题 字符串中最多数目的子序列 贪心 字符串 前缀和

Problem: 2207. 字符串中最多数目的子序列 &#x1f468;‍&#x1f3eb; 参考题解 class Solution {public long maximumSubsequenceCount(String s, String pattern){long res 0;long cnt1 0, cnt2 0;for (int i 0; i < s.length(); i){if (s.charAt(i) pattern.cha…

【有啥问啥】Chain of Goal-Oriented Reasoning(CoGOR)原理详解

Chain of Goal-Oriented Reasoning&#xff08;CoGOR&#xff09;原理详解 引言 在人工智能领域&#xff0c;实现真正意义上的智能一直是研究的重点。传统的 AI 方法在处理复杂、开放式的问题时往往显得力不从心。为了解决这一问题&#xff0c;Chain of Goal-Oriented Reason…

从汽车高速线束角度浅谈中控屏黑屏、闪屏及信号阈值低故障-之AEM线束测试仪应用案例

故障成因和解决方案 随着车载信息娱乐技术的迅速发展&#xff0c;中控屏已经成为现代汽车的标配。然而&#xff0c;许多主机厂和消费者在车辆使用过程中常常遇到中控屏出现黑屏、闪屏以及信号阈值低等问题&#xff0c;给使用带来了诸多困扰。本文将从汽车高速线束的角度&#…

LeetCode 面试经典150题 137.只出现一次的数字II

题目&#xff1a; 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你必须设计并实现线性时间复杂度的算法且使用常数级空间来解决此问题。 思路&#xff1a; 方法一&#xf…