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

鸿蒙网络编程系列59-仓颉版TLS回声服务器示例

1. 网络通讯的安全性问题及解决方案

在基于TCP或者UDP的通讯中,通讯内容是明文发送和接收的,对于安全性要求不太高的通讯场景,这种方式因为实现简单,传输效率高而得到广泛应用;
但是,如果数据包在传输过程中被拦截,攻击者可以直接读取其中的信息,这使得用户的敏感信息(如密码、个人资料等)容易遭受窃听或篡改。要避免这种情况的发生,
就需要对传输过程进行加密,典型的是基于TLS协议的通讯,它通过加密技术确保数据的保密性和完整性,防止数据在传输过程中被窃听或篡改。当使用TLS进行通讯时,客户端和服务器先进行握手,在这个过程中双方协商加密算法、交换加密密钥等,之后所有传输的数据都会被加密,即使数据包被第三方截获,由于没有解密密钥,第三方也无法读取数据的真实内容。

本系列的第33篇文章,在API 12环境下使用ArkTS语言实现了TLS回声服务器,本篇文章将在API 17环境下,使用仓颉语言实现TLS回声服务器。
在目前的版本里,鸿蒙并没有提供TLS服务端相关的API,所以,本文将使用仓颉语言原生的TLS相关API实现,典型的类有TlsSocket、TlsServerConfig等,它们在net.tls包里。

TLS服务端的实现还需要服务端数字证书及私钥,需要预先准备好相关的文件并上传到鸿蒙设备中。

2. TLS回声服务器演示

本示例运行后的界面如图所示:

选择服务端数字证书及服务端数字证书私钥,然后单击“启动”按钮,可以绑定服务端到本地端口,如图所示:

更进一步的TLS通讯需要TLS客户端的配合,我们将在下一篇文章介绍TLS服务端和客户端的数据收发过程。

3. TLS回声服务器示例编写

下面详细介绍创建该示例的步骤(确保DevEco Studio已安装仓颉插件)。

步骤1:创建[Cangjie]Empty Ability项目。

步骤2:在module.json5配置文件加上对权限的声明:

"requestPermissions": [{"name": "ohos.permission.INTERNET"}]

这里添加了访问互联网的权限。

步骤3:在build-profile.json5配置文件加上仓颉编译架构:

"cangjieOptions": {"path": "./src/main/cangjie/cjpm.toml","abiFilters": ["arm64-v8a", "x86_64"]}

步骤4:在main_ability.cj文件里添加如下的代码:

package ohos_app_cangjie_entryinternal import ohos.base.AppLog
internal import ohos.ability.AbilityStage
internal import ohos.ability.LaunchReason
internal import cj_res_entry.app
import ohos.ability.*//Ability全局上下文
var globalAbilityContext: Option<AbilityContext> = Option<AbilityContext>.None
class MainAbility <: Ability {public init() {super()registerSelf()}public override func onCreate(want: Want, launchParam: LaunchParam): Unit {AppLog.info("MainAbility OnCreated.${want.abilityName}")globalAbilityContext = Option<AbilityContext>.Some(this.context)match (launchParam.launchReason) {case LaunchReason.START_ABILITY => AppLog.info("START_ABILITY")case _ => ()}}public override func onWindowStageCreate(windowStage: WindowStage): Unit {AppLog.info("MainAbility onWindowStageCreate.")windowStage.loadContent("EntryView")}
}

步骤5:在index.cj文件里添加如下的代码:

package ohos_app_cangjie_entryimport ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import ohos.file_picker.*
import ohos.ability.*
import ohos.file_fs.*
import crypto.x509.*
import ohos.crypto.*
import std.convert.*
import net.tls.*
import std.socket.*@Observed
//文件选择状态
class FileSelectStatus {@Publishpublic var fileSelected: Bool = false@Publishpublic var fileUri: String = ""
}@Entry
@Component
class EntryView {@Statevar title: String = 'TLS回声服务器示例';//连接、通讯历史记录@Statevar msgHistory: String = ''//证书文件选择状态@Statevar certFileStatus: FileSelectStatus = FileSelectStatus()//私钥文件选择状态@Statevar keyFileStatus: FileSelectStatus = FileSelectStatus()//服务端端口@Statevar port: UInt16 = 9990//服务运行状态@Statevar running: Bool = false//服务端套接字var echoServer: ?TcpServerSocket = Nonelet scroller: Scroller = Scroller()func build() {Row {Column {Text(title).fontSize(14).fontWeight(FontWeight.Bold).width(100.percent).textAlign(TextAlign.Center).padding(10)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("服务端数字证书:").fontSize(14).width(90).flexGrow(1)Button("选择").onClick {evt => selectFile(this.certFileStatus)}.width(60).fontSize(14)}.width(100.percent).padding(5)Text(certFileStatus.fileUri).fontSize(14).width(100.percent).padding(10)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("服务端数字证书私钥:").fontSize(14).width(90).flexGrow(1)Button("选择").onClick {evt => selectFile(this.keyFileStatus)}.width(60).fontSize(14)}.width(100.percent).padding(5)Text(keyFileStatus.fileUri).fontSize(14).width(100.percent).padding(10)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("绑定的服务器端口:").fontSize(14).width(90).flexGrow(1)TextInput(text: port.toString()).onChange({value => if (value == "") {port = 0} else {port = UInt16.parse(value)}}).setType(InputType.Number).width(80).fontSize(11)Button(if (running) {"停止"} else {"启动"}).onClick {evt => if (!running) {startServer()} else {stopServer()}}.width(60).fontSize(14).enabled(certFileStatus.fileSelected && keyFileStatus.fileSelected && port != 0)}.width(100.percent).padding(5)Scroll(scroller) {Text(msgHistory).textAlign(TextAlign.Start).padding(10).width(100.percent).backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width(100.percent).height(100.percent)}.height(100.percent)}//选择文件func selectFile(fileSelectStatus: FileSelectStatus) {let picker = DocumentViewPicker(getContext())let documentSelectCallback = {errorCode: Option<AsyncError>, data: Option<Array<String>> => match (errorCode) {case Some(e) => msgHistory += "选择失败,错误码:${e.code}\r\n"case _ => match (data) {case Some(value) =>fileSelectStatus.fileUri = value[0]fileSelectStatus.fileSelected = truecase _ => ()}}}picker.select(documentSelectCallback, option: DocumentSelectOptions(selectMode: DocumentSelectMode.MIXED))}//启动tls服务器func startServer() {let socketAddress = SocketAddress("0.0.0.0", port)//回显TcpSocket服务端echoServer = TcpServerSocket(bindAt: socketAddress)let tlsCfg = getTlsServerCfg()//允许恢复tls会话let sessionContext = TlsSessionContext.fromName("echo-server")//启动一个线程监听客户端连接spawn {//绑定到本地端口echoServer?.bind()msgHistory += "绑定到本地端口,等待连接...\r\n"running = truewhile (true) {//已接受客户端连接let acceptEchoSocket = echoServer?.accept()if (let Some(echoSocket) <- acceptEchoSocket) {msgHistory += "接受新的连接,对端地址为:${echoSocket.remoteAddress.kapString()}\r\n"//启动一个线程处理新的socketspawn {try {//生成服务端TLS套接字let tlsSocket = TlsSocket.server(echoSocket, sessionContext: sessionContext,serverConfig: tlsCfg)//握手tlsSocket.handshake()msgHistory += "已握手\r\n"//处理加密通讯dealWithEchoSocket(tlsSocket)} catch (err: SocketException) {println(err.message)}}}}}}//从socket读取数据并回写到socketfunc dealWithEchoSocket(echoSocket: TlsSocket) {//存放从socket读取数据的缓冲区let buffer = Array<UInt8>(1024, item: 0)while (true) {//从socket读取数据var readCount = echoSocket.read(buffer)if (readCount > 0) {//把接收到的数据转换为字符串let content = String.fromUtf8(buffer[0..readCount])msgHistory += "[${echoSocket.remoteAddress}]:${content}\r\n"//回写客户端,把content写入echoSocketechoSocket.write(content.toArray())}}}//停止tls服务器func stopServer() {echoServer?.close()running = falsemsgHistory += "服务已停止\r\n"}//获取服务端TLS配置信息func getTlsServerCfg() {//得到服务端x509数字证书let x509 = getCert(certFileStatus.fileUri)//得到服务端私钥let privateKey = getPrivateKey(keyFileStatus.fileUri)var tlsCfg = TlsServerConfig(x509, privateKey)//设置支持的TLS版本tlsCfg.maxVersion = TlsVersion.V1_3tlsCfg.minVersion = TlsVersion.V1_2return tlsCfg}//获取私钥func getPrivateKey(keyPath: String) {//获取文件在沙箱cache文件夹的路径let sandboxFilePath = getSandboxFilePath(keyPath)//从沙箱读取证书文件信息let certContent = FileFs.readText(sandboxFilePath)return PrivateKey.decodeFromPem(certContent)}//把文件复制到沙箱并返回沙箱中的文件路径func getSandboxFilePath(oriFilePath: String) {let fileName = getFileNameFromPath(oriFilePath)let file = FileFs.open(oriFilePath)//构造文件在沙箱cache文件夹的路径let sandboxFilePath = getContext().filesDir.replace("files", "cache") + "/" + fileName//复制私钥到沙箱给定路径FileFs.copyFile(file.fd, sandboxFilePath)//关闭文件FileFs.close(file)return sandboxFilePath}//获取数字证书func getCert(certPath: String) {//获取文件在沙箱cache文件夹的路径let sandboxFilePath = getSandboxFilePath(certPath)//从沙箱读取证书文件信息let certContent = FileFs.readText(sandboxFilePath)return X509Certificate.decodeFromPem(certContent)}//从文件路径获取文件名称public func getFileNameFromPath(filePath: String) {let segments = filePath.split('/')//文件名称return segments[segments.size - 1]}//获取Ability上下文func getContext(): AbilityContext {match (globalAbilityContext) {case Some(context) => contextcase _ => throw Exception("获取全局Ability上下文异常")}}
}

步骤6:编译运行,可以使用模拟器或者真机。

步骤7:按照本文第2部分“TLS回声服务器演示”操作即可。

4. 代码分析

仓颉语言版本的TLS服务器和ArkTS版本的实现差异非常大,基本没有相似性,相对来说,仓颉语言版本更靠底层一些,首先是启动一个TCP服务器,在指定的端口进行监听,在监听到新的客户端连接时,启动一个新的线程处理该连接,该线程调用TlsSocket的server函数生成一个服务端TLS套接字,接着处理该套接字的读写,主线程则继续监听新的连接。

(本文作者原创,除非明确授权禁止转载)

本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tls/TLSEchoServer4Cj

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples

http://www.xdnf.cn/news/16686.html

相关文章:

  • 如何迁移gitlab到另一台服务器
  • 图像认知与OpenCV | Day5:图像预处理(4)
  • C++20协程实战:高效网络库、手机终端、多媒体开发开发指南
  • Javaweb - 13 - AJAX
  • Qt|槽函数耗时操作阻塞主界面问题
  • Chrome 提示 “此扩展程序不再受支持”(MacOS/Windows)
  • WindowsAPI|每天了解几个winAPI接口之网络配置相关文档Iphlpapi.h详细分析六
  • C++异常捕获:为何推荐按引用(by reference)捕获?
  • 华为昇腾芯片:多模态模型国产化的硬核突破
  • Ext JS极速项目之 Coworkee
  • ETH 交易流程深度技术详解
  • Linux进程概念(五)进程地址空间
  • 凸优化:凸函数的一些常用性质
  • 低成本嵌入式Linux开发方案:通过配置文件实现参数设置
  • 基于黑马教程——微服务架构解析(二):雪崩防护+分布式事务
  • 如何在 Ubuntu 24.04 或 22.04 Linux 上安装和使用 NoMachine
  • JavaScript 回调函数讲解_callback
  • 力扣7:整数反转
  • golang--通道和锁
  • 做了一款小而美的本地校验器
  • jimfs:Java内存文件系统,脱离磁盘IO瓶颈利器
  • 使用Docker在Rocky Linux 9.5上在线部署LangFlow
  • 【力扣热题100】哈希——两数之和
  • 基于深度学习的医学图像分析:使用3D CNN实现肿瘤检测
  • 智慧工地系统:科技赋能建筑新未来
  • 采用黑翅鸢优化算法BKA-CNN-LSTM、CNN-LSTM、LSTM、CNN四模型多变量回归预测,多输入单输出(Matlab)
  • nifi 访问Kerberos的kafka集群
  • 【行测】常识判断1
  • 图解系统的学习笔记--硬件结构
  • 【安卓笔记】OOM与内存优化