鸿蒙OS开发实战:【Socket小试MQTT连接】

本篇分享一下 HarmonyOS 中的Socket使用方法

将从2个方面实践:

  1. HarmonyOS 手机应用连接PC端 SocketServer
  2. HarmonyOS 手机应用连接MQTT 服务端

通过循序渐进的方式,全面了解实践HarmonyOS中的Socket用法

学习本章前先熟悉文档开发知识更新库gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md前往。

V2.0白皮书.png

注意:编译时IDE会给出如下警告提示,@ohos.net.socket 这个包没有验证过。

或者+mau123789熟悉文档是v喔!

Currently module for ‘@ohos.net.socket’ is not verified. If you’re importing napi, its verification will be enabled in later SDK version. Please make sure the corresponding .d.ts file is provided and the napis are correctly declared.

官方文档说明

引入Socket包

import socket from '@ohos.net.socket';

创建TCPSocket,连接远端服务器

//注意,这里声明变量tcp, 建议将类型加上,否则不利于IDE联想API
let tcp: socket.TCPSocket = socket.constructTCPSocketInstance();let bindAddress = {address: '192.168.xx.xx',port: x
};let connectAddress = {address: '192.168.xx.xx',port: x
};//1. 绑定本机地址和端口
tcp.bind(bindAddress, err => {if(err){console.log('1-' + JSON.stringify(err))return}//2. 连接远端服务器tcp.connect({address: connectAddress,timeout: 30000}).then( r => {console.log('2-' +JSON.stringify(r))}).catch((e) => {console.log('3-' + JSON.stringify(e))})})
复制

状态监听

订阅“连接”

tcp.on('connect', () => {});
复制

解除“连接”订阅

tcp.off('connect', () => {});
复制

订阅“消息”

tcp.on('message', () => {//即:可以在这里随时接收到服务端发送过来的消息
});
复制

解除“消息”订阅

tcp.off('message', () => {});
复制

发送消息

let msg: string = '我是HarmonyOS客户端'tcp.send({data: msg}, (error)=>{if(error) {console.log('消息没有发送成功: ' + JSON.stringify(error))} else {console.log('消息发送成功')}
})
复制

实践:实现Socket与ServerSocket通信

创建ServerSocket 服务

这里使用IntelliJ IDEA创建一个Java工程,然后运行在自己电脑上即可使用

如果你实在没法自己实现ServerSocket,可以在网上找一个调试Socket的工具

监听客户端接入

package com.harvey.socketserver;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class Main {public static void main(String[] args) {try {ServerSocket serverSocket = new ServerSocket(6666);//一. 建立客户端监听new Thread(new Runnable() {@Overridepublic void run() {while (true){try {Socket clientSocket = serverSocket.accept();System.out.println("客户端:" + clientSocket.getInetAddress().getLocalHost()+"已连接到服务器");new Server(clientSocket).start();} catch (IOException e) {e.printStackTrace();}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}).start();} catch (IOException e) {e.printStackTrace();}}}
复制

监听客户端消息且自动回复

package com.harvey.socketserver;import java.io.*;
import java.net.Socket;public class Server extends Thread{Socket clientSocket;InputStream is;OutputStream os;String lastReceiverMessage;int LIVE_TIME = 60*1000;public Server(Socket clientSocket){this.clientSocket = clientSocket;try {is = this.clientSocket.getInputStream();os = this.clientSocket.getOutputStream();} catch (IOException e) {e.printStackTrace();}}@Overridepublic void run() {while(LIVE_TIME != 0){if ( this.clientSocket != null ) {if ( this.clientSocket.isClosed() || this.clientSocket.isInputShutdown() || this.clientSocket.isOutputShutdown()) {LIVE_TIME = 0;} else {readMessage();responseMessage();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}LIVE_TIME -= 1000;}} else {LIVE_TIME = 0;}}closeAllChannel();}//释放资源private void closeAllChannel(){try {if(clientSocket != null){clientSocket.close();clientSocket = null;}if(is != null){is.close();is = null;}if(os != null){os.close();os = null;}} catch (IOException e) {e.printStackTrace();}}//读取客户端消息, 注意:这里是为了省事,用的readLine接口//如果消息中有换行符,则会丢失消息private void readMessage(){BufferedReader br = new BufferedReader(new InputStreamReader(is));try {lastReceiverMessage  = br.readLine();System.out.println("已接收到端消息:【" + lastReceiverMessage +"】");} catch (IOException e) {System.err.println("接收消息失败:" + e.getMessage());}}//自动回复客户端消息private void responseMessage(){try {BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));bw.write(System.currentTimeMillis() + "亲爱的客户端,已收到您的来信。"  +  lastReceiverMessage + "\n");bw.flush();System.out.println("回复消息成功");} catch (IOException e) {System.err.println("回复消息失败:" + e.getMessage());}}}
复制

创建客户端应用

布局

为了验证Socket,交互页面包含如下功能:

  1. 连接/断开
  2. 渲染接收到的消息
  3. 渲染已发送的消息
  4. 文字输入
  5. 消息发送

HarmonyOS 核心代码

import socket from '@ohos.net.socket'
import util from '@ohos.util';
import Prompt from '@system.prompt';let tcpSocket: socket.TCPSocket = null@Entry
@Component
struct SocketPage {@State isConnected: boolean = false@State sendMessage: string = ''@State inputMessage: string = ''@State receiveMessage: string = ''@State connectButtonBGColor: Color = Color.Gray@State connectButtonTextColor: Color = Color.White//页面创建时,注册自定义消息,监听来消息和连接状态aboutToAppear(){getContext().eventHub.on('remotemsg', (value)=>{this.receiveMessage = value})getContext().eventHub.on('connectStatus', (value)=>{if(value === 'connect'){this.connectButtonBGColor = Color.Green} else if(value === 'close'){this.connectButtonBGColor = Color.Gray}})}//页面销毁时,解除所有订阅,关闭socketaboutToDisappear() {tcpSocket.off("message")tcpSocket.off("connect")tcpSocket.off("close")tcpSocket.close()tcpSocket = null}build(){Column( {space: 20} ){Row( {space: 20} ){Button('连接').width(60).height(60).fontSize(12).backgroundColor(this.connectButtonBGColor).fontColor(this.connectButtonTextColor).onClick( ()=> {if(this.isConnected){tcpSocket.close()} else {connectServer()}})}Column({space: 30}){Text('发送:' + this.sendMessage).fontSize(20).width('100%')Text('接收:' + this.receiveMessage).fontSize(20).width('100%')}.backgroundColor(Color.Pink).padding( { top: 20, bottom: 20} )Row({space: 10}) {TextArea({placeholder: '输入文字', text: this.inputMessage}).onChange( (value) => {this.inputMessage = value}).fontSize(20).width('75%')Button('发送').fontColor(29).onClick( () => {sendMessage(this.inputMessage)this.sendMessage = this.inputMessagethis.inputMessage = ''}).width('20%')}.width('100%').justifyContent(FlexAlign.Center)}.width('100%').height('100%').alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.Start).padding({top: px2vp(120)})}
}//发送消息
function sendMessage(msg: string){if(tcpSocket){tcpSocket.send({data: msg + '\n'}, (error)=>{if(error) {console.log('消息没有发送成功: ' + JSON.stringify(error))} else {getContext().eventHub.emit('')console.log('消息发送成功')}})} else {Prompt.showToast({message: '还没有连接服务器'})}
}//开始连接服务器
function connectServer(){let bindAddress = {address: '192.168.71.66',port: 1983,family: 1};let connectAddress = {address: '192.168.71.23',port: 6666 //端口号要和ServerSocket 一致};tcpSocket = socket.constructTCPSocketInstance()tcpSocket.on('close', () => {console.log("on close")getContext().eventHub.emit('connectStatus', 'close')});tcpSocket.on('connect', () => {console.log("on connect")getContext().eventHub.emit('connectStatus', 'connect')});tcpSocket.on('message' , ( value: {message: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo} ) => {let view = new Uint8Array(value.message);let textDecoder = util.TextDecoder.create()let str = textDecoder.decodeWithStream(view);getContext().eventHub.emit('remotemsg', str)})tcpSocket.bind(bindAddress, err => {if(err){console.log('1-' + JSON.stringify(err))return}tcpSocket.connect({address: connectAddress,timeout: 30000}).then( r => {console.log('2-' +JSON.stringify(r))}).catch((e) => {console.log('3-' + JSON.stringify(e))})})}
复制

最终效果

实践:实现MQTT连接

准备MQTT Broker

MQTT使用的是 mosquitto,官网下载地址

关于MQTT的入门使用,可参见“MQTT试用”

注意:mosquitto 安装完成后,需要打开匿名设置,并且监听自己电脑的IP和1883端口 mosquitto的配置文件名:mosquitto.conf

  • allow_anonymous true
  • listener 1883 192.168.xxx.xxx

MQTT 5.0 网址

https://docs.oasis-open.org/mqtt/mqtt/v5.0/mqtt-v5.0.pdf

  1. 熟悉 “3.1 CONNECT – Connection Request”
  2. 熟悉 “3.2 CONNACK – Connect acknowledgement”

场景模拟

客户端使用Socket 连接mosquitto, 并且响应连接ACK

代码实现

请求连接

这部分需要分为两步:1. Socket连接 2. MQTT连接请求(即发送连接请求命令)

a. Socket连接
function connectMQTTServer(){let bindAddress = {address: '192.168.71.66',port: 1983,family: 1};let connectAddress = {address: '192.168.71.23', //MQTT服务器IPport: 1883 //MQTT服务器端口};tcpSocket = socket.constructTCPSocketInstance()listenerStatus()tcpSocket.bind(bindAddress, err => {if(err){console.log('1-' + JSON.stringify(err))return}tcpSocket.connect({address: connectAddress,timeout: 30000}).then( r => {console.log('2-' +JSON.stringify(r))}).catch((e) => {console.log('3-' + JSON.stringify(e))})})
}
复制
b. MQTT连接请求
tcpSocket.on('connect', () => {console.log("on connect")//拼接MQTT请求数据let connectCMD = MQTTConnect.getFixHeader()//发送MQTT连接请求命令tcpSocket.send({data: connectCMD.buffer}, (error)=>{if(error) {console.log('消息没有发送成功: ' + JSON.stringify(error))} else {console.log('消息发送成功')}})getContext().eventHub.emit('connectStatus', 'connect')
});
复制

构建MQTT请求连接数据

代码有点枯燥,都是要对着MQTT 5.0规范文档来实现的

import util from '@ohos.util'export default class MQTTConnect{//3.1.1 CONNECT Fixed Header 【2字节】//3.1.2 CONNECT Variable Header//3.1.2.1 Protocol Name 【6字节】//3.1.2.2 Protocol Version 【1字节】//3.1.2.3 Connect Flags 【1字节】//3.1.2.10 Keep Alive 【2字节】//3.1.2.11 CONNECT Properties//3.1.2.11.1 Property Length 【1字节】//3.1.2.11.2 Session Expiry Interval 【4字节】//3.1.2.11.3 Receive Maximum 【2字节】//3.1.2.11.4 Maximum Packet Size 【4字节】//3.1.2.11.5 Topic Alias Maximum 【2字节】//3.1.2.11.6 Request Response Information 【1字节】//3.1.2.11.7 Request Problem Information 【1字节】//3.1.2.11.8 User Property【UTF-8 String Pair】//3.1.2.11.9 Authentication Method 【UTF-8 String】//3.1.2.11.10 Authentication Data【Binary Data】//3.1.3 CONNECT Payload//3.1.3.1 Client Identifier (ClientID) 【UTF-8 String】//3.1.3.2 Will Properties//3.1.3.2.1 Property Length 【Variable Byte Integer】//3.1.3.2.2 Will Delay Interval 【4字节】//3.1.3.2.3 Payload Format Indicator 【1字节】//3.1.3.2.4 Message Expiry Interval 【4字节】//3.1.3.2.5 Content Type【UTF-8 String】//3.1.3.2.6 Response Topic【UTF-8 String】//3.1.3.2.7 Correlation Data 【Binary Data】//3.1.3.2.8 User Property【UTF-8 String Pair】//3.1.3.3 Will Topic 【UTF-8 String】//3.1.3.4 Will Payload【 Binary Data】//3.1.3.5 User Name【UTF-8 String】//3.1.3.6 Password【 Binary Data】//3.1.4 CONNECT Actionspublic static getFixHeader(): Uint8Array{let remainLength: number = 0//3.1.1 CONNECT Fixed Header - 包类型let abPacketType = new ArrayBuffer(1)const dv_abPacketType = new DataView(abPacketType);dv_abPacketType.setInt8(0, 0x10)//3.1.2.1 Protocol Namelet u8a_protolName = this.utf8String('MQTT')remainLength += u8a_protolName.length//3.1.2.2 Protocol Versionlet version = new Uint8Array([5])remainLength++//3.1.2.3 Connect Flagsconst  UserNameFlag: number = 0x80const  PasswordFlag: number = 0x40const  WillRetain: number = 0x20const  WillQoS0: number = 0x00const  WillQoS1: number = 0x8const  WillQoS2: number = 0x10const  WillQoS3: number = 0x18const  WillFlag: number = 0x4const  CleanStart: number = 0x2let connectFlags: number = 0//可以根据实际对外暴露的接口,在这里进行与运算connectFlags = CleanStartlet u8a_connectFlags = new Uint8Array([connectFlags])remainLength++//3.1.2.10 Keep Aliveconst keepAlive = 60let u8a_keepalive = new Uint8Array([(keepAlive & 0xff00) >> 8 , keepAlive & 0xff])remainLength += 2//3.1.2.11 CONNECT Properties//3.1.2.11.1 Property Lengthlet u8a_propertylength = new Uint8Array([0])remainLength++//3.1.3 CONNECT Payload//3.1.3.1 Client Identifier (ClientID)let u8a_clientidentifier = this.utf8String('Harvey鸿蒙')remainLength += u8a_clientidentifier.length//3.1.1 CONNECT Fixed Header - 包剩余长度let abRemainLength = new ArrayBuffer(1)const dv_remainLength = new DataView(abRemainLength);dv_remainLength.setInt8(0, remainLength)let allIndex: number = 0let allUint8Array = new Uint8Array(2 + remainLength)allUint8Array[allIndex++] = dv_abPacketType.getUint8(0) //包类型allUint8Array[allIndex++] = dv_remainLength.getUint8(0) //包剩余长度u8a_protolName.forEach((value)=>{                       //协议名称allUint8Array[allIndex++] = value})version.forEach((value)=>{                              //协议版本号allUint8Array[allIndex++] = value})u8a_connectFlags.forEach((value)=>{                     //连接标志allUint8Array[allIndex++] = value})u8a_keepalive.forEach((value)=>{                        //长连保活时间allUint8Array[allIndex++] = value})u8a_propertylength.forEach((value)=>{                   //连接属性长度allUint8Array[allIndex++] = value})u8a_clientidentifier.forEach((value)=>{                 //客户端名称allUint8Array[allIndex++] = value})//数值打印let str = [...new Uint8Array(abPacketType),...new Uint8Array(abRemainLength), ...u8a_protolName, ...version, ...u8a_connectFlags, ...u8a_keepalive, ...u8a_propertylength, ...u8a_clientidentifier].map(x => x.toString(16).padStart(2, '0')).join(' ')console.log(str)let allStr: string = ''allUint8Array.forEach((value) => {allStr = allStr.concat(value.toString(16).padStart(2, '0')).concat(' ')})console.log(allStr)return allUint8Array}private static utf8String(content: string): Uint8Array{const encoder = new util.TextEncoder()let u8a_encoder = encoder.encodeInto(content)let encoderLength = u8a_encoder.lengthlet abEncoder = new ArrayBuffer(encoderLength + 2)const dv_encoder = new DataView(abEncoder)dv_encoder.setInt8(0, (encoderLength & 0xff00) >> 8)dv_encoder.setInt8(1, encoderLength & 0x00ff)let index: number = 2u8a_encoder.forEach( (value) => {dv_encoder.setInt8(index++, value)})return new Uint8Array(abEncoder)}}
复制

解析MQTT请求连接ACK数据

DevEco IDE 日志控制台

tcpSocket.on('message' , ( value: {message: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo} ) => {let str = [...new Uint8Array(value.message)].map(x => x.toString(16).padStart(2, '0')).join(' ')console.log(str)let index: number = 0let uint8Array = new Uint8Array(value.message)let cfh = uint8Array[index]index++//3.2.1 CONNACK Fixed Header//解析MQTT ACK数据,转换为日志输出if(cfh == 32){console.log('Fixed Header format:CONNACK('+cfh+')')MQTTConnectACK.parse(index, uint8Array)}getContext().eventHub.emit('remotemsg', str)
})
复制

import MQTTTool from './MQTTTool'export default class MQTTConnectACK{public static parse(index: number, uint8Array: Uint8Array) {let remainLength = uint8Array[index]console.log('Remaining Length:' + remainLength.toString(16))index++if(remainLength == 0){return}let remainIndex: number = 0//3.2.2 CONNACK Variable Header//3.2.2.1 Connect Acknowledge Flagslet caf = uint8Array[index]console.log('Connect Acknowledge Flags:' + caf.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}//3.2.2.2 Connect Reason Codelet crc = uint8Array[index]let des: string = ''if(crc == 0){des = 'Success'} else if(crc == 128){des = 'Unspecified error'} else if(crc == 129){des = 'Malformed Packet'} else if(crc == 130){des = 'Protocol Error'} else if(crc == 131){des = 'Implementation specific error'} else if(crc == 132){des = 'Unsupported Protocol Version'} else if(crc == 133){des = 'Client Identifier not valid'} else if(crc == 134){des = 'Bad User Name or Password'} else if(crc == 135){des = 'Not authorized'} else if(crc == 136){des = 'Server unavailable'} else if(crc == 137){des = 'Server busy'} else if(crc == 138){des = 'Banned'} else if(crc == 140){des = 'Bad authentication method'} else if(crc == 144){des = 'Topic Name invalid'} else if(crc == 149){des = 'Packet too large'} else if(crc == 151){des = 'Quota exceeded'} else if(crc == 153){des = 'Payload format invalid'} else if(crc == 154){des = 'Retain not supported'} else if(crc == 155){des = 'QoS not supported'} else if(crc == 156){des = 'Use another server'} else if(crc == 157){des = 'Server moved'} else if(crc == 158){des = 'Connection rate exceeded'}console.log('Connect Reason Code:' + des)index++remainIndex++if(remainIndex >= remainLength){return}//3.2.2.3 CONNACK Properties//3.2.2.3.1 Property Lengthlet propertyLength = uint8Array[index]console.log('Property Length:' + propertyLength.toString(16))index++remainIndex++if(propertyLength != 0){while (true){//判断类型let nextType = uint8Array[index]index++remainIndex++if(remainIndex >= remainLength){return}if(nextType == 17){ //值为4个字节//3.2.2.3.2 Session Expiry Intervallet costByteNumber = MQTTTool.parseFourByte(uint8Array, index, "Session Expiry Interval:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 33){//值为2个字节//3.2.2.3.3 Receive Maximumlet costByteNumber = MQTTTool.parseTwoByte(uint8Array, index, "Receive Maximum:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 36){ //值为1个字节//3.2.2.3.4 Maximum QoSlet mq = uint8Array[index]console.log('Maximum QoS:' + mq.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}} else if(nextType == 37) {  //值为1个字节//3.2.2.3.5 Retain Availablelet ra = uint8Array[index]console.log('Retain Available:' + ra.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}} else if(nextType == 39) { //值为4个字节//3.2.2.3.6 Maximum Packet Sizelet costByteNumber = MQTTTool.parseFourByte(uint8Array, index, "Maximum Packet Size:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 18) { //UTF-8 String = 2个字节 + 2个字节值的字节//3.2.2.3.7 Assigned Client Identifierlet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "Assigned Client Identifier:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 34) { // 值为2个字节//3.2.2.3.8 Topic Alias Maximumlet costByteNumber = MQTTTool.parseTwoByte(uint8Array, index, "Topic Alias Maximum:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 31) { //UTF-8 String = 2个字节 + 2个字节值的字节//3.2.2.3.9 Reason Stringlet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "Reason String:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 38) {//UTF-8 String Pair = (2个字节 + 2个字节值的字节)+(2个字节 + 2个字节值的字节)//3.2.2.3.10 User Propertylet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "User Property:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 40) { //值为1个字节//3.2.2.3.11 Wildcard Subscription Availablelet wsa = uint8Array[index]console.log('Wildcard Subscription Available:' + wsa.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}} else if(nextType == 41) { //值为1个字节//3.2.2.3.12 Subscription Identifiers Availablelet sia = uint8Array[index]console.log('Subscription Identifiers Available:' + sia.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}} else if(nextType == 42) { //值为1个字节//3.2.2.3.13 Shared Subscription Availablelet ssa = uint8Array[index]console.log('Shared Subscription Available:' + ssa.toString(16))index++remainIndex++if(remainIndex >= remainLength){return}} else if(nextType == 19) { //值为2个字节//3.2.2.3.14 Server Keep Alivelet costByteNumber = MQTTTool.parseTwoByte(uint8Array, index, "Server Keep Alive:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 26) { //UTF-8 String = 2个字节 + 2个字节值的字节//3.2.2.3.15 Response Informationlet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "Response Information:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 28) { //UTF-8 String = 2个字节 + 2个字节值的字节//3.2.2.3.16 Server Referencelet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "Server Reference:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 21) { //UTF-8 String = 2个字节 + 2个字节值的字节//3.2.2.3.17 Authentication Methodlet costByteNumber = MQTTTool.parseUTF8String(uint8Array, index, "Authentication Method:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}} else if(nextType == 22) { //Binary Data = 2个字节 + 2个字节值的字节//3.2.2.3.18 Authentication Datalet costByteNumber = MQTTTool.parseBinaryData(uint8Array, index, "Authentication Data:")index += costByteNumberremainIndex += costByteNumberif(remainIndex >= remainLength){return}}}}}}
复制

最终效果

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

马蹄集第九周

MT3011 代码 #include<bits/stdc.h> using namespace std; const int N 1e3 7;int n; struct NODE{vector<int> v;int ind 0; }g[N];int main( ) {cin >> n;int x;for(int i 1; i < n; i){for(int j 1; j< n-1; j){cin >> x;g[i].v.push_…

笔迹/签名数据集汇总

这里只收集公开/易申请的数据集 数据集发表年份语言最小单元Writers/人规模颜色最小单元文件格式示例图片备注CSAFE Handwriting Database2019英语页9090 人*(3 次*9 个样本) 2430 页300 dpi 扫描png-HWDB2.0-2.22011汉字页1,019每人 5 页,共 5091 页灰度图dgrl-CEDAR2006英语…

【python】flask各种版本的项目,终端命令运行方式的实现

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

HEVC的Profile和Level介绍

文章目录 HEVCProfile&#xff08;配置&#xff09;&#xff1a;Level&#xff08;级别&#xff09;&#xff1a;划分标准 HEVC HEVC&#xff08;High Efficiency Video Coding&#xff09;&#xff0c;也称为H.265&#xff0c;是一种视频压缩标准&#xff0c;旨在提供比先前的…

vivado 器件编程

生成器件镜像后 &#xff0c; 下一步是将其下载到目标器件。 Vivado IDE 具有内置原生的系统内器件编程功能用于执行此操作。 Vivado Design Suite 和 Vivado Lab Edition 都包含相应的功能 &#xff0c; 支持您连接到包含 1 个或多个 FPGA 或 ACAP 的硬 件&#xff0c; 以…

数据可视化为什么能在智慧港口中发挥作用?

随着全球贸易活动日益频繁&#xff0c;港口作为国际贸易的重要节点&#xff0c;其运营效率与智能化程度直接影响着整个物流链的效能。在此背景下&#xff0c;智慧港口的概念应运而生&#xff0c;它借助先进的信息技术手段对传统港口进行改造升级&#xff0c;其中&#xff0c;数…

Antd Vue3 使用 Anchor 锚点组件记录

项目场景 客户要求做一个表单页面&#xff0c;表单数据分为三步&#xff0c;每一步骤是一个单独的 Vue 组件&#xff0c;表单上方需要使用锚点组件实现锚点定位到每一步的功能。 代码总览 <template><div class"guided-form-content-wrapper"><!-- …

蓝桥杯基础练习汇总详细解析(一)——数列排序、十六进制转八进制、十六进制转十进制、十进制转十六进制、特殊回文数(代码实现、解题思路、Python)

试题 基础练习 数列排序 资源限制 内存限制&#xff1a;512.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个长度为n的数列&#xff0c;将这个数列按从小到大的顺序排列。1<n<200 输入格式 第…

【使用 PyQt6-第01章】 创建基本的应用程序

使用 PyQt6 创建您的第一个应用程序 目录 一、说明二、创建应用程序三、单步执行代码四、什么是事件循环&#xff1f;4.1 Qt 中的事件循环。 五、主窗口 QMainWindow六、调整窗口和小部件的大小 一、说明 本教程也适用于 PySide6 、 PySide2 和 PyQt5 在本教程中&#xff0c;…

新品发布|灵雀云重磅推出大模型 LLMOps 平台

自即日起&#xff0c;灵雀云正式推出大模型 LLMOps 平台 Alauda Machine Learning &#xff08;简称 AML&#xff09;&#xff0c;AML在整合传统 MLOps 解决方案的基础之上&#xff0c;为大模型/大语言模型场景提供更强大、更易用的功能。灵雀云意在将AML打造成全面覆盖传统 ML…

夏季水域安全管理,AI智能识别算法防溺水视频监控方案

随着夏季的到来&#xff0c;不少人为了一时的痛快凉爽就私自下水游泳&#xff0c;特别是在野外池塘&#xff0c;由于长期无人监管&#xff0c;极易发生人员溺亡事件&#xff0c;如何对池塘水域进行全天候无人值守智能监管&#xff0c;并实现发生人员闯入就立即告警&#xff1f;…

iOS UIFont-实现三方字体的下载和使用

UIFont 系列传送门 第一弹加载本地字体:iOS UIFont-新增第三方字体 第二弹加载线上字体:iOS UIFont-实现三方字体的下载和使用 前言 在上一章我们完成啦如何加载使用本地的字体。如果我们有很多的字体可供用户选择,我们当然可以全部使用本地字体加载方式,可是这样就增加了…