鸿蒙网络编程系列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配置文件加上对权限的声明:

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...

相关推荐
一只大侠的侠24 分钟前
Flutter开源鸿蒙跨平台训练营 Day 10特惠推荐数据的获取与渲染
flutter·开源·harmonyos
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60613 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅4 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅4 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment4 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端