鸿蒙网络编程系列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...

相关推荐
小满zs3 小时前
Zustand 第五章(订阅)
前端·react.js
涵信4 小时前
第一节 基础核心概念-TypeScript与JavaScript的核心区别
前端·javascript·typescript
谢尔登4 小时前
【React】常用的状态管理库比对
前端·spring·react.js
编程乐学(Arfan开发工程师)4 小时前
56、原生组件注入-原生注解与Spring方式注入
java·前端·后端·spring·tensorflow·bug·lua
小公主5 小时前
JavaScript 柯里化完全指南:闭包 + 手写 curry,一步步拆解原理
前端·javascript
姑苏洛言6 小时前
如何解决答题小程序大小超过2M的问题
前端
GISer_Jing7 小时前
JWT授权token前端存储策略
前端·javascript·面试
开开心心就好7 小时前
电脑扩展屏幕工具
java·开发语言·前端·电脑·php·excel·batch
拉不动的猪7 小时前
es6常见数组、对象中的整合与拆解
前端·javascript·面试
GISer_Jing8 小时前
Vue Router知识框架以及面试高频问题详解
前端·vue.js·面试