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

鸿蒙网络编程系列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.lryc.cn/news/603353.html

相关文章:

  • 42、鸿蒙HarmonyOS Next开发:应用上下文Context
  • Apache Ignite 的分布式原子类型(Atomic Types)
  • 专业Python爬虫实战教程:逆向加密接口与验证码突破完整案例
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 微博文章数据可视化分析-文章评论量分析实现
  • Apache Ignite Cluster Groups的介绍
  • U3D中的package
  • 【PHP】Swoole:CentOS安装Composer+Hyperf
  • vue2 使用liveplayer加载视频
  • .NET Core 3.1 升级到 .NET 8
  • 自学嵌入式 day37 HTML
  • 前端代码格式化工具HTML离线版
  • LangChain学习笔记01---基本概念及使用
  • SkSurface---像素的容器:表面
  • echarts饼图
  • .NET测试平台Parasoft dotTEST在汽车电子行业的核心功能及应用
  • OpenAI Python API 完全指南:从入门到实战
  • 使用jQuery动态操作HTML和CSS
  • 从centos更换至ubuntu的安装、配置、操作记录
  • 系统选择菜单(ubuntu grub)介绍
  • 智能健康项链专利拆解:ECG 与 TBI 双模态监测的硬件架构与信号融合
  • Ubuntu22.04系统安装,Nvidia显卡驱动安装问题
  • 【Linux系统编程】Ext2文件系统
  • Java 9 新特性解析
  • VR全景制作流程分享-众趣VR全景制作平台
  • 博物馆 VR 导览:图形渲染算法+智能讲解技术算法实现及优化
  • 以需求破局:DPVR AI Glasses 重塑 AI 眼镜产业生态
  • 【OpenAI】ChatGPT辅助编码:Spring Boot + Copilot自动生成业务逻辑
  • Agent常用搜索引擎Tavily使用学习
  • VR 三维重建:开启沉浸式体验新时代
  • idea 服务器Debug端口启动设置