写点什么

鸿蒙网络编程系列 25-TCP 回声服务器的实现

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

    阅读完需:约 14 分钟

1. TCP 回声服务器实现可行性

在前文鸿蒙网络编程系列2-UDP回声服务器的实现中,介绍了什么是回声服务器,并且基于 UDP 协议实现了一个简单的回声服务器,本节将基于 TCP 协议实现一个类似的回声服务器。在鸿蒙 API10 以后,提供了 TCPSocketServer 类,该类封装了 TCP 服务端的相关接口,包括用来监听的 listen 方法,订阅各种事件的 on 方法,以及发送数据的 send 方法,关于这些接口的简介见前文鸿蒙网络编程系列23-实现一个基于鸿蒙API的HTTP服务器中的第一部分,或者直接查看鸿蒙官方文档,有了这些接口的支持,编写回声服务器也是易如反掌了。

2. 实现 TCP 回声服务器示例

本示例运行后的界面如下所示:



输入要监听的端口,然后单击“启动”按钮即可启动对该端口的监听了,如果有 TCP 客户端发送数据到这个端口,服务器会接收数据并在下面的日志区域显示,然后回写到发送端。


下面详细介绍创建该示例应用的步骤并演示用法。


步骤 1:创建 Empty Ability 项目。


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


    "requestPermissions": [      {        "name": "ohos.permission.INTERNET"      },      {        "name": "ohos.permission.KEEP_BACKGROUND_RUNNING"      }    ]
复制代码


这里添加了对互联网的访问权限以及后台执行权限。


在 module.json5 配置文件的 abilities 节点下加上长时任务类型的配置:


        "backgroundModes": [          "dataTransfer"        ]
复制代码


步骤 3:在文件 EntryAbility.ets 中的 onCreate 生命周期回调函数添加如下的代码:


onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {    let wantAgentInfo: wantAgent.WantAgentInfo = {      // 点击通知后,将要执行的动作列表      wants: [        {          bundleName: "com.zl.network.demo.tcp.echoserver",          abilityName: "EntryAbility"        }      ],      // 点击通知后,动作类型      actionType: wantAgent.OperationType.START_ABILITY,      // 使用者自定义的一个私有值      requestCode: 0,      // 点击通知后,动作执行属性      wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]    };
try { // 通过wantAgent模块下getWantAgent方法获取WantAgent对象 wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => { try { let list: Array<string> = ["dataTransfer"]; backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj) .then((res: backgroundTaskManager.ContinuousTaskNotification) => { console.info("Operation startBackgroundRunning succeeded");
}) .catch((error: BusinessError) => { console.error(`Operation startBackgroundRunning failed. code is ${error.code} message is ${error.message}`); }); } catch (error) { console.error(`Operation startBackgroundRunning failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`); } }); } catch (error) { console.error(`Operation getWantAgent failed. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`); } }
复制代码


该段代码主要是启动后台长时服务,因为 TCP 服务端有可能需要长时间在后台运行,通过该步骤可以获得授权。


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


import { socket } from '@kit.NetworkKit';import { BusinessError } from '@kit.BasicServicesKit';import { ArrayList, util } from '@kit.ArkTS';
//TCP服务端实例let tcpSocketServer: socket.TCPSocketServer = socket.constructTCPSocketServerInstance()
@Entry@Componentstruct Index { @State title: string = 'TCP回声服务器示例'; @State running: boolean = false //连接、通讯历史记录 @State msgHistory: string = '' //本地端口 @State port: number = 9999 scroller: Scroller = new Scroller() //已连接的客户端列表 clientList = new ArrayList<socket.TCPSocketConnection>()
build() { RelativeContainer() { Text(this.title) .id('txtTitle') .fontSize(20) .fontWeight(FontWeight.Bold) .alignRules({ middle: { anchor: '__container__', align: HorizontalAlign.Center }, top: { anchor: '__container__', align: VerticalAlign.Top } }) .margin(10)
Text("绑定的服务器端口:") .id('txtPort') .fontSize(15) .textAlign(TextAlign.Start) .height(40) .width(150) .alignRules({ left: { anchor: '__container__', align: HorizontalAlign.Start }, top: { anchor: 'txtTitle', align: VerticalAlign.Bottom } }) .margin(10)
TextInput({ text: this.port.toString() }) .onChange((value) => { this.port = parseInt(value) }) .type(InputType.Number) .height(40) .id('txtInputPort') .fontSize(15) .alignRules({ left: { anchor: 'txtPort', align: HorizontalAlign.End }, right: { anchor: 'btnStart', align: HorizontalAlign.Start }, top: { anchor: 'txtTitle', align: VerticalAlign.Bottom } }) .margin(5)
Button(this.running ? "停止" : "启动") .onClick(() => { if (!this.running) { this.startServer() } else { this.stopServer() } }) .height(40) .width(80) .id('btnStart') .fontSize(15) .alignRules({ right: { anchor: '__container__', align: HorizontalAlign.End }, top: { anchor: 'txtTitle', align: VerticalAlign.Bottom } }) .margin(5)
Scroll(this.scroller) { Text(this.msgHistory) .textAlign(TextAlign.Start) .padding(10) .width('100%') .fontSize(12) .backgroundColor(0xeeeeee) } .alignRules({ left: { anchor: '__container__', align: HorizontalAlign.Start }, top: { anchor: 'txtPort', align: VerticalAlign.Bottom }, bottom: { anchor: '__container__', align: VerticalAlign.Bottom } }) .align(Alignment.Top) .backgroundColor(0xeeeeee) .scrollable(ScrollDirection.Vertical) .scrollBar(BarState.On) .scrollBarWidth(20) .margin(5) .id('scrollHis') } .height('100%') .width('100%') }
//停止服务 stopServer() { tcpSocketServer.off('connect') for (let client of this.clientList) { client.off('message') } this.running = false this.msgHistory += "停止服务\r\n" }
//启动服务 startServer() { //订阅连接事件消息 tcpSocketServer.on('connect', (clientSocket: socket.TCPSocketConnection) => { this.clientList.add(clientSocket) clientSocket.on('message', (msgInfo: socket.SocketMessageInfo) => { //收到的信息转化为字符串 let content = buf2String(msgInfo.message) //显示信息日志,最后加上回车换行 this.msgHistory += `[${msgInfo.remoteInfo.address}:${msgInfo.remoteInfo.port}]${content}\r\n` //把收到的信息发回客户端 clientSocket.send({ data: msgInfo.message }) }) });
let listenAddress: socket.NetAddress = { address: '0.0.0.0', port: this.port } //绑定到指定的端口并启动客户端连接监听 tcpSocketServer.listen(listenAddress).then(() => { this.msgHistory += "监听成功\r\n" this.running = true this.msgHistory += "服务启动\r\n" }).catch((err: BusinessError) => { this.msgHistory += "监听失败\r\n" }); }}
//ArrayBuffer转utf8字符串function buf2String(buf: ArrayBuffer) { let msgArray = new Uint8Array(buf); let textDecoder = util.TextDecoder.create("utf-8"); return textDecoder.decodeWithStream(msgArray)}
复制代码


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


步骤 6:单击“启动”按钮启动 TCP 服务端。


步骤 7:启动 TCP 客户端,输入服务端地址和端口,单击“连接”按钮连接到服务端,然后输入要发送给服务端的消息,最后单击“发送”按钮,界面如下图所示。



步骤 8:此时查看 TCP 服务端界面,可以看到服务端也接收到了客户端的消息,如下图所示。



在本示例中,要注意的是 TCP 服务端是可以接受多个客户端的连接的,也就是说在订阅连接事件的消息并进行处理时,每一个连接(TCPSocketConnection)都代表一个新的 TCP 客户端,这些客户端都是互相独立的,要处理与客户端的消息收发,就要订阅每一个连接的接收消息事件。

3.注意事项

服务端需要长时间运行,为了在应用进入后台后仍然可以响应,在代码里申请了后台运行权限,在 oncreate 回调里也做了响应的处理,不过本示例只在模拟器里调试通过,还没有机会在实体机中实际运行。


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


本文源码地址:


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


本系列源码地址:


https://gitee.com/zl3624/harmonyos_network_samples


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

长弓三石

关注

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

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

评论

发布
暂无评论
鸿蒙网络编程系列25-TCP回声服务器的实现_DevEco Studio_长弓三石_InfoQ写作社区