前提:需要将手机端与手表端分别部署在两个项目上,因为DevEco Studio 同一项目无法同时运行两种设备类型的模拟器,部署第二个会覆盖第一个。

一、项目架构

项目 设备类型 职责
A wearable(手表端) TCP客户端,发送请求
B phone(手机端) TCP服务端,接收并处理请求

二、关键配置

1.权限配置(项目根目录\entry\src\main\module.json5)

  • 手表端:ohos.permission.INTERNETohos.permission.DISTRIBUTED_DATASYNC
  • 手机端:ohos.permission.INTERNET
"requestPermissions": [
  {
    "name": "ohos.permission.INTERNET",
    "reason": "$string:internet_reason",
    "usedScene": {
      "abilities": ["EntryAbility"],
      "when": "inuse"
    }
  },
{
        "name": "ohos.permission.DISTRIBUTED_DATASYNC",  //仅手表端
        "reason": "$string:distributed_reason",
        "usedScene": {
          "abilities": ["EntryAbility", "WearableAbility"],
          "when": "inuse"
        }
      }
]

其中,"$string:internet_reason"和"$string:distributed_reason"需要分别在各自项目根目录\entry\src\main\resources\base\element\string.json中进行配置:

{
  "name": "internet_reason",
  "value": "用于与服务器通信,获取设备状态和发送控制指令"
},
{
  "name": "distributed_reason",
  "value": "分布式权限"
}

2.模拟器代理配置

启动模拟器后右边栏有三根线,点击可以进行“网络”配置

  • 手表端模拟器网络代理必须选"无代理"
  • 若设置手工代理会导致所有 HTTP 请求走代理服务器,代理不存在则全部失败

三、核心代码

手机端:TCP 服务器

项目根目录\entry\src\main\ets\pages\Index.ets

this.tcpServer = socket.constructTCPSocketServerInstance();
await this.tcpServer.listen({ address: '0.0.0.0', port: 8888, family: 1 });
this.tcpServer.on('connect', (connection) => {
  connection.on('message', (recv) => { /* 处理消息 */ });
});

手表端:TCP 客户端

项目根目录\entry\src\main\ets\common\TCPuser.ets:

this.tcpClient = socket.constructTCPSocketInstance();
const phoneIp = '10.0.2.2';  // 宿主机网关地址
await this.tcpClient.connect({ address: { address: phoneIp, port: 8888, family: 1 } });
await this.tcpClient.send({ data });

项目根目录\entry\src\main\ets\pages\Index.ets需要调用TCPuser:

TCPuser()

其中,宿主机网关地址是如何得到的呢?终端执行:

hdc list targets          # 查看模拟器设备对应端口

【注:若提示无法识别hdc指令,报错如下图所示,则是因为hdc没有事先配置在环境变量当中,可以看“附录一”先进行hdc配置】

如下图所示,执行指令得到的127.0.0.1:5555/5557分别是手表模拟器、手机模拟器对应的hdc调试端口(主机用来连接模拟器进行部署、日志等操作)

然后在终端执行命令:

 hdc -t 127.0.0.1:5557 shell ifconfig  #127.0.0.1:5557是上一条指令查到的手机模拟器调试端口

获得127.0.0.1:5557hdc调试端口对应的手机模拟器的ip地址10.0.2.15

四、端口转发

两个模拟器网络互相隔离,无法直接通信,必须通过主机端口转发桥接

hdc -t 127.0.0.1:5557 fport tcp:8888 tcp:8888

含义:主机 8888 端口 → 手机模拟器(5557) 的 8888 端口

手表模拟器访问 10.0.2.2(模拟器视角的宿主机网关)→ 流量到达主机 8888 → 转发到手机模拟器 8888。

五、IP地址说明

地址 含义
本机地址 主机IP,模拟器内不可用
10.0.2.15 模拟器自身IP
10.0.2.2 模拟器访问宿主机的网关地址
127.0.0.1:5555/5557 hdc调试端口,与TCP8888无关

六、完整启动流程

  1. 启动手机模拟器 + 手表模拟器
  2. 部署手机端项目 → 手机模拟器
  3. 部署手表端项目 → 手表模拟器
  4. 执行 hdc -t 127.0.0.1:5557 fport tcp:8888 tcp:8888
  5. 手表端发送消息 → 手机端收到消息

七、示意代码

注:需要预先按照“二、关键配置”配置好module.json5

说明:以下示意代码省略了我项目中的一些页面代码,所以直接复制粘贴是无法运行的,仅供参考,了解如何进行使用

手表端  项目根目录\entry\src\main\ets\common\TCPuser.ets(然后在pages\Index.ets中调用)

// products/wearable/src/main/ets/common/VoiceAssistant/VoiceAssistant.ets
import promptAction from '@ohos.promptAction';
import socket from '@ohos.net.socket';

@Component
export struct VoiceAssistant {
  @State offsetX: number = 0;
  @State offsetY: number = 0;
  private startX: number = 0;
  private startY: number = 0;
  private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.All });
  private tcpClient: socket.TCPSocket | null = null;
  private isConnected: boolean = false;

  aboutToAppear() {
    this.loadOffset();
    this.connectToPhone();
  }

  async connectToPhone() {
    try {
      this.tcpClient = socket.constructTCPSocketInstance();
      // 通过 hdc fport 转发后,连接宿主机网关地址
      const phoneIp = '10.0.2.2';
      await this.tcpClient.connect({ address: { address: phoneIp, port: 8888, family: 1 } });
      this.isConnected = true;
      console.info('已连接手机');
    } catch (err) {
      console.error('连接失败', JSON.stringify(err));
      promptAction.showToast({ message: '连接手机失败' });
    }
  }

  async sendHello() {
    if (!this.isConnected) {
      promptAction.showToast({ message: '未连接手机' });
      return;
    }
    const data = this.stringToArrayBuffer('Hello World');
    try {
      await this.tcpClient!.send({ data });
      promptAction.showToast({ message: '已发送 Hello World' });
    } catch (err) {
      promptAction.showToast({ message: '发送失败' });
    }
  }

  stringToArrayBuffer(str: string): ArrayBuffer {
    const buf = new ArrayBuffer(str.length);
    const view = new Uint8Array(buf);
    for (let i = 0; i < str.length; i++) {
      view[i] = str.charCodeAt(i);
    }
    return buf;
  }

  // ... 拖拽逻辑保持不变 ...
  loadOffset() { /* 原有代码 */ }
  saveOffset() { /* 原有代码 */ }

  build() {
    Column() {
      Text('🐱')
        .fontSize(48)
    }
    .width(70)
    .height(70)
    .backgroundColor('#4A90E2')
    .borderRadius(35)
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
    .shadow({ radius: 8, color: '#00000040' })
    .offset({ x: this.offsetX, y: this.offsetY })
    .gesture(
      PanGesture(this.panOption)
        .onActionStart((event: GestureEvent) => {
          this.startX = event.offsetX;
          this.startY = event.offsetY;
        })
        .onActionUpdate((event: GestureEvent) => {
          this.offsetX = this.offsetX + (event.offsetX - this.startX);
          this.offsetY = this.offsetY + (event.offsetY - this.startY);
          this.startX = event.offsetX;
          this.startY = event.offsetY;
        })
        .onActionEnd(() => {
          this.saveOffset();
        })
    )
    .onClick(() => {
      this.sendHello(); // 点击小猫直接发送 Hello World
    })
  }
}

手机端  项目根目录\entry\src\main\ets\pages\Index.ets

import socket from '@ohos.net.socket';
import promptAction from '@ohos.promptAction';

interface MessageEvent {
  message: ArrayBuffer;
}

@Entry
@Component
struct Index {
  private tcpServer: socket.TCPSocketServer | null = null;
  @State log: string = '服务端未启动';

  aboutToAppear() {
    this.startServer();
  }

  async startServer() {
    try {
      this.tcpServer = socket.constructTCPSocketServerInstance();
      await this.tcpServer.listen({ address: '0.0.0.0', port: 8888, family: 1 });
      this.log = '服务端已启动,监听8888端口';

      this.tcpServer.on('connect', (connection: socket.TCPSocketConnection) => {
        this.log += `\n客户端已连接`;
        connection.on('message', (recv: MessageEvent) => {
          const msg = this.arrayBufferToString(recv.message);
          this.log += `\n收到: ${msg}`;
          promptAction.showToast({ message: `收到: ${msg}` });
        });
      });
    } catch (err) {
      this.log = `启动失败: ${JSON.stringify(err)}`;
      console.error('服务端错误', err);
    }
  }

  arrayBufferToString(buf: ArrayBuffer): string {
    return String.fromCharCode(...new Uint8Array(buf));
  }

  build() {
    Column() {
      Text('手机服务端').fontSize(24).margin(20);
      Scroll() {
        Text(this.log).fontSize(16).padding(10).width('100%').textAlign(TextAlign.Start);
      }
      .height('80%')
      .backgroundColor('#F5F5F5')
      .margin(10)
    }
    .width('100%')
    .height('100%')
  }
}

手表端发送Hello World

手机端接收

附录一、配置hdc环境变量

1. 找到 hdc.exe 的路径,通常在 DevEco Studio 安装目录下(默认安装在C盘):

比如我的hdc放在E:\Huawei\DevEco Studio\sdk\default\openharmony\toolchains\

2.将该路径添加到系统环境变量 PATH 中。

注:环境变量修改后需要重启终端才能生效,不需要重启计算机

3.重新打开终端,即可使用 hdc 命令。


 

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐