写点什么

鸿蒙网络编程系列 49- 仓颉版 TCP 客户端

作者:长弓三石
  • 2024-11-18
    广东
  • 本文字数:3573 字

    阅读完需:约 12 分钟

1. TCP 客户端简介

TCP 协议作为传输层的核心协议,确保了数据传输的可靠性与顺序性,构成了许多广泛应用的高层协议的基础。相较于 UDP,TCP 在正式开始数据传输前需完成三次握手以建立连接,这一过程虽然使得 TCP 在效率上略逊一筹,但其采用的发送-确认机制确保了数据传输的高度可靠性。此外,通过引入滑动窗口机制,TCP 不仅能够有效提升数据传输效率,还能够在一定程度上优化网络资源的利用。因此,尽管 TCP 在建立连接方面存在一定的开销,但它凭借强大的可靠性和高效的传输特性,在众多应用场景中占据着不可或缺的地位。本系列的第 3 篇文章《鸿蒙网络编程系列 3-TCP 客户端通讯示例》中基于 ArkTS 语言在 API 9 的环境下演示了 TCP 客户端的基本用法,本文将使用仓颉语言在 API 12 的环境中实现类似的功能。

2. TCP 套接字函数简介

在适配 ArkTS 的鸿蒙 API 中,提供了 TCPSocket 类作为 TCP 客户端套接字的封装,但是,在适配仓颉的鸿蒙 API 中,暂时没有提供类似的封装类,不过,仓颉基础类库中内置了 TcpSocket 类,可以提供类似的更加强大的功能。该类和本文相关的主要函数如下所示:


//使用地址和端口创建一个未连接的套接字public init(address: String, port: UInt16)
//连接远端套接字,timeout为超时时间public func connect(timeout!: ?Duration = None): Unit
//读取报文并存储到buffer指定的缓冲区,返回读取的数据长度public override func read(buffer: Array<Byte>): Int64
复制代码

3. TCP 客户端演示

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



配置 TCP 服务端的 IP 地址和端口号,如果没有合适的 TCP 服务端,也可以使用 nc 命令在服务器监听 9999 端口,命令如下:


nc -l -p 9999
复制代码


输入绑定的本地端口后,单击“连接”按钮即可连接服务端,如图所示:



连接成功后,输入要发送的信息,比如“Hi,Server!”,然后单击“发送”按钮,服务端也输入要发送的信息,比如“Hi,Client!”,TCP 客户端成功收发信息后的界面如图所示:



TCP 服务端成功收发信息后的界面如图所示:



可以看到,TCP 消息成功发送并接收了。

4. TCP 客户端示例编写

下面详细介绍创建该示例的步骤(确保 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 std.collection.HashMapimport std.convert.*import std.net.*import std.socket.*
@Entry@Componentclass EntryView { @State var title: String = '仓颉版TCP客户端示例'; //连接、通讯历史记录 @State var msgHistory: String = ''
//是否运行 @State var running = false
//客户端端口 @State var clientPort: UInt16 = 9998
//服务端端口号 @State var serverPort: UInt16 = 9999
//服务端地址 @State var serverAddress = "127.0.0.1"
//要发送的信息 @State var sendMsg = ""
//TCP客户端 var tcpClient: ?TcpSocket = 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)
TextInput(text: serverAddress).onChange({ value => serverAddress = value }).width(100).fontSize(11).flexGrow(1) Text(":").fontSize(14)
TextInput(text: serverPort.toString()).onChange({ value => serverPort = UInt16.parse(value) }).setType(InputType.Number).width(80).fontSize(11) }.width(100.percent).padding(10)
Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) { Text("绑定的本地端口:").fontSize(14)
TextInput(text: clientPort.toString()).onChange({ value => clientPort = UInt16.parse(value) }).setType(InputType.Number).width(100).fontSize(11).flexGrow(1)
Button("连接").onClick { evt => connect2Server() }.enabled(!this.running).width(70).fontSize(14) }.width(100.percent).padding(10)
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}" tcpClient?.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) }
func connect2Server() { tcpClient = TcpSocket(serverAddress, serverPort) tcpClient?.connect() msgHistory += "连接成功!\r\n" running = true //启动一个线程读取服务器的消息 spawn { try { //存放从socket读取数据的缓冲区 let buffer = Array<UInt8>(1024, item: 0)
while (true) { //从socket读取数据 var readCount = tcpClient?.read(buffer)
//把接收到的数据转换为字符串 let content = String.fromUtf8(buffer[0..readCount.getOrThrow()]) this.msgHistory += "S:" + content + "\r\n" } } catch (exp: Exception) { msgHistory += "从Socket读取数据错误:${exp}\r\n" } } }}
复制代码


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


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

4. 代码分析

本示例在接收服务端发送过来的消息时,也是通过一个线程实现的,该线程会循环阻塞式读取服务端发送的数据,如果服务端没有数据过来,就一直处于阻塞状态,代码如下:


while (true) {                    //从socket读取数据                    var readCount = tcpClient?.read(buffer)
//把接收到的数据转换为字符串 let content = String.fromUtf8(buffer[0..readCount.getOrThrow()]) this.msgHistory += "S:" + content + "\r\n" }
复制代码


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


本文源码地址:


https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/TCPClient4Cj


本系列源码地址:


https://gitee.com/zl3624/harmonyos_network_samples


发布于: 刚刚阅读数: 6
用户头像

长弓三石

关注

还未添加个人签名 2024-10-16 加入

二十多年软件开发经验的软件架构师,华为HDE、华为云HCDE、仓颉语言CLD、CCS,著有《仓颉语言网络编程》、《仓颉语言元编程》、《仓颉语言实战》、《鲲鹏架构入门与实战》等书籍,清华大学出版社出版。

评论

发布
暂无评论
鸿蒙网络编程系列49-仓颉版TCP客户端_DevEco Studio_长弓三石_InfoQ写作社区