鸿蒙网络编程系列50-仓颉版TCP回声服务器示例

1. TCP服务端简介

TCP服务端是基于TCP协议构建的一种网络服务模式,它为HTTP(超文本传输协议)、SMTP(简单邮件传输协议)等高层协议的应用程序提供了可靠的底层支持。在TCP服务端中,服务器启动后会监听一个或多个端口,等待客户端发起连接请求。当接收到客户端的连接请求时,服务端会响应并建立一个全双工的连接,并确保双方都准备好进行数据交换。一旦连接建立成功,服务端就能够开始接收来自客户端的数据,并根据需要向客户端发送响应信息。对于运行在TCP服务端之上的应用层协议来说,这种可靠的服务是非常重要的。例如,Web服务器通过HTTP协议处理网页请求时,就需要依赖TCP来确保请求和响应数据的正确性;邮件服务器使用SMTP协议发送和接收邮件时,同样依靠TCP保证邮件内容的完整传输。掌握TCP服务端的编程技巧,可以极大地提升开发者的网络应用构建能力。本系列的第25篇文章《鸿蒙网络编程系列25-TCP回声服务器的实现》中基于ArkTS语言实现了TCP回声服务器,演示了基本的TCP服务端编程方法,本文将使用仓颉语言在API 12的环境中实现类似的功能。

2. TCP回声服务器演示

本示例运行后的页面如图所示:

输入绑定的本地端口,默认是9999,单击“启动”按钮即可启动TCP监听服务,如图所示:

再启动上一篇文章《鸿蒙网络编程系列49-仓颉版TCP客户端》中介绍的TCP客户端,使用该客户端连接本TCP服务器,然后发送“Hi,TCP Server”给服务端,如图所示:

可以看到,收到了服务端的回复,此时再查看回声服务器的日志,如图所示:

可以看到,回声服务器也收到了客户端发送的消息。

3. TCP回声服务器示例编写

下面详细介绍创建该示例的步骤(确保DevEco Studio已安装仓颉插件)。

步骤1:创建[Cangjie]Empty Ability项目。

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

"requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]

这里添加了访问互联网的权限。

步骤3:在build-profile.json5配置文件加上仓颉编译架构:

"cangjieOptions": {
      "path": "./src/main/cangjie/cjpm.toml",
      "abiFilters": ["arm64-v8a", "x86_64"]
    }

步骤4:在index.cj文件里添加如下的代码:

package ohos_app_cangjie_entry

import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import ohos.net.http.*
import ohos.ability.getStageContext
import ohos.ability.*
import std.convert.*
import std.net.*
import std.socket.*

@Entry
@Component
class EntryView {
    @State
    var title: String = '仓颉版TCP回声服务器示例';
    //连接、通讯历史记录
    @State
    var msgHistory: String = ''
    //本地端口
    @State
    var localPort: UInt16 = 9999

    //绑定状态
    @State
    var bindState = false

    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: localPort.toString()).onChange({
                        value => localPort = UInt16.parse(value)
                    }).setType(InputType.Number).width(100).fontSize(11).flexGrow(1)

                    Button("启动").onClick {
                        evt => startServer()
                    }.enabled(!bindState).width(70).fontSize(14)
                }.width(100.percent).padding(10)

                Scroll(scroller) {
                    Text(msgHistory).textAlign(TextAlign.Start).padding(10).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 startServer() {
        //TCP服务端
        let tcpServer = TcpServerSocket(bindAt: localPort)
        tcpServer.bind()
        msgHistory += "绑定到端口${localPort}\r\n"
        bindState = true

        //启动一个线程监听客户端的连接并读取客户端发送过来的消息
        spawn {
            msgHistory += "开始监听客户端连接\r\n"
            while (true) {
                let echoClient = tcpServer.accept()
                msgHistory += "接受客户端连接, 客户端地址:${echoClient.remoteAddress}\r\n"
                //启动一个线程处理新的socket
                spawn {
                    try {
                        dealWithEchoClient(echoClient)
                    } catch (exp: Exception) {
                        msgHistory += "从套接字读取数据出错:${exp}\r\n"
                    }
                }
            }
        }
    }

    //从客户端套接字读取数据并回写
    func dealWithEchoClient(echoClient: TcpSocket) {
        //存放从socket读取数据的缓冲区
        let buffer = Array<UInt8>(1024, item: 0)

        while (true) {
            //从socket读取数据
            var readCount = echoClient.read(buffer)
            if (readCount > 0) {
                //把接收到的数据转换为字符串
                let content = String.fromUtf8(buffer[0..readCount])

                //输出接收到的信息到日志
                msgHistory += "${echoClient.remoteAddress}:${content}\r\n"
                //回写到客户端
                echoClient.write(content.toArray())
            }
        }
    }
}

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

步骤6:按照本文第2部分“TCP回声服务器演示”操作即可。

4. 代码分析

本示例的关键部分有两处,第一处是开启监听,就是监听绑定的端口,等待客户端的连接,这个监听是阻塞的,如果没有客户端连接就会一直等待,代码如下:

let echoClient = tcpServer.accept()

如果有客户端连接,就会返回代表客户端连接的套接字,本示例中就是echoClient变量。

另一处是关于客户端连接的数据读取和发送,因为服务端可能会同时接收多个客户端的连接,为提高处理效率,这里新起了一个线程来专门负责客户端连接的数据读写,代码如下:

spawn {
                    try {
                        dealWithEchoClient(echoClient)
                    } catch (exp: Exception) {
                        msgHistory += "从套接字读取数据出错:${exp}\r\n"
                    }
                }

具体的处理在函数dealWithEchoClient中实现。

本示例为简化代码的编写,假设客户端发送的数据可以一次性全部接收,也就是假设不存在数据粘包问题,当然实际中可能会存在,后面文章会针对仓颉语言讲解实现方式,关于ArkTS的实现方式见第本系列的第35篇《鸿蒙网络编程系列35-通过数据包结束标志解决TCP粘包问题》或第36篇《鸿蒙网络编程系列36-固定包头可变包体解决TCP粘包问题》

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

本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/TCPEchoServer4Cj

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/920436.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第5-1节:SpringBoot对SpringMVC的自动配置

我的后端学习大纲 SpringBoot学习大纲 1、SpringBoot对SpringMVC自动配置概览

Emacs进阶之插入时间信息(一百六十三)

简介&#xff1a; CSDN博客专家、《Android系统多媒体进阶实战》一书作者 新书发布&#xff1a;《Android系统多媒体进阶实战》&#x1f680; 优质专栏&#xff1a; Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a; 多媒体系统工程师系列【…

嵌入式实验报告:家用计时器

实验目的和要求 1、实验目的 掌握STM32串口通信原理。学习编程实现STM32的UART通信掌握STM32中断程序设计流程。熟悉STM32固件库的基本使用。熟悉STM32定时器中断设计流程。2、实验要求 设计一个家用计时器,其功能如下: 利用串口设置计时时间,格式:XX:XX:X 例如01:59:…

【WRF理论第十二期】Registry.EM 文件详解

【WRF理论第十二期】Registry.EM 文件详解 Registry.EM 文件的作用Registry.EM 文件的结构Registry.EM 文件内容理解如何修改 Registry.EM 文件以输出特定变量WRF-Urban 修改 Registry.EM 文件以输出 UCM 相关变量1. 修改 Registry.EM 文件2. 重新编译 WRF 注意事项参考 在 WRF…

Midjourney 图生图,真人二次元保持一致性,场景多元可选择

Midjourney 拥有强大的图生图的功能&#xff0c;下面我们就来看一下&#xff0c;如何在我们的AceDataCloud网站上实现将照片切换成任意的二次元场景&#xff0c;同时保持人物的一致性。 我们可以按照如下的步骤去实现人物一致性。 下面我们来看看效果吧&#xff0c;原图如下。…

三种复制只有阅读权限的飞书网络文档的方法

大家都知道&#xff0c;飞书是一款功能强大的在线协作工具&#xff0c;可以帮助团队更高效地协作和沟通。越来越多的资料都在使用飞书文档&#xff0c;在使用飞书的过程中&#xff0c;发现很多文档没有复制权限&#xff0c;如果想要摘抄笔记&#xff0c;只能一个字一个字地敲出…

【GL003】TCP/IP 协议

目录 一、TCP/IP协议简介 二、TCP/IP协议的分层模型 2.1 OSI模型的七层框架 2.2 TCP/IP协议层&#xff08;四层&#xff09; 2.2.1 TCP/IP协议层与ISO模型 2.2.2 TCP/IP协议层的作用 三、TCP协议的报文格式 3.1 什么是报文 3.2 TCP报文 四、TCP的通信连接 4.1 TCP…

Spring WebFlux学习笔记(二)

目标 运行第一个spring webflux项目 官网操作 https://start.spring.io/ 依赖、工具 jdk 21、idea、maven 运行过程 将下载的代码直接导入到idea后运行 运行上个笔记的例子 注意 需要更改为MediaType.TEXT_EVENT_STREAM_VALUE 未完待续。。。

【YOLOv8】安卓端部署-2-项目实战

文章目录 1 准备Android项目文件1.1 解压文件1.2 放置ncnn模型文件1.3 放置ncnn和opencv的android文件1.4 修改CMakeLists.txt文件 2 手机连接电脑并编译软件2.1 编译软件2.2 更新配置及布局2.3 编译2.4 连接手机 3 自己数据集训练模型的部署4 参考 1 准备Android项目文件 1.1…

基于CNN-LSTM的时序预测MATLAB实战

卷积神经网络&#xff08;CNN&#xff09;用于提取时间序列数据中的局部空间特征&#xff0c;通过卷积层和池化层的堆叠&#xff0c;CNN能够有效捕获数据中的短期模式和局部依赖关系。长短时记忆网络&#xff08;LSTM&#xff09;用于处理时间序列数据&#xff0c;特别擅长捕捉…

3D可视化产品定制,打造“所见即所得”的购物体验!

在当今数字化时代&#xff0c;3D可视化产品定制正逐步改变着消费者的购物体验与企业的销售模式&#xff0c;相较于大多仍停留在二维层面的线上定制服务&#xff0c;3D可视化产品定制为消费者提供了一个直观、互动且高度个性化的定制功能&#xff0c;并为消费者带来了沉浸式的购…

捉虫记录02-Nacos访问失败

目录 一、问题 二、排查 三、解决方案 一、问题 在访问nacos的时候出现以下问题&#xff1a; 二、排查 先用docker logs nacos来查找报错信息 docker logs nacos 看问题报错就是数据源问题&#xff0c;nacos没能连接上mysql 三、解决方案 第一步 docker restart mysql …

详细教程-Linux上安装单机版的Hadoop

1、上传Hadoop安装包至linux并解压 tar -zxvf hadoop-2.6.0-cdh5.15.2.tar.gz 安装包&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1u59OLTJctKmm9YVWr_F-Cg 提取码&#xff1a;0pfj 2、配置免密码登录 生成秘钥&#xff1a; ssh-keygen -t rsa -P 将秘钥写入认…

桌面怎么快速添加便签?适合桌面记事的便签小工具

在数字化时代&#xff0c;我们每天面对电脑处理大量任务&#xff0c;无论是工作计划、会议纪要还是个人生活琐事&#xff0c;都需要一个可靠的桌面记事工具来帮助我们记录和整理。因此&#xff0c;一款适合桌面使用的便签软件成为了我们不可或缺的助手。 敬业签就是这样一款功…

Django实现智能问答助手-基础配置

设置 Django 项目、创建应用、定义模型和视图、实现问答逻辑&#xff0c;并设计用户界面。下面是一步一步的简要说明&#xff1a; 目录&#xff1a; QnAAssistant/ # 项目目录 │ ├── QnAAssistant/ # 项目文件夹 │ ├── init.py # 空文件 │ ├── settings.py # 项目配…

Python的3D可视化库 - vedo (2)visual子模块 基本可视化行为

文章目录 1. visual模块的继承关系2. 基类CommonVisual的方法2.1 获取对象信息2.1.1 对象本身信息2.1.2 对象的查找表2.1.3 对象标量范围2.1.4 对象缩略图 2.2 呈现对象2.2.1 在窗口显示1.2.2 对象可见性 2.2.3 对象颜色2.2.4 对象透明度 2.3 添加标度条2.3.1 2D标度条2.3.2 3D…

Mysql案例之COALESCE函数使用详解

hello&#xff0c;大家好&#xff0c;我是灰小猿&#xff01;最近在做一个三表关联查询的场景处理时&#xff0c;遇到了一个比较有用的MySQL函数&#xff0c;在这里记录一下&#xff0c;大概场景如下&#xff1a; 需求场景 场景&#xff1a;有一张object_rel表&#xff0c;表中…

机器学习—迁移学习:使用其他任务中的数据

对于一个没有那么多数据的应用程序&#xff0c;迁移学习是一种奇妙的技术&#xff0c;它允许你使用来自不同任务的数据来帮助你的应用程序&#xff0c;迁移学习是如何工作的&#xff1f; 以下是迁移学习的工作原理&#xff0c;假设你想识别手写的数字0到9&#xff0c;但是你没…

寻找用户推荐人(考点:ifnull)【SQL+Pandas】

今天尝试刷一下力扣的sql面试题&#xff0c;这个写法我也是第一次见 题目是 我们需要在这个表中查出referee_id&#xff01;2的 正确写法是 select name from customer where ifnull(referee_id,0) ! 2 -- 不等于还可以这么写&#xff1a;<>

【软件开发】如何理解异地多活?

本文转载自https://mp.weixin.qq.com/s/T6mMDdtTfBuIiEowCpqu6Q,对文章部分内容做二次修改。 文章目录 01 系统可用性02 单机架构03 主从副本04 风险不可控05 同城灾备06 同城双活07 两地三中心08 伪异地双活09 真正的异地双活10 如何实施异地双活11 异地多活总结在软件开发领…