写点什么

鸿蒙网络编程系列 5-TCP 连接超时分析

作者:长弓三石
  • 2024-10-22
    广东
  • 本文字数:4382 字

    阅读完需:约 14 分钟

1. TCP 连接超时简介

TCP 是面向连接的协议,通过三次握手建立连接,但是,在建立连接的过程中对方有可能没有响应,这时候发起连接的一方会重试,如果重试多次仍然没有响应,就会触发超时,从而导致连接失败,socket 的 connect 方法支持通过参数设置连接超时时间,默认情况下也可以使用缺省的超时时间,那么,问题来了:


  1. 连接的默认超时时间是多少?

  2. 如果超时时间设置为 0 会怎么样?

  3. 如果超时时间设置的非常大,比如 5 分钟,套接字会一直尝试连接吗?


要回答这些问题还不太容易,官方文档也没有给出,下面咱们就自己尝试下,寻找问题的答案。

2.TCP 连接超时示例

本示例通过 TCP 套接字尝试连接一个不存在的 IP 地址,所以肯定会连接失败,示例中共尝试了 5 次,第一次使用默认的超时时间,第二次使用 0 超时时间,第三次使用 3 秒超时时间,第四次使用 30 秒超时时间,第五次使用 300 秒超时时间,每次连接前后都记录下当时的时间,从而方便分析实际耗费的时间。本示例运行后的截图如下所示:



下面详细介绍创建该应用的步骤。


步骤 1:创建 Empty Ability 项目。


步骤 2:在 module.json5 配置文件加上对权限的声明:


"requestPermissions": [

{

​ "name": "ohos.permission.INTERNET"

},

{

​ "name": "ohos.permission.GET_WIFI_INFO"

}

]


这里分别添加了访问互联网和访问 WIFI 信息的权限。


步骤 3:在 Index.ets 文件里添加如下的代码:


import socket from '@ohos.net.socket';import wifiManager from '@ohos.wifiManager';import systemDateTime from '@ohos.systemDateTime';
//说明:本地的IP地址不是必须知道的,绑定时绑定到IP:0.0.0.0即可,显示本地IP地址的目的是方便对方发送信息过来//本地IP的数值形式let ipNum = wifiManager.getIpInfo().ipAddress//本地IP的字符串形式let localIp = (ipNum >>> 24) + '.' + (ipNum >> 16 & 0xFF) + '.' + (ipNum >> 8 & 0xFF) + '.' + (ipNum & 0xFF);
@Entry@Componentstruct Index { //连接、通讯历史记录 @State msgHistory: string = ''
//服务端IP地址 @State serverIp: string = "0.0.0.0" //服务端端口 @State serverPort: number = 9999 scroller: Scroller = new Scroller()
build() { Row() { Column() { Text("TCP连接超时演示") .fontSize(14) .fontWeight(FontWeight.Bold) .width('100%') .textAlign(TextAlign.Center) .padding(10)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("本地IP:") .width(100) .fontSize(14) .flexGrow(0) Text(localIp) .width(110) .fontSize(12) .flexGrow(0) }.width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("服务端地址:") .fontSize(14) .width(90) .flexGrow(0)
TextInput({ text: this.serverIp }) .onChange((value) => { this.serverIp = value }) .width(110) .fontSize(12) .flexGrow(1) Text(":") .width(5) .flexGrow(0)
TextInput({ text: this.serverPort.toString() }) .type(InputType.Number) .onChange((value) => { this.serverPort = parseInt(value) }) .fontSize(12) .flexGrow(0) .width(60) } .width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("默认超时时间:") .fontSize(14) .width(130) .flexGrow(0)
Text("default") .width(110) .fontSize(12) .flexGrow(1)
Button("连接测试") .onClick(() => { this.connect2Server(true, 0) }) .width(110) .fontSize(14) .flexGrow(0) } .width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("超时时间为0:") .fontSize(14) .width(130) .flexGrow(0)
Text("0") .width(110) .fontSize(12) .flexGrow(1)
Button("连接测试") .onClick(() => { this.connect2Server(false, 0) }) .width(110) .fontSize(14) .flexGrow(0) } .width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("超时时间为3秒:") .fontSize(14) .width(130) .flexGrow(0)
Text("3000") .width(110) .fontSize(12) .flexGrow(1)
Button("连接测试") .onClick(() => { this.connect2Server(false, 3000) }) .width(110) .fontSize(14) .flexGrow(0) } .width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("超时时间为30秒:") .fontSize(14) .width(130) .flexGrow(0)
Text("30000") .width(110) .fontSize(12) .flexGrow(1)
Button("连接测试") .onClick(() => { this.connect2Server(false, 30000) }) .width(110) .fontSize(14) .flexGrow(0) } .width('100%') .padding(5)
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { Text("超时时间为300秒:") .fontSize(14) .width(130) .flexGrow(0)
Text("300000") .width(110) .fontSize(12) .flexGrow(1)
Button("连接测试") .onClick(() => { this.connect2Server(false, 300000) }) .width(110) .fontSize(14) .flexGrow(0) } .width('100%') .padding(5)
Scroll(this.scroller) { Text(this.msgHistory) .textAlign(TextAlign.Start) .padding(5) .width('100%') .backgroundColor(0xeeeeee) } .align(Alignment.Top) .backgroundColor(0xeeeeee) .height(300) .flexGrow(1) .scrollable(ScrollDirection.Vertical) .scrollBar(BarState.On) .scrollBarWidth(20) } .width('100%') .justifyContent(FlexAlign.Start) .height('100%') .padding(5) } .height('100%') }
//连接服务端 async connect2Server(defaultTimeout: boolean, timeout: number) { //执行TCP通讯的对象 let tcpSocket = socket.constructTCPSocketInstance();
let localAddress = { address: "0.0.0.0", family: 1 }
await tcpSocket.bind(localAddress)
//服务端地址 let serverAddress = { address: this.serverIp, port: this.serverPort, family: 1 }
let option: socket.TCPConnectOptions = { address: serverAddress } if (!defaultTimeout) { option = { address: serverAddress, timeout: timeout } this.msgHistory += "连接超时时间设置:" + timeout.toString() + "\r\n"; } else { this.msgHistory += "连接超时时间设置:default" + "\r\n"; }
this.msgHistory += "连接开始时间:" + await getCurrentTimeString() + "\r\n"; await tcpSocket.connect(option) .then(() => { this.msgHistory += 'connect success ' + "\r\n"; }) .catch(async (e) => { this.msgHistory += 'connect fail ' + e.message + "\r\n"; this.msgHistory += "连接结束时间:" + await getCurrentTimeString() + "\r\n\r\n"; })
await tcpSocket.close() }}
//同步获取当前时间的字符串形式async function getCurrentTimeString() { let time = "" await systemDateTime.getDate().then( (date) => { time = date.getHours().toString() + ":" + date.getMinutes().toString() + ":" + date.getSeconds().toString() + ":" + date.getMilliseconds().toString() } ) return "[" + time + "]"}
复制代码


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


步骤 5:配置服务端 IP 和端口,一定要设置不存在的地址,然后逐次单击“连接测试”按钮,执行后的截图如下所示:



截图的最后是连接日志信息:


3.TCP 连接超时分析

从输出的日志可以分析如下:


1.默认的超时时间为 5 秒钟


2.超时时间设置为 0 时,按照 5 秒钟计算超时时间


3.设置 3 秒钟或者 30 秒钟,实际超时时间就是 3 秒钟或者 30 秒钟


4.设置大于 65 秒的超时时间,实际超时时间为 65 秒左右


前 3 个好理解,最后这个 65 秒是怎么回事呢?这里分析一下,实际上,TCP 连接重试时执行等待时间翻倍的规则,也就是连接失败后等待 1 秒钟重试,再失败等待 2 秒钟,然后依次是 4 秒钟、8 秒钟、16 秒钟、32 秒钟,linux 默认重试 5 次,也就是 1+2+4+8+16+32=63 秒钟,再加上其他耗费的时间,所以表现出来最大超时时间是 65 秒左右。


怎么样,是不是对鸿蒙系统下 TCP 连接超时时间有了更清晰的认识呢?


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


本文源码地址:


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


本系列源码地址:


https://gitee.com/zl3624/harmonyos_network_samples


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

长弓三石

关注

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

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

评论

发布
暂无评论
鸿蒙网络编程系列5-TCP连接超时分析_DevEco Studio_长弓三石_InfoQ写作社区