鸿蒙网络编程系列53-仓颉版TCP连接超时分析示例
1. TCP连接超时简介
在本系列的第5篇文章《鸿蒙网络编程系列5-TCP连接超时分析》中,介绍了基于ArkTS语言使用TCPSocket对象连接TCP服务端超时的示例。不过,在基于仓颉语言的鸿蒙应用开发中,并没有提供类似的鸿蒙API,幸运的是,仓颉语言自己内置了更强大的TcpSocket对象,可以使用该对象连接TCP服务端。在本系列的第49篇文章《鸿蒙网络编程系列49-仓颉版TCP客户端》中,介绍了TcpSocket对象的connect函数的用法,本文就不再赘述了,不过,我们同样要回答如下的三个问题:
- 连接的默认超时时间是多少?
- 如果超时时间设置为0会怎么样?
- 如果超时时间设置的非常大,比如5分钟,套接字会一直尝试连接吗?
这些问题不太容易回答,官方目前也没有相应的文档,接下来我们将通过一个示例来寻找答案。
2. TCP连接超时示例演示
本示例运行后的页面如图所示:
输入服务端地址和端口后(需确保服务器是真实的,但是端口是不存在的),单击下面的“连接测试”按钮,会发起连接,如图所示:
等选择的测试完成后,再一次单击下面的按钮,最终测试的输入如下所示:
下面是滚动条隐藏的内容:
3. 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_entryimport ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.HashMap
import std.convert.*
import std.net.*
import std.socket.*
import std.time.Duration
import std.time.DateTime@Entry
@Component
class EntryView {@Statevar title: String = '仓颉版TCP连接超时示例';//连接、通讯历史记录@Statevar msgHistory: String = '';//服务端端口号@Statevar serverPort: UInt16 = 9998;//服务端地址@Statevar serverAddress: String = "*.*.*.*";//测试连接按钮是否可用@Statevar testEnable: Bool = truelet 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)TimeoutTestItem(isDefaultTimeout: true, timeout: 0, connectTest: this.connectTest,buttonEnabled: testEnable)TimeoutTestItem(isDefaultTimeout: false, timeout: 0, connectTest: this.connectTest,buttonEnabled: testEnable)TimeoutTestItem(isDefaultTimeout: false, timeout: 3000, connectTest: this.connectTest,buttonEnabled: testEnable)TimeoutTestItem(isDefaultTimeout: false, timeout: 30000, connectTest: this.connectTest,buttonEnabled: testEnable)TimeoutTestItem(isDefaultTimeout: false, timeout: 300000, connectTest: this.connectTest,buttonEnabled: testEnable)Scroll(scroller) {Text(msgHistory).textAlign(TextAlign.Start).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 connectTest(isDefaultTimeout: Bool, timeout: Int64) {let tcpClient = TcpSocket(serverAddress, serverPort)if (isDefaultTimeout) {this.msgHistory += "连接超时时间设置:默认\r\n"} else {this.msgHistory += "连接超时时间设置:${timeout}毫秒\r\n"}let start = DateTime.now()this.msgHistory += "连接开始时间:${start.toString("yyyyMMdd HH:mm:ss")}\r\n"this.msgHistory += "正在连接...\r\n"//测试期间禁止点击按钮testEnable = false//启动一个线程进行测试spawn {try {//如果使用默认超时配置,就不配置超时时间if (isDefaultTimeout) {tcpClient.connect()} else { //配置超时时间tcpClient.connect(timeout: Duration.millisecond * timeout)}} catch (err: Exception) {//抛出异常的时间let end = DateTime.now()let period = end - startthis.msgHistory += "连接异常:${err.toString()}\r\n"this.msgHistory += "连接结束时间:${end.toString("yyyyMMdd HH:mm:ss")}\r\n"this.msgHistory += "连接耗时:${period.toMilliseconds()}毫秒\r\n\r\n"}//测试完成允许点击按钮testEnable = true}}
}
步骤5:本示例使用了自定义组件TimeoutTestItem,需要创建TimeoutTestItem.cj文件,然后输入如下的代码:
package ohos_app_cangjie_entryimport ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*@Component
public class TimeoutTestItem {var isDefaultTimeout: Bool = truevar timeout: Int64 = 0//连接测试按钮是否可用@Linkvar buttonEnabled: Bool//测试按钮点击调用的函数@Propvar connectTest: (isDefaultTimeout: Bool, timeout: Int64) -> Future<Unit>func build() {Row {Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text(if (this.isDefaultTimeout) {"默认超时时间"} else {"超时时间为${this.timeout / 1000}秒"}).width(150)Text(if (this.isDefaultTimeout) {"default"} else {this.timeout.toString()}).width(150).flexGrow(1)Button("连接测试").onClick {evt => connectTest(this.isDefaultTimeout, this.timeout)}.enabled(buttonEnabled)}.width(100.percent).padding(5)}}
}
步骤6:编译运行,可以使用模拟器或者真机。
步骤7:按照本文第2部分“TCP连接超时示例演示”操作即可。
4. 代码分析
本示例为简化代码,使用自定义组件TimeoutTestItem处理连接测试按钮的点击和状态显示问题,这里面有点复杂的是测试按钮的回调函数,该函数在index.cj中定义,名称为connectTest,接收两个参数,分别表示是否使用默认超时配置以及超时时间。在TimeoutTestItem中,通过变量connectTest指向该函数,并在按钮点击后触发回调。
另外一点需要注意的是,在模拟器和真机环境里,执行的效果是不同的,模拟器可以用来测试,但是要以真机环境运行的为准。在测试的时候,需要确保目标服务器是真实存在的,网络也是通畅的,同时要确保端口不存在,这样就可以实现连接超时的测试了。
根据输出的日志可以分析如下:
1.默认的超时时间为65秒钟
2.超时时间设置为0时,如果连接不上就马上返回,也就是说超时时间就是0
3.设置3秒钟或者30秒钟,实际超时时间就是3秒钟或者30秒钟
4.设置大于65秒的超时时间,实际超时时间为65秒左右
那么,为什么最长超时时间是65秒钟呢?这里分析一下,实际上,TCP连接重试时执行等待时间翻倍的规则,也就是连接失败后等待1秒钟重试,再失败等待2秒钟,然后依次是4秒钟、8秒钟、16秒钟、32秒钟,默认重试5次,也就是1+2+4+8+16+32=63秒钟,再加上其他环节耗费的时间,所以表现出来最大超时时间是65秒左右。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/TCPTimeout4Cj
本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples