开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端

  文章的目的为了记录使用Arkts 进行Harmony app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。

 相关链接:

开源 Arkts 鸿蒙应用 开发(一)工程文件分析-优快云博客

开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-优快云博客

开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-优快云博客

开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-优快云博客

开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-优快云博客

开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-优快云博客

开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-优快云博客

开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-优快云博客

开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-优快云博客

开源 Arkts 鸿蒙应用 开发(十)通讯--Http数据传输-优快云博客

 推荐链接:

开源 java android app 开发(一)开发环境的搭建-优快云博客

开源 java android app 开发(二)工程文件结构-优快云博客

开源 java android app 开发(三)GUI界面布局和常用组件-优快云博客

开源 java android app 开发(四)GUI界面重要组件-优快云博客

开源 java android app 开发(五)文件和数据库存储-优快云博客

开源 java android app 开发(六)多媒体使用-优快云博客

开源 java android app 开发(七)通讯之Tcp和Http-优快云博客

开源 java android app 开发(八)通讯之Mqtt和Ble-优快云博客

开源 java android app 开发(九)后台之线程和服务-优快云博客

开源 java android app 开发(十)广播机制-优快云博客

开源 java android app 开发(十一)调试、发布-优快云博客

开源 java android app 开发(十二)封库.aar-优快云博客

推荐链接:

开源C# .net mvc 开发(一)WEB搭建_c#部署web程序-优快云博客

开源 C# .net mvc 开发(二)网站快速搭建_c#网站开发-优快云博客

开源 C# .net mvc 开发(三)WEB内外网访问(VS发布、IIS配置网站、花生壳外网穿刺访问)_c# mvc 域名下不可訪問內網,內網下可以訪問域名-优快云博客

开源 C# .net mvc 开发(四)工程结构、页面提交以及显示_c#工程结构-优快云博客

开源 C# .net mvc 开发(五)常用代码快速开发_c# mvc开发-优快云博客

本章内容主要是演示鸿蒙的tcp客户端的通讯:

实现TCP客户端功能,可以连接指定IP和端口的服务器。支持发送和接收文本消息,显示连接状态和通信历史。

新建DevEco的工程,选择Empty Ability,只需要修改module.json5,和Index.ets文件,就可以实现TCP客户端的APP。

一、设置权限,修改module.json5文件。

文件位置

module.json5代码

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone"
    ],
    "deliveryWithInstall": true,
    "installationFree": false,
    "pages": "$profile:main_pages",
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:layered_image",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:startIcon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        "skills": [
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ],
    "extensionAbilities": [
      {
        "name": "EntryBackupAbility",
        "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
        "type": "backup",
        "exported": false,
        "metadata": [
          {
            "name": "ohos.extension.backup",
            "resource": "$profile:backup_config"
          }
        ],
      }
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      }
    ]
  }
}

二、Index.ets的代码分析

2.1  初始化

当TcpClientPage组件被加载时,首先会初始化所有@State装饰的变量(serverIp, serverPort, message, receivedData, isConnected)

创建TCP套接字实例tcpSocket和获取UI上下文context

aboutToAppear生命周期:

再次创建TCP套接字实例(这里重复创建了,可以优化)

设置TCP连接选项(address, port, family, timeout)

注册三个事件监听器:

message:接收服务器数据时触发

close:连接关闭时触发

error:发生错误时触发

以下为代码

// 初始化TCP套接字
  aboutToAppear() {
    this.tcpSocket = socket.constructTCPSocketInstance();

    // 设置TCP连接参数
    let tcpOptions: socket.TCPConnectOptions = {
      address: {
        address: this.serverIp,
        port: parseInt(this.serverPort),
        family: 1 // 1表示IPv4
      },
      timeout: 5000 // 连接超时时间5秒
    };

    // 监听接收数据
    this.tcpSocket.on('message', (messageInfo: socket.SocketMessageInfo) => {
      console.info('Received message');

      // 从 SocketMessageInfo 中获取 ArrayBuffer
      const buffer = messageInfo.message; // message 是 ArrayBuffer 类型
      const data = this.arrayBufferToString(buffer);

      this.receivedData += `[接收] ${new Date().toLocaleTimeString()}: ${data}\n`;
    });

    // 监听连接关闭
    this.tcpSocket.on('close', () => {
      console.info('Connection closed');
      this.isConnected = false;
      promptAction.showToast({ message: '连接已关闭', duration: 2000 });
    });

    // 监听错误事件
    this.tcpSocket.on('error', (err) => {
      console.error('Socket error: ' + JSON.stringify(err));
      promptAction.showToast({ message: '发生错误: ' + JSON.stringify(err), duration: 3000 });
      this.isConnected = false;
    });
  }

2.2  UI代码

build()方法构建了以下UI元素:

服务器IP输入框

端口号输入框

连接/断开按钮(根据连接状态改变文字和颜色)

消息输入框

发送按钮

消息显示区域(带滚动条)

以下为代码

  build() {
    Column({ space: 10 }) {
      // 标题
      Text('TCP客户端')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 服务器地址输入
      Row({ space: 10 }) {
        Text('服务器IP:')
          .fontSize(16)
          .width(80)
        TextInput({ text: this.serverIp })
          .width('60%')
          .onChange((value: string) => {
            this.serverIp = value;
          })
      }.width('100%')
      .justifyContent(FlexAlign.Start)

      // 端口号输入
      Row({ space: 10 }) {
        Text('端口号:')
          .fontSize(16)
          .width(80)
        TextInput({ text: this.serverPort })
          .width('60%')
          .onChange((value: string) => {
            this.serverPort = value;
          })
      }.width('100%')
      .justifyContent(FlexAlign.Start)

      // 连接按钮
      Button(this.isConnected ? '断开连接' : '连接')
        .width('80%')
        .height(40)
        .backgroundColor(this.isConnected ? '#ff4d4f' : '#1890ff')
        .onClick(() => {
          this.toggleConnection();
        })

      // 消息输入框
      TextInput({ placeholder: '输入要发送的消息', text: this.message })
        .width('90%')
        .height(60)
        .margin({ top: 20 })
        .onChange((value: string) => {
          this.message = value;
        })

      // 发送按钮
      Button('发送')
        .width('80%')
        .height(40)
        .margin({ top: 10 })
        .onClick(() => {
          this.sendMessage();
        })

      // 消息显示区域
      Scroll() {
        Text(this.receivedData)
          .width('90%')
          .fontSize(14)
          .textAlign(TextAlign.Start)
          .padding(10)
          .backgroundColor('#f5f5f5')
      }
      .width('100%')
      .height(200)
      .margin({ top: 20 })
      .border({ width: 1, color: '#d9d9d9' })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Start)
  }

2.3  建立连接和发送

调用tcpSocket.connect()建立连接

点击"发送"按钮:

触发sendMessage()方法

以下为代码

// 连接到服务器
  private async connect() {
    try {
      let tcpOptions: socket.TCPConnectOptions = {
        address: {
          address: this.serverIp,
          port: parseInt(this.serverPort),
          family: 1
        },
        timeout: 5000
      };

      await this.tcpSocket.connect(tcpOptions);
      this.isConnected = true;
      promptAction.showToast({ message: '连接成功', duration: 2000 });

      // 开始接收数据
      this.tcpSocket.getState().then(state => {
        console.info('Socket state: ' + JSON.stringify(state));
      });
    } catch (err) {
      console.error('Connect failed: ' + JSON.stringify(err));
      promptAction.showToast({ message: '连接失败: ' + JSON.stringify(err), duration: 3000 });
      this.isConnected = false;
    }
  }

2.4  所有代码

// MainAbility.ets
import socket from '@ohos.net.socket';
import common from '@ohos.app.ability.common';
import promptAction from '@ohos.promptAction';
import { BusinessError } from '@kit.BasicServicesKit';

@Entry
@Component
struct TcpClientPage {
  @State serverIp: string = '192.168.3.146'; // 默认服务器IP
  @State serverPort: string = '1000'; // 默认端口
  @State message: string = ''; // 要发送的消息
  @State receivedData: string = ''; // 接收到的数据
  @State isConnected: boolean = false; // 连接状态

  private tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance();
  private context = getContext(this) as common.UIAbilityContext;

  // 初始化TCP套接字
  aboutToAppear() {
    this.tcpSocket = socket.constructTCPSocketInstance();

    // 设置TCP连接参数
    let tcpOptions: socket.TCPConnectOptions = {
      address: {
        address: this.serverIp,
        port: parseInt(this.serverPort),
        family: 1 // 1表示IPv4
      },
      timeout: 5000 // 连接超时时间5秒
    };

    // 监听接收数据
    this.tcpSocket.on('message', (messageInfo: socket.SocketMessageInfo) => {
      console.info('Received message');

      // 从 SocketMessageInfo 中获取 ArrayBuffer
      const buffer = messageInfo.message; // message 是 ArrayBuffer 类型
      const data = this.arrayBufferToString(buffer);

      this.receivedData += `[接收] ${new Date().toLocaleTimeString()}: ${data}\n`;
    });

    // 监听连接关闭
    this.tcpSocket.on('close', () => {
      console.info('Connection closed');
      this.isConnected = false;
      promptAction.showToast({ message: '连接已关闭', duration: 2000 });
    });

    // 监听错误事件
    this.tcpSocket.on('error', (err) => {
      console.error('Socket error: ' + JSON.stringify(err));
      promptAction.showToast({ message: '发生错误: ' + JSON.stringify(err), duration: 3000 });
      this.isConnected = false;
    });
  }

  private arrayBufferToString(buffer: ArrayBuffer): string {
    const uint8Array = new Uint8Array(buffer);
    let str = '';
    for (let i = 0; i < uint8Array.length; i++) {
      str += String.fromCharCode(uint8Array[i]);
    }
    return str;
  }

  // 字符串转ArrayBuffer
  private stringToArrayBuffer(str: string): ArrayBuffer {
    let buf = new ArrayBuffer(str.length);
    let bufView = new Uint8Array(buf);
    for (let i = 0; i < str.length; i++) {
      bufView[i] = str.charCodeAt(i);
    }
    return buf;
  }

  // 连接/断开服务器
  private toggleConnection() {
    if (this.isConnected) {
      this.disconnect();
    } else {
      this.connect();
    }
  }

  // 连接到服务器
  private async connect() {
    try {
      let tcpOptions: socket.TCPConnectOptions = {
        address: {
          address: this.serverIp,
          port: parseInt(this.serverPort),
          family: 1
        },
        timeout: 5000
      };

      await this.tcpSocket.connect(tcpOptions);
      this.isConnected = true;
      promptAction.showToast({ message: '连接成功', duration: 2000 });

      // 开始接收数据
      this.tcpSocket.getState().then(state => {
        console.info('Socket state: ' + JSON.stringify(state));
      });
    } catch (err) {
      console.error('Connect failed: ' + JSON.stringify(err));
      promptAction.showToast({ message: '连接失败: ' + JSON.stringify(err), duration: 3000 });
      this.isConnected = false;
    }
  }

  // 断开连接
  private disconnect() {
    try {
      this.tcpSocket.close();
      this.isConnected = false;
      promptAction.showToast({ message: '已断开连接', duration: 2000 });
    } catch (err) {
      console.error('Disconnect failed: ' + JSON.stringify(err));
      promptAction.showToast({ message: '断开连接失败: ' + JSON.stringify(err), duration: 3000 });
    }
  }

  private sendMessage() {
    if (!this.isConnected) {
      promptAction.showToast({ message: '请先连接到服务器', duration: 2000 });
      return;
    }

    if (!this.message.trim()) {
      promptAction.showToast({ message: '消息不能为空', duration: 2000 });
      return;
    }

    try {
      const buffer = this.stringToArrayBuffer(this.message);
      this.tcpSocket.send({ data: buffer })
        .then(() => {
          this.receivedData += `[发送] ${new Date().toLocaleTimeString()}: ${this.message}\n`;
          this.message = '';
        })
        .catch((err: BusinessError) => {
          console.error(`Send failed: code=${err.code}, message=${err.message}`);
          promptAction.showToast({ message: `发送失败: ${err.message}`, duration: 3000 });
        });
    } catch (err) {
      console.error(`Send error: code=${err.code}, message=${err.message}`);
      promptAction.showToast({ message: `发送错误: ${err.message}`, duration: 3000 });
    }
  }

  build() {
    Column({ space: 10 }) {
      // 标题
      Text('TCP客户端')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 20 })

      // 服务器地址输入
      Row({ space: 10 }) {
        Text('服务器IP:')
          .fontSize(16)
          .width(80)
        TextInput({ text: this.serverIp })
          .width('60%')
          .onChange((value: string) => {
            this.serverIp = value;
          })
      }.width('100%')
      .justifyContent(FlexAlign.Start)

      // 端口号输入
      Row({ space: 10 }) {
        Text('端口号:')
          .fontSize(16)
          .width(80)
        TextInput({ text: this.serverPort })
          .width('60%')
          .onChange((value: string) => {
            this.serverPort = value;
          })
      }.width('100%')
      .justifyContent(FlexAlign.Start)

      // 连接按钮
      Button(this.isConnected ? '断开连接' : '连接')
        .width('80%')
        .height(40)
        .backgroundColor(this.isConnected ? '#ff4d4f' : '#1890ff')
        .onClick(() => {
          this.toggleConnection();
        })

      // 消息输入框
      TextInput({ placeholder: '输入要发送的消息', text: this.message })
        .width('90%')
        .height(60)
        .margin({ top: 20 })
        .onChange((value: string) => {
          this.message = value;
        })

      // 发送按钮
      Button('发送')
        .width('80%')
        .height(40)
        .margin({ top: 10 })
        .onClick(() => {
          this.sendMessage();
        })

      // 消息显示区域
      Scroll() {
        Text(this.receivedData)
          .width('90%')
          .fontSize(14)
          .textAlign(TextAlign.Start)
          .padding(10)
          .backgroundColor('#f5f5f5')
      }
      .width('100%')
      .height(200)
      .margin({ top: 20 })
      .border({ width: 1, color: '#d9d9d9' })
    }
    .width('100%')
    .height('100%')
    .padding(20)
    .justifyContent(FlexAlign.Start)
  }
}

三、效果演示

3.1  首先搭建服务器端,这里使用了网络调试助手,设置ip和端口号,打开。

3.2  APP的效果和设置

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值