HarmonyOS鸿蒙Next模拟器做tcp/IP服务器,我用PAD程序跟它不能连接

HarmonyOS鸿蒙Next模拟器做tcp/IP服务器,我用PAD程序跟它不能连接 用的是鸿蒙API20,一直不能连接到,估计是IP地址问题,现在服务器监看的是“0.0.0.0”,监看“127.0.0.1”也不行,如果用电脑的IP地址“192.168.0.101”初始化就报错。以下是代码。客户端程序连接我用别的开发的电脑程序一切正常,就是跟模拟器的客户端不能成功连接通讯。

// TcpServerPage.ets
import { TcpServerManager, ClientInfo, ReceivedMessage } from '../TcpServerManager';
import { util } from '[@kit](/user/kit).ArkTS';
import { abilityAccessCtrl, Permissions } from '[@kit](/user/kit).AbilityKit';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';

@Entry
@Component
struct TcpServerPage {
  // TCP服务管理器实例
  private serverManager: TcpServerManager = new TcpServerManager();

  // 状态变量
  @State serverStatus: string = '未启动';
  @State serverPort: string = '5798';
  @State isRunning: boolean = false;
  @State clientList: ClientInfo[] = [];
  @State selectedClientId: string = '';
  @State logMessages: string[] = [];
  @State inputMessage: string = '';
  @State broadcastMessage: string = '';

  // 权限列表
  private requiredPermissions: Array<Permissions> = ['ohos.permission.INTERNET'];

  aboutToAppear(): void {
    // 请求权限
    this.requestPermissions();

    // 设置服务端回调
    this.setupServerCallbacks();
  }

  aboutToDisappear(): void {
    // 页面销毁时停止服务
    this.stopServer();
  }

  /**
   * 请求必要权限
   */
  private requestPermissions(): void {
    const atManager = abilityAccessCtrl.createAtManager();
    atManager.requestPermissionsFromUser(getContext(this), this.requiredPermissions)
      .then((data) => {
        this.addLog('权限申请成功');
      })
      .catch((err: BusinessError) => {
        this.addLog(`权限申请失败: ${err.message}`);
      });
  }

  /**
   * 设置服务端事件回调
   */
  private setupServerCallbacks(): void {
    // 客户端连接回调
    this.serverManager.setOnClientConnected((clientInfo: ClientInfo) => {
      this.addLog(`[连接] 客户端 ${clientInfo.id} (${clientInfo.remoteAddress}:${clientInfo.remotePort}) 已连接`);
      this.refreshClientList();
    });

    // 客户端断开回调
    this.serverManager.setOnClientDisconnected((clientId: string) => {
      this.addLog(`[断开] 客户端 ${clientId} 已断开`);
      if (this.selectedClientId === clientId) {
        this.selectedClientId = '';
      }
      this.refreshClientList();
    });

    // 消息接收回调
    this.serverManager.setOnMessageReceived((message: ReceivedMessage) => {
      const text = this.serverManager.arrayBufferToString(message.message);
      this.addLog(`[收到] 来自 ${message.clientId}: ${text}`);
    });

    // 服务端错误回调
    this.serverManager.setOnServerError((error: Error) => {
      this.addLog(`[错误] ${error.message}`);
    });

    // 服务启动回调
    this.serverManager.setOnServerStarted((address: string, port: number) => {
      this.serverStatus = `运行中 - ${address}:${port}`;
      this.isRunning = true;
      this.addLog(`[系统] 服务已启动,监听 ${address}:${port}`);
    });

    // 服务停止回调
    this.serverManager.setOnServerStopped(() => {
      this.serverStatus = '未启动';
      this.isRunning = false;
      this.clientList = [];
      this.selectedClientId = '';
      this.addLog('[系统] 服务已停止');
    });
  }

  /**
   * 刷新客户端列表
   */
  private refreshClientList(): void {
    this.clientList = this.serverManager.getClients();
  }

  /**
   * 添加日志信息
   */
  private addLog(message: string): void {
    const timestamp = new Date().toLocaleTimeString();
    this.logMessages.unshift(`[${timestamp}] ${message}`);
    // 限制日志条数,防止内存溢出
    if (this.logMessages.length > 100) {
      this.logMessages.pop();
    }
  }

  /**
   * 启动服务
   */
  private async startServer(): Promise<void> {
    const port = parseInt(this.serverPort);
    if (isNaN(port) || port < 1 || port > 65535) {
      this.addLog('[错误] 端口号无效,请输入 1-65535 之间的数字');
      return;
    }

    try {
      await this.serverManager.start(port,"0.0.0.0");
    } catch (error) {
      this.addLog(`[错误] 启动失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 停止服务
   */
  private async stopServer(): Promise<void> {
    try {
      await this.serverManager.stop();
    } catch (error) {
      this.addLog(`[错误] 停止失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 发送消息给选中的客户端
   */
  private async sendToSelectedClient(): Promise<void> {
    if (!this.selectedClientId) {
      this.addLog('[提示] 请先选择一个客户端');
      return;
    }
    if (!this.inputMessage.trim()) {
      this.addLog('[提示] 消息内容不能为空');
      return;
    }

    try {
      const data = this.serverManager.stringToArrayBuffer(this.inputMessage);
      await this.serverManager.sendToClient(this.selectedClientId, data);
      this.addLog(`[发送] 向 ${this.selectedClientId}: ${this.inputMessage}`);
      this.inputMessage = '';
    } catch (error) {
      this.addLog(`[错误] 发送失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 广播消息
   */
  private async broadcastToAll(): Promise<void> {
    if (!this.broadcastMessage.trim()) {
      this.addLog('[提示] 广播内容不能为空');
      return;
    }

    try {
      const data = this.serverManager.stringToArrayBuffer(this.broadcastMessage);
      const count = await this.serverManager.broadcast(data);
      this.addLog(`[广播] 已向 ${count} 个客户端发送: ${this.broadcastMessage}`);
      this.broadcastMessage = '';
    } catch (error) {
      this.addLog(`[错误] 广播失败: ${JSON.stringify(error)}`);
    }
  }

  /**
   * 断开指定客户端
   */
  private disconnectClient(clientId: string): void {
    this.serverManager.disconnectClient(clientId);
    this.addLog(`[操作] 主动断开客户端 ${clientId}`);
    this.refreshClientList();
    if (this.selectedClientId === clientId) {
      this.selectedClientId = '';
    }
  }

  build() {
    Column() {
      // 标题栏
      Text('TCP 服务端')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .padding({ top: 20, bottom: 10, left: 16, right: 16 })
        .textAlign(TextAlign.Center)

      // 服务控制区
      Column() {
        Row() {
          Text('端口:')
            .fontSize(16)
            .width(60)
          TextInput({ text: this.serverPort, placeholder: '端口号' })
            .type(InputType.Number)
            .fontSize(16)
            .layoutWeight(1)
            .onChange((value: string) => {
              this.serverPort = value;
            })
            .enabled(!this.isRunning)
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 10 })

        Row() {
          Text(`状态: ${this.serverStatus}`)
            .fontSize(14)
            .fontColor(this.isRunning ? '#00AA00' : '#666666')
            .layoutWeight(1)

          if (!this.isRunning) {
            Button('启动服务')
              .fontSize(14)
              .onClick(() => this.startServer())
          } else {
            Button('停止服务')
              .fontSize(14)
              .backgroundColor('#FF4444')
              .onClick(() => this.stopServer())
          }
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 10 })
        .justifyContent(FlexAlign.SpaceBetween)
      }
      .width('100%')
      .padding({ bottom: 10 })
      .backgroundColor('#F1F3F5')
      .borderRadius(8)
      .margin({ left: 16, right: 16, bottom: 10 })

      // 客户端列表
      Column() {
        Row() {
          Text('已连接客户端')
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
          Text(`(${this.clientList.length})`)
            .fontSize(14)
            .fontColor('#666666')
            .margin({ left: 5 })
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 10, bottom: 5 })

        if (this.clientList.length === 0) {
          Text('暂无客户端连接')
            .fontSize(14)
            .fontColor('#999999')
            .width('100%')
            .textAlign(TextAlign.Center)
            .padding({ top: 20, bottom: 20 })
        } else {
          List() {
            ForEach(this.clientList, (client: ClientInfo) => {
              ListItem() {
                Row() {
                  Column() {
                    Text(client.id)
                      .fontSize(14)
                      .fontWeight(FontWeight.Medium)
                      .maxLines(1)
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                    Text(`${client.remoteAddress}:${client.remotePort}`)
                      .fontSize(12)
                      .fontColor('#666666')
                  }
                  .alignItems(HorizontalAlign.Start)
                  .layoutWeight(1)

                  Button('断开')
                    .fontSize(12)
                    .height(30)
                    .backgroundColor('#FF8888')
                    .onClick(() => this.disconnectClient(client.id))
                }
                .width('100%')
                .padding(10)
                .backgroundColor(this.selectedClientId === client.id ? '#E3F2FD' : '#FFFFFF')
                .borderRadius(4)
                .onClick(() => {
                  this.selectedClientId = client.id;
                })
              }
              .margin({ bottom: 5 })
            })
          }
          .width('100%')
          .height(200)
          .padding({ left: 16, right: 16 })
        }
      }
      .width('100%')
      .margin({ left: 16, right: 16, bottom: 10 })
      .backgroundColor('#F1F3F5')
      .borderRadius(8)

      // 消息发送区
      Column() {
        Text('发送消息')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .width('100%')
          .padding({ left: 16, right: 16, top: 10, bottom: 5 })

        // 定向发送
        Row() {
          Text('目标:')
            .fontSize(14)
            .width(50)
          Text(this.selectedClientId || '未选择')
            .fontSize(14)
            .fontColor(this.selectedClientId ? '#333333' : '#999999')
            .layoutWeight(1)
            .maxLines(1)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 5 })

        Row() {
          TextInput({ text: this.inputMessage, placeholder: '输入消息内容' })
            .fontSize(14)
            .layoutWeight(1)
            .onChange((value: string) => {
              this.inputMessage = value;
            })
            .enabled(this.isRunning && !!this.selectedClientId)

          Button('发送')
            .fontSize(14)
            .margin({ left: 10 })
            .enabled(this.isRunning && !!this.selectedClientId)
            .onClick(() => this.sendToSelectedClient())
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 10 })

        // 广播发送
        Row() {
          TextInput({ text: this.broadcastMessage, placeholder: '广播消息内容' })
            .fontSize(14)
            .layoutWeight(1)
            .onChange((value: string) => {
              this.broadcastMessage = value;
            })
            .enabled(this.isRunning && this.clientList.length > 0)

          Button('广播')
            .fontSize(14)
            .margin({ left: 10 })
            .enabled(this.isRunning && this.clientList.length > 0)
            .onClick(() => this.broadcastToAll())
        }
        .width('100%')
        .padding({ left: 16, right: 16, bottom: 10 })
      }
      .width('100%')
      .margin({ left: 16, right: 16, bottom: 10 })
      .backgroundColor('#F1F3F5')
      .borderRadius(8)

      // 日志区域
      Column() {
        Row() {
          Text('消息日志')
            .fontSize(16)
            .fontWeight(FontWeight.Medium)
          Blank()
          Button('清空')
            .fontSize(12)
            .height(28)
            .onClick(() => {
              this.logMessages = [];
            })
        }
        .width('100%')
        .padding({ left: 16, right: 16, top: 10, bottom: 5 })

        List() {
          ForEach(this.logMessages, (log: string) => {
            ListItem() {
              Text(log)
                .fontSize(12)
                .fontColor('#333333')
                .padding({ left: 10, right: 10, top: 5, bottom: 5 })
                .width('100%')
            }
          })
        }
        .width('100%')
        .layoutWeight(1)
        .padding({ left: 16, right: 16, bottom: 10 })
      }
      .width('100%')
      .layoutWeight(1)
      .margin({ left: 16, right: 16, bottom: 16 })
      .backgroundColor('#F1F3F5')
      .borderRadius(8)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
  }
}

TcpServerManager.ets

import { socket } from '[@kit](/user/kit).NetworkKit';
import { BusinessError } from '[@kit](/user/kit).BasicServicesKit';
import { util } from '[@kit](/user/kit).ArkTS';

export interface ClientInfo {
  id: string;
  connection: socket.TCPSocketConnection;
  remoteAddress: string;
  remotePort: number;
  connectTime: number;
}

export interface ReceivedMessage {
  clientId: string;
  message: ArrayBuffer;
  timestamp: number;
}

export class TcpServerManager {
  private server: socket.TCPSocketServer | null = null;
  private clients: Map<string, ClientInfo> = new Map();
  private isRunning: boolean = false;
  private serverPort: number = 0;
  private serverAddress: string = '0.0.0.0';

  private onClientConnectedCallback?: (clientInfo: ClientInfo) => void;
  private onClientDisconnectedCallback?: (clientId: string) => void;
  private onMessageReceivedCallback?: (message: ReceivedMessage) => void;
  private onServerErrorCallback?: (error: Error) => void;
  private onServerStartedCallback?: (address: string, port: number) => void;
  private onServerStoppedCallback?: () => void;

  private generateClientId(address: string, port: number): string {
    return `${address}:${port}_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
  }

  arrayBufferToString(buffer: ArrayBuffer): string {
    const decoder: util.TextDecoder = new util.TextDecoder('utf-8');
    const uint8Array: Uint8Array = new Uint8Array(buffer);
    return decoder.decodeWithStream(uint8Array, { stream: false });
  }

  stringToArrayBuffer(str: string): ArrayBuffer {
    const encoder: util.TextEncoder = new util.TextEncoder();
    return encoder.encodeInto(str).buffer as ArrayBuffer;
  }

  async start(port: number, address: string = '0.0.0.0'): Promise<void> {
    if (this.isRunning) {
      console.warn('服务器已在运行中');
      return;
    }

    try {
      this.serverPort = port;
      this.serverAddress = address;
      this.server = socket.constructTCPSocketServerInstance();

      // ✅ 关键修正:回调函数使用 async
      this.server.on('connect', async (connection: socket.TCPSocketConnection) => {
        await this.handleClientConnection(connection);
      });

      this.server.on('error', (err: BusinessError) => {
        console.error('服务端错误', JSON.stringify(err));
        this.onServerErrorCallback?.(new Error(err.message));
      });

      const netAddress: socket.NetAddress = {
        address: this.serverAddress,
        port: this.serverPort,
        family: 1
      };

      await this.server.listen(netAddress);
      this.isRunning = true;
      console.info(`服务器已启动 ${this.serverAddress}:${this.serverPort}`);
      this.onServerStartedCallback?.(this.serverAddress, this.serverPort);

    } catch (error) {
      console.error('启动服务器失败', JSON.stringify(error));
      throw new Error(`启动失败: ${JSON.stringify(error)}`);
    }
  }

  private async handleClientConnection(connection: socket.TCPSocketConnection): Promise<void> {
    try {
      // ✅ 使用 await 获取异步结果
      const remoteAddress: socket.NetAddress = await connection.getRemoteAddress();
      // 端口校验
      console.error('获取客户端端口失败,连接将被拒绝5');
      if (remoteAddress.port === undefined) {
        console.error('获取客户端端口失败,连接将被拒绝');
        connection.close();

更多关于HarmonyOS鸿蒙Next模拟器做tcp/IP服务器,我用PAD程序跟它不能连接的实战教程也可以访问 https://www.itying.com/category-93-b0.html

10 回复

好的,感谢您的反馈。若后续还有其他问题,欢迎您的提问。

更多关于HarmonyOS鸿蒙Next模拟器做tcp/IP服务器,我用PAD程序跟它不能连接的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


开发者您好,
本地测试手机A,安装上述代码打包生成的应用,手机B安装tcp测试软件,测试可以正常连接。绑定地址为0.0.0.0:5798,权限已配置ohos.permission.INTERNET,手机A(API20)
本地通过两个模拟器测试,通过hdc端口映射,发现也可以正常连接,排除了代码问题。

排查方案:

  • 查看PAD连接的ip是否是手机A的IP
  • 电脑(同样连该 Wi-Fi)分别 ping 手机和 PAD,ping 不通手机,说明手机开启了防火墙或路由器有隔离。或者手机和PAD互相ping试下。
  • 尝试更换一个不常用的端口
  • 关闭手机代理(如果开启的话)
  • PAD连接是拒绝连接还是连接超时
  • 手机和PAD连接另一个手机热点测试
  • 确保两台设备的 IP 网段一致。有些路由器或多频合一(2.4G/5G)路由器,会将设备分配到不同的虚拟网段,导致看似在一个 Wi-Fi 下其实无法直接寻址。

谢谢您的回复,我这里这个代码还是不通,但我用的另一段基本相同的代码又通了,搞得比较糊涂,后面如果有问题再请教。再次感谢。

开发者您好,
模拟器访问互联网实际上利用的是本地计算机的以太网或者WLAN,与本地计算机共享同一网络资源。模拟器运行在宿主机的虚拟网络中,对外部是不可见的。

  1. 可以先使用hdc映射主机到模拟器的端口:hdc -t 127.0.0.1:5555 fport tcp:5798 tcp:5798
  2. 使用hdc -t 127.0.0.1:5555 fport ls查看是否创建成功
  3. 用 Windows 自带的网络工具把真实 IP 的流量转给回环地址:netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=5798 connectaddress=127.0.0.1 connectport=5798
  4. netstat -ano | findstr :5798查看应该只有两个结果
  5. 发起请求可以正常连接。

谢谢您的回复,我担心模拟器设置出错,所以特意改成真机测试,发现用“0.0.0.0”或“127.0.0.1”都提示启动监听服务成功(listen),但用真实本地IP地址“192.168.0.101”,提示监听失败。无论是“0.0.0.0”或“127.0.0.1”都无法接收到我另一个安卓PAD发过来的连接请求(这个PAD的连接请求是成熟的以往我自己的程序,可以确认它是没问题的),也就是一直收不到“connect”,所以我还是怀疑代码有问题。目前调试没有任何报错,就是连接不上,辛苦您帮我看一下代码,谢谢!

"ohos.permission.INTERNET"权限也给了

希望HarmonyOS能继续加强在安全性方面的研发,保护用户的隐私和数据安全。

我明白你是把我刚才那段话又发回来了,是想确认步骤对不对,还是照着做了没用/找不到选项?
你直接告诉我这三种情况里哪种:
找不到“开发人员选项”
开发人员里没有“启用5G”
关了还是卡,想别的办法
我按你实际情况给你改对应步骤。

鸿蒙Next模拟器作为TCP/IP服务器时,PAD程序无法连接,通常涉及网络配置问题。请检查模拟器与PAD是否在同一网络段,并确认模拟器防火墙未阻止TCP端口。确保服务器程序正确绑定IP地址(如0.0.0.0)并监听指定端口。PAD客户端需使用模拟器的正确IP和端口进行连接。

根据你的代码和描述,问题很可能出在模拟器的网络环境配置上。HarmonyOS Next模拟器默认使用NAT网络模式,这会影响外部设备(如PAD)对模拟器内服务的访问。

以下是几个关键排查点:

  1. 模拟器网络配置:HarmonyOS模拟器默认使用内部NAT网络,外部设备无法直接访问模拟器内监听的端口。你需要在DevEco Studio中配置模拟器的端口转发。

  2. 端口转发设置

    • 在DevEco Studio中,打开模拟器管理器
    • 选择你的模拟器,点击"Edit"
    • 在"Networking"选项卡中,添加端口转发规则
    • 例如:将主机端口5798转发到模拟器端口5798
  3. 代码中的地址问题

    • 使用"0.0.0.0"是正确的,这表示监听所有网络接口
    • 不要使用"127.0.0.1"或电脑的物理IP,因为模拟器有自己的网络栈
  4. PAD客户端连接地址

    • PAD应该连接你开发电脑的IP地址(192.168.0.101)
    • 端口使用5798(或你在端口转发中设置的主机端口)
  5. 防火墙检查

    • 确保开发电脑的防火墙允许5798端口的入站连接
    • 可以暂时关闭防火墙测试
  6. 代码调试

    • startServer方法中添加更详细的日志,确认服务器是否成功启动
    • 检查是否有权限相关的错误

模拟器网络访问的典型流程是:PAD → 开发电脑IP:端口 → 端口转发 → 模拟器内服务。先配置好端口转发规则,这个问题应该能解决。

回到顶部