HarmonyOS 鸿蒙Next网络编程系列60-仓颉版TLS客户端示例

HarmonyOS 鸿蒙Next网络编程系列60-仓颉版TLS客户端示例

1. TLS客户端简介

TLS加密传输需要通讯双方的配合,在上一篇文章《鸿蒙网络编程系列59-仓颉版TLS回声服务器示例》中,我们介绍了基于仓颉语言的TLS服务端实现。本文也将使用仓颉语言在API 17的环境下实现TLS通讯客户端。TLS通讯的认证方式中,分为两种,一种是双向认证,也就是客户端需要验证服务端的身份,服务端也需要验证客户端的身份,这种方式安全性最高,但是需要客户端提供自己的数字证书,操作起来有点复杂,成本也较高,实际中只是在类似金融、政企等行业应用较多;另外一种是单向认证,也就是客户端认证服务端的身份,但是不需要服务端认证客户端的身份,比如我们平时上网浏览基于HTTPS协议的网站时,只需要验证网站提供的证书是否有效即可,网站是不需要查看我们自己的数字证书的。

在对数字证书的认证中,一般来说有默认、自定义以及完全相信三种,在仓颉的API中,通过枚举CertificateVerifyMode实现,该枚举包括如下三个选项:

  • CustomCA(Array) 根据提供的 CA 列表进行验证
  • Default 默认验证模式,根据系统 CA 验证证书
  • TrustAll 信任所有证书

在生产环境,一般使用Default选项即可,在开发和测试阶段,为降低开发成本,简化开发复杂性,可以选择CustomCA或者TrustAll,本文选择的是TrustAll信任所有证书的选项。

2. TLS客户端演示

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

假如我们已经启动了TLS服务端,可以输入服务端的地址,然后单击“连接”按钮,即可连接服务端并握手,如图所示:

接着,可以输入要发送给服务端的消息,比如“Hi,Tls Server!”,然后单击“发送”按钮,既可发送消息给服务端,同时接收服务端返回的消息,如图所示:

此时查看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:在index.cj文件里添加如下的代码:

package ohos_app_cangjie_entry

import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import ohos.ability.*
import std.convert.*
import net.tls.*
import std.socket.*

@Entry
@Component
class EntryView {
    @State
    var title: String = 'TLS通讯客户端示例';
    //连接、通讯历史记录
    @State
    var msgHistory: String = ''

    //是否运行
    @State
    var running = false

    //服务端端口
    @State
    var port: UInt16 = 9990

    //回显服务器地址
    @State
    var serverIp = "127.0.0.1"

    //要发送的信息
    @State
    var sendMsg = ""

    var tlsClient: ?TlsSocket = None

    let 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)

                    TextInput(text: serverIp).onChange({
                        value => serverIp = value
                    }).width(80).fontSize(11).flexGrow(1)
                    Text(":").fontSize(14)

                    TextInput(text: port.toString())
                        .onChange({
                            value => if (value == "") {
                                port = 0
                            } else {
                                port = UInt16.parse(value)
                            }
                        })
                        .setType(InputType.Number)
                        .width(80)
                        .fontSize(11)

                    Button("连接")
                        .onClick {
                            evt => connectServer()
                        }
                        .width(60)
                        .fontSize(14)
                        .enabled(serverIp.split(".", removeEmpty: true).size == 4 && port != 0)
                }.width(100.percent).padding(5)

                Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {
                    TextInput(placeholder: "输入要发送的信息", text: sendMsg)
                        .onChange({
                            value => sendMsg = value
                        })
                        .width(100)
                        .fontSize(11)
                        .flexGrow(1)

                    Button("发送")
                        .onClick {
                            evt =>
                            //为方便演示,在发送的信息后面添加回车换行
                            let realSendMsg = sendMsg + "\r\n"
                            msgHistory += "C:${realSendMsg}"
                            tlsClient?.write(realSendMsg.toArray())
                        }
                        .enabled(this.running && sendMsg != "")
                        .width(70)
                        .fontSize(14)
                }.width(100.percent).padding(10)

                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)
    }

    //连接tls服务端
    func connectServer() {
        //回显服务器客户端
        let echoClient = TcpSocket(serverIp, port)

        //连接回显服务器
        echoClient.connect()
        msgHistory += "连接服务端\r\n"

        var tlsClientCfg = getTlsClientCfg()

        //创建TLS客户端
        tlsClient = TlsSocket.client(echoClient, clientConfig: tlsClientCfg)

        //握手
        tlsClient?.handshake()
        msgHistory += "已握手\r\n"

        this.running = true
        //启动一个线程读取服务器的消息
        spawn {
            try {
                readFromEchoServer(tlsClient.getOrThrow())
            } catch (exp: SocketException) {
                msgHistory += "从套接字读取数据异常:${exp}\r\n"
            } catch (exp: Exception) {
                msgHistory += "异常:${exp}\r\n"
            }
        }
    }

    //获取客户端端TLS配置信息
    func getTlsClientCfg() {
        var tlsClientCfg = TlsClientConfig()

        tlsClientCfg.maxVersion = TlsVersion.V1_3
        tlsClientCfg.minVersion = TlsVersion.V1_2

        //设置信任所有证书认证模式
        tlsClientCfg.verifyMode = CertificateVerifyMode.TrustAll
        return tlsClientCfg
    }

    //从socket读取数据并打印输出
    func readFromEchoServer(echoSocket: TlsSocket) {
        //存放从socket读取数据的缓冲区
        let buffer = Array<UInt8>(1024, item: 0)

        while (true) {
            //从socket读取数据
            var readCount = echoSocket.read(buffer)

            //把接收到的数据转换为字符串
            let content = String.fromUtf8(buffer[0..readCount])

            //输出读取的内容,加上前缀S:
            msgHistory += "S:${content}"
        }
    }
}

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

步骤6:按照本文第2部分“TLS客户端演示”操作即可。

4. 代码分析

本示例成功演示的关键在于证书认证模式的设置,如果设置成Default或者CustomCA,就需要精心准备服务端证书,并且可能需要在客户端配置服务端证书的CA证书,如果是自己签发的服务端证书,很可能通不过客户端认证。本文旨在验证TLS通讯本身,可以在顺利通过TrustAll认证模式后,再切换到其他模式进行认证。

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

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

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


更多关于HarmonyOS 鸿蒙Next网络编程系列60-仓颉版TLS客户端示例的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

鸿蒙Next仓颉版TLS客户端开发要点:

  1. 使用@ohos.net.http模块创建HttpRequest对象
  2. 配置TLS参数需设置protocol为"TLSv1.2"或"TLSv1.3"
  3. 证书配置通过options.securityOptions指定CA证书路径
  4. 关键代码结构:
let httpRequest = http.createHttp()
let options = {
  method: "GET",
  protocol: "TLSv1.2",
  securityOptions: {
    caPath: "/path/to/ca.crt"
  }
}
httpRequest.request("https://example.com", options)

注意:仓颉TS语法需使用严格类型声明。

更多关于HarmonyOS 鸿蒙Next网络编程系列60-仓颉版TLS客户端示例的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


这是一个很好的HarmonyOS TLS客户端实现示例。代码清晰地展示了如何使用仓颉语言开发TLS客户端应用,主要特点包括:

  1. 实现了基本的TLS客户端功能,包括连接建立、握手和数据传输
  2. 使用了TrustAll证书验证模式简化开发测试
  3. 采用异步线程处理服务器响应
  4. 提供了完整的UI交互界面

关键点分析:

  • 通过TlsSocket.client()创建TLS客户端
  • 配置TlsClientConfig设置TLS版本和验证模式
  • 使用spawn创建后台线程处理数据接收
  • 实现了基本的错误处理机制

这个示例对于学习HarmonyOS网络编程和TLS安全通信很有参考价值,开发者可以根据实际需求调整证书验证模式和其他安全参数。

回到顶部