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配置文件加上对权限的声明:
json
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
这里添加了访问互联网的权限。
步骤3:在build-profile.json5配置文件加上仓颉编译架构:
json
"cangjieOptions": {
"path": "./src/main/cangjie/cjpm.toml",
"abiFilters": ["arm64-v8a", "x86_64"]
}
步骤4:在index.cj文件里添加如下的代码:
javascript
package ohos_app_cangjie_entry
import 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 {
@State
var title: String = '仓颉版TCP连接超时示例';
//连接、通讯历史记录
@State
var msgHistory: String = '';
//服务端端口号
@State
var serverPort: UInt16 = 9998;
//服务端地址
@State
var serverAddress: String = "*.*.*.*";
//测试连接按钮是否可用
@State
var testEnable: Bool = true
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)
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 - start
this.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文件,然后输入如下的代码:
javascript
package ohos_app_cangjie_entry
import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
@Component
public class TimeoutTestItem {
var isDefaultTimeout: Bool = true
var timeout: Int64 = 0
//连接测试按钮是否可用
@Link
var buttonEnabled: Bool
//测试按钮点击调用的函数
@Prop
var 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秒左右。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
gitee.com/zl3624/harm...
本系列源码地址:
gitee.com/zl3624/harm...