当前位置: 首页 > news >正文

鸿蒙开发(API 12 Beta6版)【通用属性协议】 网络篇

简介

通用属性协议是GATT(Generic Attribute)的缩写,它是一种用于在蓝牙低功耗设备之间传输数据的协议,定义了一套通用的属性和服务框架。通过GATT协议,蓝牙设备可以向其他设备提供服务,也可以从其他设备获取服务。

场景介绍

主要场景有:

  • 连接server端读取和写入信息。
  • server端操作services和通知客户端信息。

接口说明

具体接口说明如下表。

接口名功能描述
connect()client端发起连接远端蓝牙低功耗设备。
disconnect()client端断开与远端蓝牙低功耗设备的连接。
close()关闭客户端功能,注销client在协议栈的注册,调用该接口后GattClientDevice实例将不能再使用。
getDeviceName()client获取远端蓝牙低功耗设备名。
getServices()client端获取蓝牙低功耗设备的所有服务,即服务发现 。
readCharacteristicValue()client端读取蓝牙低功耗设备特定服务的特征值。
readDescriptorValue()client端读取蓝牙低功耗设备特定的特征包含的描述符。
writeCharacteristicValue()client端向低功耗蓝牙设备写入特定的特征值。
writeDescriptorValue()client端向低功耗蓝牙设备特定的描述符写入二进制数据。
getRssiValue()client获取远端蓝牙低功耗设备的信号强度 (Received Signal Strength Indication, RSSI),调用connect接口连接成功后才能使用。
setBLEMtuSize()client协商远端蓝牙低功耗设备的最大传输单元(Maximum Transmission Unit, MTU),调用connect接口连接成功后才能使用。
setCharacteristicChangeNotification()向服务端发送设置通知此特征值请求。
setCharacteristicChangeIndication()向服务端发送设置通知此特征值请求。
on(type: ‘BLECharacteristicChange’)订阅蓝牙低功耗设备的特征值变化事件。需要先调用setNotifyCharacteristicChanged接口才能接收server端的通知。
off(type: ‘BLECharacteristicChange’)取消订阅蓝牙低功耗设备的特征值变化事件。
on(type: ‘BLEConnectionStateChange’)client端订阅蓝牙低功耗设备的连接状态变化事件。
off(type: ‘BLEConnectionStateChange’)取消订阅蓝牙低功耗设备的连接状态变化事件。
on(type: ‘BLEMtuChange’)client端订阅MTU状态变化事件。
off(type: ‘BLEMtuChange’)client端取消订阅MTU状态变化事件。
addService()server端添加服务。
removeService()删除已添加的服务。
close()关闭服务端功能,去注销server在协议栈的注册,调用该接口后GattServer实例将不能再使用。
notifyCharacteristicChanged()server端特征值发生变化时,主动通知已连接的client设备。
sendResponse()server端回复client端的读写请求。
on(type: ‘characteristicRead’)server端订阅特征值读请求事件。
off(type: ‘characteristicRead’)server端取消订阅特征值读请求事件。
on(type: ‘characteristicWrite’)server端订阅特征值写请求事件。
off(type: ‘characteristicWrite’)server端取消订阅特征值写请求事件。
on(type: ‘descriptorRead’)server端订阅描述符读请求事件。
off(type: ‘descriptorRead’)server端取消订阅描述符读请求事件。
on(type: ‘descriptorWrite’)server端订阅描述符写请求事件。
off(type: ‘descriptorWrite’)server端取消订阅描述符写请求事件。
on(type: ‘connectionStateChange’)server端订阅BLE连接状态变化事件。
off(type: ‘connectionStateChange’)server端取消订阅BLE连接状态变化事件。
on(type: ‘BLEMtuChange’)server端订阅MTU状态变化事件。
off(type: ‘BLEMtuChange’)server端取消订阅MTU状态变化事件。

主要场景开发步骤

连接server端读取和写入信息

  1. import需要的ble模块。

  2. 创建gattClient实例对象。

  3. 连接gattServer。

  4. 读取gattServer的特征值和描述符。

  5. 向gattServer写入特征值和描述符。

  6. 断开连接,销毁gattClient实例。

  7. 示例代码:

import { ble } from '@kit.ConnectivityKit';
import { constant } from '@kit.ConnectivityKit';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';const TAG: string = 'GattClientManager';export class GattClientManager {device: string = undefined;gattClient: ble.GattClientDevice = undefined;connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';myFirstDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902一般用于notification或者indicationmySecondDescriptorUuid: string = '00002903-0000-1000-8000-00805F9B34FB';found: boolean = false;// 构造BLEDescriptorprivate initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {let descriptor: ble.BLEDescriptor = {serviceUuid: this.myServiceUuid,characteristicUuid: this.myCharacteristicUuid,descriptorUuid: des,descriptorValue: value};return descriptor;}// 构造BLECharacteristicprivate initCharacteristic(): ble.BLECharacteristic {let descriptors: Array<ble.BLEDescriptor> = [];let descBuffer = new ArrayBuffer(2);let descValue = new Uint8Array(descBuffer);descValue[0] = 11;descValue[1] = 12;descriptors[0] = this.initDescriptor(this.myFirstDescriptorUuid, new ArrayBuffer(2));descriptors[1] = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);let charBuffer = new ArrayBuffer(2);let charValue = new Uint8Array(charBuffer);charValue[0] = 1;charValue[1] = 2;let characteristic: ble.BLECharacteristic = {serviceUuid: this.myServiceUuid,characteristicUuid: this.myCharacteristicUuid,characteristicValue: charBuffer,descriptors: descriptors};return characteristic;}private logCharacteristic(char: ble.BLECharacteristic) {let message = 'logCharacteristic uuid:' + char.characteristicUuid + '\n';let value = new Uint8Array(char.characteristicValue);message += 'logCharacteristic value: ';for (let i = 0; i < char.characteristicValue.byteLength; i++) {message += value[i] + ' ';}console.info(TAG, message);}private logDescriptor(des: ble.BLEDescriptor) {let message = 'logDescriptor uuid:' + des.descriptorUuid + '\n';let value = new Uint8Array(des.descriptorValue);message += 'logDescriptor value: ';for (let i = 0; i < des.descriptorValue.byteLength; i++) {message += value[i] + ' ';}console.info(TAG, message);}private checkService(services: Array<ble.GattService>): boolean {for (let i = 0; i < services.length; i++) {if (services[i].serviceUuid != this.myServiceUuid) {continue;}for (let j = 0; j < services[i].characteristics.length; j++) {if (services[i].characteristics[j].characteristicUuid != this.myCharacteristicUuid) {continue;}for (let k = 0; k < services[i].characteristics[j].descriptors.length; k++) {if (services[i].characteristics[j].descriptors[k].descriptorUuid == this.myFirstDescriptorUuid) {console.info(TAG, 'find expected service from server');return true;}}}}console.error(TAG, 'no expected service from server');return false;}// 1. 订阅连接状态变化事件public onGattClientStateChange() {if (!this.gattClient) {console.error(TAG, 'no gattClient');return;}try {this.gattClient.on('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {let state = '';switch (stateInfo.state) {case 0:state = 'DISCONNECTED';break;case 1:state = 'CONNECTING';break;case 2:state = 'CONNECTED';break;case 3:state = 'DISCONNECTING';break;default:state = 'undefined';break;}console.info(TAG, 'onGattClientStateChange: device=' + stateInfo.deviceId + ', state=' + state);if (stateInfo.deviceId == this.device) {this.connectState = stateInfo.state;}});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 2. client端主动连接时调用public startConnect(peerDevice: string) { // 对端设备一般通过ble scan获取到if (this.connectState != constant.ProfileConnectionState.STATE_DISCONNECTED) {console.error(TAG, 'startConnect failed');return;}console.info(TAG, 'startConnect ' + peerDevice);this.device = peerDevice;// 2.1 使用device构造gattClient,后续的交互都需要使用该实例this.gattClient = ble.createGattClientDevice(peerDevice);try {this.onGattClientStateChange(); // 2.2 订阅连接状态this.gattClient.connect(); // 2.3 发起连接} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 3. client端连接成功后,需要进行服务发现public discoverServices() {if (!this.gattClient) {console.info(TAG, 'no gattClient');return;}console.info(TAG, 'discoverServices');try {this.gattClient.getServices().then((result: Array<ble.GattService>) => {console.info(TAG, 'getServices success: ' + JSON.stringify(result));this.found = this.checkService(result); // 要确保server端的服务内容有业务所需要的服务});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 4. 在确保拿到了server端的服务结果后,读取server端特定服务的特征值时调用public readCharacteristicValue() {if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {console.error(TAG, 'no gattClient or not connected');return;}if (!this.found) { // 要确保server端有对应的characteristicconsole.error(TAG, 'no characteristic from server');return;}let characteristic = this.initCharacteristic();console.info(TAG, 'readCharacteristicValue');try {this.gattClient.readCharacteristicValue(characteristic).then((outData: ble.BLECharacteristic) => {this.logCharacteristic(outData);})} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 5. 在确保拿到了server端的服务结果后,写入server端特定服务的特征值时调用public writeCharacteristicValue() {if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {console.error(TAG, 'no gattClient or not connected');return;}if (!this.found) { // 要确保server端有对应的characteristicconsole.error(TAG, 'no characteristic from server');return;}let characteristic = this.initCharacteristic();console.info(TAG, 'writeCharacteristicValue');try {this.gattClient.writeCharacteristicValue(characteristic, ble.GattWriteType.WRITE, (err) => {if (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);return;}console.info(TAG, 'writeCharacteristicValue success');});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 6. 在确保拿到了server端的服务结果后,读取server端特定服务的描述符时调用public readDescriptorValue() {if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {console.error(TAG, 'no gattClient or not connected');return;}if (!this.found) { // 要确保server端有对应的descriptorconsole.error(TAG, 'no descriptor from server');return;}let descBuffer = new ArrayBuffer(0);let descriptor = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);console.info(TAG, 'readDescriptorValue');try {this.gattClient.readDescriptorValue(descriptor).then((outData: ble.BLEDescriptor) => {this.logDescriptor(outData);});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 7. 在确保拿到了server端的服务结果后,写入server端特定服务的描述符时调用public writeDescriptorValue() {if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {console.error(TAG, 'no gattClient or not connected');return;}if (!this.found) { // 要确保server端有对应的descriptorconsole.error(TAG, 'no descriptor from server');return;}let descBuffer = new ArrayBuffer(2);let descValue = new Uint8Array(descBuffer);descValue[0] = 11;descValue[1] = 12;let descriptor = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);console.info(TAG, 'writeDescriptorValue');try {this.gattClient.writeDescriptorValue(descriptor, (err) => {if (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);return;}console.info(TAG, 'writeDescriptorValue success');});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 8.client端主动断开时调用public stopConnect() {if (!this.gattClient || this.connectState != constant.ProfileConnectionState.STATE_CONNECTED) {console.error(TAG, 'no gattClient or not connected');return;}console.info(TAG, 'stopConnect ' + this.device);try {this.gattClient.disconnect(); // 8.1 断开连接this.gattClient.off('BLEConnectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {});this.gattClient.close() // 8.2 如果不再使用此gattClient,则需要close} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}
}let gattClientManager = new GattClientManager();
export default gattClientManager as GattClientManager;

server端操作services和通知客户端信息

  1. import需要的ble模块。

  2. 创建gattServer实例对象。

  3. 添加services信息。

  4. 当向gattServer写入特征值通知gattClient。

  5. 移除services信息。

  6. 注销gattServer实例。

  7. 示例代码:

import { ble } from '@kit.ConnectivityKit';
import { constant } from '@kit.ConnectivityKit';
import { AsyncCallback, BusinessError } from '@kit.BasicServicesKit';const TAG: string = 'GattServerManager';export class GattServerManager {gattServer: ble.GattServer = undefined;connectState: ble.ProfileConnectionState = constant.ProfileConnectionState.STATE_DISCONNECTED;myServiceUuid: string = '00001810-0000-1000-8000-00805F9B34FB';myCharacteristicUuid: string = '00001820-0000-1000-8000-00805F9B34FB';myFirstDescriptorUuid: string = '00002902-0000-1000-8000-00805F9B34FB'; // 2902一般用于notification或者indicationmySecondDescriptorUuid: string = '00002903-0000-1000-8000-00805F9B34FB';// 构造BLEDescriptorprivate initDescriptor(des: string, value: ArrayBuffer): ble.BLEDescriptor {let descriptor: ble.BLEDescriptor = {serviceUuid: this.myServiceUuid,characteristicUuid: this.myCharacteristicUuid,descriptorUuid: des,descriptorValue: value};return descriptor;}// 构造BLECharacteristicprivate initCharacteristic(): ble.BLECharacteristic {let descriptors: Array<ble.BLEDescriptor> = [];let descBuffer = new ArrayBuffer(2);let descValue = new Uint8Array(descBuffer);descValue[0] = 31;descValue[1] = 32;descriptors[0] = this.initDescriptor(this.myFirstDescriptorUuid, new ArrayBuffer(2));descriptors[1] = this.initDescriptor(this.mySecondDescriptorUuid, descBuffer);let charBuffer = new ArrayBuffer(2);let charValue = new Uint8Array(charBuffer);charValue[0] = 21;charValue[1] = 22;let characteristic: ble.BLECharacteristic = {serviceUuid: this.myServiceUuid,characteristicUuid: this.myCharacteristicUuid,characteristicValue: charBuffer,descriptors: descriptors};return characteristic;}// 1. 订阅连接状态变化事件public onGattServerStateChange() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}try {this.gattServer.on('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => {let state = '';switch (stateInfo.state) {case 0:state = 'DISCONNECTED';break;case 1:state = 'CONNECTING';break;case 2:state = 'CONNECTED';break;case 3:state = 'DISCONNECTING';break;default:state = 'undefined';break;}console.info(TAG, 'onGattServerStateChange: device=' + stateInfo.deviceId + ', state=' + state);});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 2. server端注册服务时调用public registerServer() {let characteristics: Array<ble.BLECharacteristic> = [];let characteristic = this.initCharacteristic();characteristics.push(characteristic);let gattService: ble.GattService = {serviceUuid: this.myServiceUuid,isPrimary: true,characteristics: characteristics};console.info(TAG, 'registerServer ' + this.myServiceUuid);try {this.gattServer = ble.createGattServer(); // 2.1 构造gattServer,后续的交互都需要使用该实例this.onGattServerStateChange(); // 2.2 订阅连接状态this.gattServer.addService(gattService);} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 3. 订阅来自gattClient的读取特征值请求时调用public onCharacteristicRead() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}console.info(TAG, 'onCharacteristicRead');try {this.gattServer.on('characteristicRead', (charReq: ble.CharacteristicReadRequest) => {let deviceId: string = charReq.deviceId;let transId: number = charReq.transId;let offset: number = charReq.offset;console.info(TAG, 'receive characteristicRead');let rspBuffer = new ArrayBuffer(2);let rspValue = new Uint8Array(rspBuffer);rspValue[0] = 21;rspValue[1] = 22;let serverResponse: ble.ServerResponse = {deviceId: deviceId,transId: transId,status: 0, // 0表示成功offset: offset,value: rspBuffer};try {this.gattServer.sendResponse(serverResponse);} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 4. 订阅来自gattClient的写入特征值请求时调用public onCharacteristicWrite() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}console.info(TAG, 'onCharacteristicWrite');try {this.gattServer.on('characteristicWrite', (charReq: ble.CharacteristicWriteRequest) => {let deviceId: string = charReq.deviceId;let transId: number = charReq.transId;let offset: number = charReq.offset;console.info(TAG, 'receive characteristicWrite: needRsp=' + charReq.needRsp);if (!charReq.needRsp) {return;}let rspBuffer = new ArrayBuffer(0);let serverResponse: ble.ServerResponse = {deviceId: deviceId,transId: transId,status: 0, // 0表示成功offset: offset,value: rspBuffer};try {this.gattServer.sendResponse(serverResponse);} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 5. 订阅来自gattClient的读取描述符请求时调用public onDescriptorRead() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}console.info(TAG, 'onDescriptorRead');try {this.gattServer.on('descriptorRead', (desReq: ble.DescriptorReadRequest) => {let deviceId: string = desReq.deviceId;let transId: number = desReq.transId;let offset: number = desReq.offset;console.info(TAG, 'receive descriptorRead');let rspBuffer = new ArrayBuffer(2);let rspValue = new Uint8Array(rspBuffer);rspValue[0] = 31;rspValue[1] = 32;let serverResponse: ble.ServerResponse = {deviceId: deviceId,transId: transId,status: 0, // 0表示成功offset: offset,value: rspBuffer};try {this.gattServer.sendResponse(serverResponse);} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 6. 订阅来自gattClient的写入描述符请求时调用public onDescriptorWrite() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}console.info(TAG, 'onDescriptorWrite');try {this.gattServer.on('descriptorWrite', (desReq: ble.DescriptorWriteRequest) => {let deviceId: string = desReq.deviceId;let transId: number = desReq.transId;let offset: number = desReq.offset;console.info(TAG, 'receive descriptorWrite: needRsp=' + desReq.needRsp);if (!desReq.needRsp) {return;}let rspBuffer = new ArrayBuffer(0);let serverResponse: ble.ServerResponse = {deviceId: deviceId,transId: transId,status: 0, // 0表示成功offset: offset,value: rspBuffer};try {this.gattServer.sendResponse(serverResponse);} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}});} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}// 7. server端删除服务,不再使用时调用public unRegisterServer() {if (!this.gattServer) {console.error(TAG, 'no gattServer');return;}console.info(TAG, 'unRegisterServer ' + this.myServiceUuid);try {this.gattServer.removeService(this.myServiceUuid); // 7.1 删除服务this.gattServer.off('connectionStateChange', (stateInfo: ble.BLEConnectionChangeState) => { // 7.2 取消订阅连接状态});this.gattServer.close() // 7.3 如果不再使用此gattServer,则需要close} catch (err) {console.error(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' + (err as BusinessError).message);}}
}let gattServerManager = new GattServerManager();
export default gattServerManager as GattServerManager;

最后呢

很多开发朋友不知道需要学习那些鸿蒙技术?鸿蒙开发岗位需要掌握那些核心技术点?为此鸿蒙的开发学习必须要系统性的进行。

而网上有关鸿蒙的开发资料非常的少,假如你想学好鸿蒙的应用开发与系统底层开发。你可以参考这份资料,少走很多弯路,节省没必要的麻烦。由两位前阿里高级研发工程师联合打造的《鸿蒙NEXT星河版OpenHarmony开发文档》里面内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(Harmony NEXT)技术知识点

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。下面是鸿蒙开发的学习路线图。

在这里插入图片描述

针对鸿蒙成长路线打造的鸿蒙学习文档。话不多说,我们直接看详细鸿蒙(OpenHarmony )手册(共计1236页)与鸿蒙(OpenHarmony )开发入门视频,帮助大家在技术的道路上更进一步。

  • 《鸿蒙 (OpenHarmony)开发学习视频》
  • 《鸿蒙生态应用开发V2.0白皮书》
  • 《鸿蒙 (OpenHarmony)开发基础到实战手册》
  • OpenHarmony北向、南向开发环境搭建
  • 《鸿蒙开发基础》
  • 《鸿蒙开发进阶》
  • 《鸿蒙开发实战》

在这里插入图片描述

总结

鸿蒙—作为国家主力推送的国产操作系统。部分的高校已经取消了安卓课程,从而开设鸿蒙课程;企业纷纷跟进启动了鸿蒙研发。

并且鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,未来将会支持 50 万款的应用。那么这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行! 自↓↓↓拿
1

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 十分钟学会Kubernetes(K8S) 部署SpringBoot3.0
  • Java语言程序设计基础篇_编程练习题**17.20 (二进制编辑器)
  • 系统编程-多路IO复用
  • NLP自然语言处理学习过程中知识点总结
  • CSS具有哪些特点呢?
  • 滚雪球学Java(88):连接无限,资源有度:JavaSE数据库连接池深度解析,有两下子!
  • 顶级出图效果!免费在线使用FLux.1 模型,5s出图无限制!
  • 三十四、模型绑定与验证
  • 客户端负载均衡Ribbon 小实例
  • 足球预测可以人工智能AI吗
  • c++ codedump文件输出具体出错位置
  • 汇聚创意精英?西安数字影像产业园如何实现这一壮举?
  • 三分钟带你看懂,低代码开发赋能办公方式转变
  • JAVA代码审计(基础漏洞:SQL注入、XXE、XSS、反序列化、CSRF、文件上传、逻辑漏洞、SSRF、命令执行)
  • C#-使用Serilog框架快速实现日志及其相关扩展
  • canvas 高仿 Apple Watch 表盘
  • JavaWeb(学习笔记二)
  • Map集合、散列表、红黑树介绍
  • node-sass 安装卡在 node scripts/install.js 解决办法
  • React Native移动开发实战-3-实现页面间的数据传递
  • 搞机器学习要哪些技能
  • 给初学者:JavaScript 中数组操作注意点
  • 关于Flux,Vuex,Redux的思考
  • 观察者模式实现非直接耦合
  • 爬虫进阶 -- 神级程序员:让你的爬虫就像人类的用户行为!
  • 使用Maven插件构建SpringBoot项目,生成Docker镜像push到DockerHub上
  • 手写一个CommonJS打包工具(一)
  • 为什么要用IPython/Jupyter?
  • 继 XDL 之后,阿里妈妈开源大规模分布式图表征学习框架 Euler ...
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​用户画像从0到100的构建思路
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (52)只出现一次的数字III
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (day 2)JavaScript学习笔记(基础之变量、常量和注释)
  • (done) NLP “bag-of-words“ 方法 (带有二元分类和多元分类两个例子)词袋模型、BoW
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (五)activiti-modeler 编辑器初步优化
  • (五)网络优化与超参数选择--九五小庞
  • (源码分析)springsecurity认证授权
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)大型网站的系统架构
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .Net 4.0并行库实用性演练
  • .NET 8 跨平台高性能边缘采集网关
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .Net MVC + EF搭建学生管理系统
  • .NET 自定义中间件 判断是否存在 AllowAnonymousAttribute 特性 来判断是否需要身份验证
  • .NET命名规范和开发约定
  • .vimrc 配置项
  • :O)修改linux硬件时间
  • @Autowired 和 @Resource 区别的补充说明与示例
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell