HarmonyOS PC开发中Socket TCP编程:传输层网络通信

深入传输层,掌握TCP Socket让你的应用拥有更底层的网络控制能力

一、背景与动机:为什么需要TCP Socket?

HTTP、WebSocket这些应用层协议已经很好用了,为什么还要学习TCP Socket?

性能极致要求:HTTP有大量头信息,WebSocket有帧封装开销。对于追求极致性能的场景(如游戏、实时音视频),直接使用TCP可以减少开销。

自定义协议:某些场景需要自定义二进制协议,HTTP/WebSocket无法满足。比如物联网设备的专有协议、金融交易系统的定长报文。

长连接管理:TCP提供更细粒度的连接控制,可以实现连接池、多路复用等高级特性。

学习底层原理:理解TCP Socket有助于深入理解网络编程,遇到问题时能从底层分析。

鸿蒙系统提供了@ohos.net.socket模块,支持TCP和UDP两种Socket类型。本文聚焦TCP,下一篇讲UDP。

二、核心原理:TCP连接与数据传输

2.1 TCP三次握手与四次挥手

服务器 客户端 服务器 客户端 三次握手建立连接 服务器收到SYN\n进入SYN_RCVD状态 客户端收到SYN+ACK\n进入ESTABLISHED状态 服务器收到ACK\n进入ESTABLISHED状态 数据传输 四次挥手关闭连接 服务器收到FIN\n进入CLOSE_WAIT状态 客户端收到ACK\n进入FIN_WAIT_2状态 客户端收到FIN\n进入TIME_WAIT状态 服务器收到ACK\n进入CLOSED状态 SYN seq=x SYN seq=y, ACK ack=x+1 ACK ack=y+1 Data seq=x+1 ACK ack=x+1+数据长度 Data seq=y+1 ACK ack=y+1+数据长度 FIN seq=m ACK ack=m+1 FIN seq=n ACK ack=n+1

2.2 TCP特性与应用场景

TCP特性 说明 应用场景
面向连接 通信前需建立连接 需要可靠传输的场景
可靠传输 通过确认、重传保证数据到达 文件传输、消息发送
有序传输 数据按发送顺序到达 流式数据处理
流量控制 滑动窗口机制,避免接收方溢出 大数据传输
拥塞控制 慢启动、拥塞避免,公平使用网络 公网通信

2.3 鸿蒙TCP Socket API

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

// 创建TCP Socket
let tcpSocket = socket.constructTCPSocketServerInstance();  // 服务端
let tcpClient = socket.constructTCPSocketInstance();        // 客户端

// 客户端连接
await tcpClient.connect({
  address: {
    address: '192.168.1.100',
    port: 8080,
    family: 1  // IPv4
  },
  timeout: 60000
});

// 发送数据
await tcpClient.send({
  data: 'Hello Server'
});

// 接收数据
tcpClient.on('message', (value) => {
  console.info('收到数据:', value.message);
});

// 关闭连接
await tcpClient.close();

三、代码实战:三种典型场景

场景一:TCP客户端实现

实现一个完整的TCP客户端,连接服务器并进行数据交互。

import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';

// TCP客户端配置
interface TCPClientConfig {
  host: string;
  port: number;
  timeout?: number;
  reconnect?: boolean;
  reconnectInterval?: number;
}

// TCP客户端
class TCPClient {
  private socket: socket.TCPSocket | null = null;
  private config: TCPClientConfig;
  private isConnected: boolean = false;
  
  // 接收数据缓冲区
  private receiveBuffer: ArrayBuffer[] = [];
  
  // 回调
  private onMessage: ((data: ArrayBuffer) => void) | null = null;
  private onConnectionChange: ((connected: boolean) => void) | null = null;
  private onError: ((error: BusinessError) => void) | null = null;
  
  constructor(config: TCPClientConfig) {
    this.config = {
      timeout: 60000,
      reconnect: false,
      reconnectInterval: 5000,
      ...config
    };
  }
  
  // 连接服务器
  async connect(): Promise<boolean> {
    if (this.isConnected) {
      console.warn('已连接');
      return true;
    }
  
    // 创建Socket
    this.socket = socket.constructTCPSocketInstance();
  
    try {
      // 绑定本地地址(可选)
      await this.socket.bind({
        address: '0.0.0.0',  // 自动选择
        port: 0,              // 自动分配端口
        family: 1             // IPv4
      });
    
      // 连接服务器
      await this.socket.connect({
        address: {
          address: this.config.host,
          port: this.config.port,
          family: 1
        },
        timeout: this.config.timeout
      });
    
      this.isConnected = true;
    
      // 设置事件监听
      this.setupListeners();
    
      // 通知连接成功
      this.onConnectionChange?.(true);
    
      console.info(`TCP连接成功: ${this.config.host}:${this.config.port}`);
      return true;
    
    } catch (error) {
      let e = error as BusinessError;
      console.error('TCP连接失败:', e.message);
    
      this.onError?.(e);
    
      // 尝试重连
      if (this.config.reconnect) {
        this.scheduleReconnect();
      }
    
      return false;
    }
  }
  
  // 设置事件监听
  private setupListeners(): void {
    if (!this.socket) return;
  
    // 接收数据
    this.socket.on('message', (value: socket.SocketMessageInfo) => {
      // 数据到达
      let data = value.message;
    
      console.debug(`收到数据: ${data.byteLength} 字节`);
    
      // 通知上层
      this.onMessage?.(data);
    });
  
    // 连接关闭
    this.socket.on('close', () => {
      console.info('连接已关闭');
      this.isConnected = false;
      this.onConnectionChange?.(false);
    
      // 尝试重连
      if (this.config.reconnect) {
        this.scheduleReconnect();
      }
    });
  
    // 错误
    this.socket.on('error', (err: BusinessError) => {
      console.error('Socket错误:', err.message);
      this.isConnected = false;
      this.onError?.(err);
    });
  }
  
  // 发送数据
  async send(data: string | ArrayBuffer): Promise<boolean> {
    if (!this.socket || !this.isConnected) {
      console.error('未连接,无法发送');
      return false;
    }
  
    try {
      let sendData: socket.TCPSendOptions;
    
      if (typeof data === 'string') {
        // 字符串转ArrayBuffer
        let encoder = new TextEncoder();
        let buffer = encoder.encode(data);
        sendData = { data: buffer.buffer };
      } else {
        sendData = { data: data };
      }
    
      await this.socket.send(sendData);
    
      console.debug(`发送数据: ${sendData.data.byteLength} 字节`);
      return true;
    
    } catch (error) {
      console.error('发送失败:', error);
      return false;
    }
  }
  
  // 发送二进制数据(指定偏移和长度)
  async sendBinary(data: ArrayBuffer, offset: number = 0, length?: number): Promise<boolean> {
    if (!this.socket || !this.isConnected) {
      return false;
    }
  
    try {
      let actualLength = length || data.byteLength - offset;
    
      // 创建视图发送部分数据
      let view = new Uint8Array(data, offset, actualLength);
    
      await this.socket.send({
        data: view.buffer
      });
    
      return true;
    
    } catch (error) {
      console.error('发送二进制数据失败:', error);
      return false;
    }
  }
  
  // 获取本地地址
  async getLocalAddress(): Promise<socket.NetAddress | null> {
    if (!this.socket) return null;
  
    try {
      let info = await this.socket.getState();
      return {
        address: info.localAddress,
        port: info.localPort,
        family: 1
      };
    } catch (error) {
      return null;
    }
  }
  
  // 获取远程地址
  async getRemoteAddress(): Promise<socket.NetAddress | null> {
    if (!this.socket) return null;
  
    try {
      let info = await this.socket.getState();
      return {
        address: info.remoteAddress,
        port: info.remotePort,
        family: 1
      };
    } catch (error) {
      return null;
    }
  }
  
  // 关闭连接
  async close(): Promise<void> {
    if (this.socket) {
      try {
        await this.socket.close();
        console.info('连接已关闭');
      } catch (error) {
        console.error('关闭连接失败:', error);
      }
    
      this.socket = null;
      this.isConnected = false;
    }
  }
  
  // 调度重连
  private scheduleReconnect(): void {
    setTimeout(() => {
      console.info('尝试重连...');
      this.connect();
    }, this.config.reconnectInterval);
  }
  
  // 设置回调
  setOnMessage(callback: (data: ArrayBuffer) => void): void {
    this.onMessage = callback;
  }
  
  setOnConnectionChange(callback: (connected: boolean) => void): void {
    this.onConnectionChange = callback;
  }
  
  setOnError(callback: (error: BusinessError) => void): void {
    this.onError = callback;
  }
  
  // 获取连接状态
  isConnected_(): boolean {
    return this.isConnected;
  }
}

// 使用示例:TCP客户端页面
@Entry
@Component
struct TCPClientPage {
  @State connectionStatus: string = '未连接';
  @State receivedData: string = '';
  @State sentCount: number = 0;
  @State receivedCount: number = 0;
  
  private tcpClient: TCPClient | null = null;
  private dataBuffer: string = '';
  
  async aboutToAppear() {
    this.tcpClient = new TCPClient({
      host: '192.168.1.100',
      port: 8888,
      reconnect: true
    });
  
    // 设置回调
    this.tcpClient.setOnConnectionChange((connected) => {
      this.connectionStatus = connected ? '已连接' : '未连接';
    });
  
    this.tcpClient.setOnMessage((data) => {
      // 解析数据
      let decoder = new TextDecoder();
      let text = decoder.decode(data);
    
      this.dataBuffer += text;
      this.receivedCount += data.byteLength;
    
      // 显示最近的数据
      if (this.dataBuffer.length > 1000) {
        this.dataBuffer = this.dataBuffer.slice(-1000);
      }
      this.receivedData = this.dataBuffer;
    });
  
    // 连接服务器
    await this.tcpClient.connect();
  }
  
  async aboutToDisappear() {
    await this.tcpClient?.close();
  }
  
  // 发送测试数据
  async sendTestData() {
    let message = `Test message ${Date.now()}\n`;
    let success = await this.tcpClient?.send(message);
  
    if (success) {
      this.sentCount += message.length;
    }
  }
  
  build() {
    Column() {
      // 连接状态
      Row() {
        Text('连接状态:')
          .fontSize(16)
        Text(this.connectionStatus)
          .fontSize(16)
          .fontColor(this.connectionStatus === '已连接' ? '#7ED321' : '#D0021B')
          .margin({ left: 10 })
      }
      .width('100%')
      .padding(15)
    
      // 统计信息
      Row() {
        Text(`发送: ${this.sentCount} 字节`)
          .fontSize(14)
          .layoutWeight(1)
        Text(`接收: ${this.receivedCount} 字节`)
          .fontSize(14)
          .layoutWeight(1)
      }
      .width('100%')
      .padding(10)
    
      // 接收数据区域
      Column() {
        Text('接收数据:')
          .fontSize(14)
          .width('100%')
      
        Scroll() {
          Text(this.receivedData || '暂无数据')
            .fontSize(12)
            .fontFamily('monospace')
        }
        .width('100%')
        .height(200)
        .backgroundColor('#F5F5F5')
        .padding(10)
      }
      .width('100%')
      .padding(10)
    
      // 操作按钮
      Row() {
        Button('发送测试数据')
          .onClick(() => this.sendTestData())
          .layoutWeight(1)
      
        Button('清空数据')
          .onClick(() => {
            this.dataBuffer = '';
            this.receivedData = '';
          })
          .layoutWeight(1)
          .margin({ left: 10 })
      }
      .width('100%')
      .padding(10)
    }
    .width('100%')
    .height('100%')
  }
}

场景二:TCP服务端实现

实现一个TCP服务器,监听端口并处理客户端连接。

import socket from '@ohos.net.socket';
import { BusinessError } from '@ohos.base';

// 客户端连接信息
interface ClientConnection {
  id: string;
  socket: socket.TCPSocket;
  remoteAddress: string;
  remotePort: number;
  connectTime: number;
}

// TCP服务端配置
interface TCPServerConfig {
  port: number;
  host?: string;
  backlog?: number;  // 等待连接队列长度
}

// TCP服务端
class TCPServer {
  private serverSocket: socket.TCPSocketServer | null = null;
  private config: TCPServerConfig;
  private isListening: boolean = false;
  
  // 客户端连接管理
  private clients: Map<string, ClientConnection> = new Map();
  private clientCounter: number = 0;
  
  // 回调
  private onClientConnect: ((client: ClientConnection) => void) | null = null;
  private onClientDisconnect: ((clientId: string) => void) | null = null;
  private onMessage: ((clientId: string, data: ArrayBuffer) => void) | null = null;
  
  constructor(config: TCPServerConfig) {
    this.config = {
      host: '0.0.0.0',
      backlog: 10,
      ...config
    };
  }
  
  // 启动服务器
  async start(): Promise<boolean> {
    if (this.isListening) {
      console.warn('服务器已启动');
      return true;
    }
  
    // 创建服务端Socket
    this.serverSocket = socket.constructTCPSocketServerInstance();
  
    try {
      // 绑定地址和端口
      await this.serverSocket.bind({
        address: this.config.host,
        port: this.config.port,
        family: 1
      });
    
      // 开始监听
      await this.serverSocket.listen({
        backlog: this.config.backlog
      });
    
      this.isListening = true;
    
      // 设置连接监听
      this.setupListeners();
    
      console.info(`TCP服务器启动: ${this.config.host}:${this.config.port}`);
      return true;
    
    } catch (error) {
      console.error('启动服务器失败:', error);
      return false;
    }
  }
  
  // 设置监听
  private setupListeners(): void {
    if (!this.serverSocket) return;
  
    // 监听客户端连接
    this.serverSocket.on('connect', (client: socket.TCPSocket) => {
      this.handleNewConnection(client);
    });
  }
  
  // 处理新连接
  private async handleNewConnection(client: socket.TCPSocket): Promise<void> {
    try {
      // 获取客户端信息
      let state = await client.getState();
    
      // 生成客户端ID
      let clientId = `client_${++this.clientCounter}`;
    
      let connection: ClientConnection = {
        id: clientId,
        socket: client,
        remoteAddress: state.remoteAddress,
        remotePort: state.remotePort,
        connectTime: Date.now()
      };
    
      // 保存连接
      this.clients.set(clientId, connection);
    
      console.info(`新客户端连接: ${clientId} from ${connection.remoteAddress}:${connection.remotePort}`);
    
      // 设置客户端消息监听
      client.on('message', (value: socket.SocketMessageInfo) => {
        this.onMessage?.(clientId, value.message);
      });
    
      // 设置客户端断开监听
      client.on('close', () => {
        console.info(`客户端断开: ${clientId}`);
        this.clients.delete(clientId);
        this.onClientDisconnect?.(clientId);
      });
    
      client.on('error', (err: BusinessError) => {
        console.error(`客户端错误 ${clientId}:`, err.message);
        this.clients.delete(clientId);
      });
    
      // 通知新连接
      this.onClientConnect?.(connection);
    
    } catch (error) {
      console.error('处理新连接失败:', error);
    }
  }
  
  // 向指定客户端发送数据
  async sendToClient(clientId: string, data: string | ArrayBuffer): Promise<boolean> {
    let client = this.clients.get(clientId);
  
    if (!client) {
      console.error('客户端不存在:', clientId);
      return false;
    }
  
    try {
      let sendData: socket.TCPSendOptions;
    
      if (typeof data === 'string') {
        let encoder = new TextEncoder();
        sendData = { data: encoder.encode(data).buffer };
      } else {
        sendData = { data: data };
      }
    
      await client.socket.send(sendData);
      return true;
    
    } catch (error) {
      console.error('发送失败:', error);
      return false;
    }
  }
  
  // 广播数据给所有客户端
  async broadcast(data: string | ArrayBuffer): Promise<void> {
    let promises: Promise<boolean>[] = [];
  
    this.clients.forEach((client, clientId) => {
      promises.push(this.sendToClient(clientId, data));
    });
  
    await Promise.all(promises);
  }
  
  // 断开指定客户端
  async disconnectClient(clientId: string): Promise<void> {
    let client = this.clients.get(clientId);
  
    if (client) {
      try {
        await client.socket.close();
      } catch (error) {
        // 忽略
      }
    
      this.clients.delete(clientId);
    }
  }
  
  // 停止服务器
  async stop(): Promise<void> {
    // 断开所有客户端
    for (let [clientId, client] of this.clients) {
      try {
        await client.socket.close();
      } catch (error) {
        // 忽略
      }
    }
  
    this.clients.clear();
  
    // 关闭服务端Socket
    if (this.serverSocket) {
      try {
        await this.serverSocket.close();
      } catch (error) {
        // 忽略
      }
    
      this.serverSocket = null;
    }
  
    this.isListening = false;
    console.info('服务器已停止');
  }
  
  // 获取所有客户端
  getConnectedClients(): ClientConnection[] {
    return Array.from(this.clients.values());
  }
  
  // 获取客户端数量
  getClientCount(): number {
    return this.clients.size;
  }
  
  // 设置回调
  setOnClientConnect(callback: (client: ClientConnection) => void): void {
    this.onClientConnect = callback;
  }
  
  setOnClientDisconnect(callback: (clientId: string) => void): void {
    this.onClientDisconnect = callback;
  }
  
  setOnMessage(callback: (clientId: string, data: ArrayBuffer) => void): void {
    this.onMessage = callback;
  }
  
  // 获取监听状态
  isListening_(): boolean {
    return this.isListening;
  }
}

// TCP服务端页面
@Entry
@Component
struct TCPServerPage {
  @State serverStatus: string = '未启动';
  @State clientCount: number = 0;
  @State clients: ClientConnection[] = [];
  @State logs: string[] = [];
  
  private tcpServer: TCPServer | null = null;
  
  async aboutToAppear() {
    this.tcpServer = new TCPServer({
      port: 8888
    });
  
    // 设置回调
    this.tcpServer.setOnClientConnect((client) => {
      this.clients = this.tcpServer?.getConnectedClients() || [];
      this.clientCount = this.clients.length;
      this.addLog(`客户端连接: ${client.remoteAddress}:${client.remotePort}`);
    });
  
    this.tcpServer.setOnClientDisconnect((clientId) => {
      this.clients = this.tcpServer?.getConnectedClients() || [];
      this.clientCount = this.clients.length;
      this.addLog(`客户端断开: ${clientId}`);
    });
  
    this.tcpServer.setOnMessage((clientId, data) => {
      let decoder = new TextDecoder();
      let text = decoder.decode(data);
      this.addLog(`收到消息 [${clientId}]: ${text.substring(0, 50)}`);
    });
  }
  
  async aboutToDisappear() {
    await this.tcpServer?.stop();
  }
  
  // 启动服务器
  async startServer() {
    let success = await this.tcpServer?.start();
    this.serverStatus = success ? '运行中' : '启动失败';
    this.addLog(`服务器${success ? '启动成功' : '启动失败'}`);
  }
  
  // 停止服务器
  async stopServer() {
    await this.tcpServer?.stop();
    this.serverStatus = '已停止';
    this.addLog('服务器已停止');
  }
  
  // 广播消息
  async broadcastMessage() {
    let message = `Server broadcast at ${Date.now()}\n`;
    await this.tcpServer?.broadcast(message);
    this.addLog(`广播消息: ${message.trim()}`);
  }
  
  // 添加日志
  private addLog(log: string) {
    let time = new Date().toLocaleTimeString();
    this.logs.unshift(`[${time}] ${log}`);
    if (this.logs.length > 50) {
      this.logs.pop();
    }
  }
  
  build() {
    Column() {
      // 服务器状态
      Row() {
        Text('服务器状态:')
          .fontSize(16)
        Text(this.serverStatus)
          .fontSize(16)
          .fontColor(this.serverStatus === '运行中' ? '#7ED321' : '#666')
          .margin({ left: 10 })
      
        Blank().layoutWeight(1)
      
        Text(`客户端: ${this.clientCount}`)
          .fontSize(16)
      }
      .width('100%')
      .padding(15)
    
      // 控制按钮
      Row() {
        Button('启动服务器')
          .onClick(() => this.startServer())
          .enabled(this.serverStatus !== '运行中')
          .layoutWeight(1)
      
        Button('停止服务器')
          .onClick(() => this.stopServer())
          .enabled(this.serverStatus === '运行中')
          .layoutWeight(1)
          .margin({ left: 10 })
      
        Button('广播消息')
          .onClick(() => this.broadcastMessage())
          .enabled(this.serverStatus === '运行中')
          .layoutWeight(1)
          .margin({ left: 10 })
      }
      .width('100%')
      .padding(10)
    
      // 客户端列表
      if (this.clients.length > 0) {
        Column() {
          Text('已连接客户端:')
            .fontSize(14)
            .width('100%')
        
          ForEach(this.clients, (client: ClientConnection) => {
            Row() {
              Text(client.id)
                .fontSize(12)
                .layoutWeight(1)
            
              Text(`${client.remoteAddress}:${client.remotePort}`)
                .fontSize(12)
                .fontColor('#666')
            }
            .width('100%')
            .padding(5)
          }, (client: ClientConnection) => client.id)
        }
        .width('100%')
        .padding(10)
      }
    
      // 日志区域
      Column() {
        Text('服务器日志:')
          .fontSize(14)
          .width('100%')
      
        List() {
          ForEach(this.logs, (log: string, index: number) => {
            ListItem() {
              Text(log)
                .fontSize(12)
                .fontFamily('monospace')
            }
          })
        }
        .width('100%')
        .height(200)
        .backgroundColor('#F5F5F5')
      }
      .width('100%')
      .padding(10)
      .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
  }
}

场景三:自定义协议实现

实现一个基于TCP的自定义二进制协议,包含消息头和消息体。

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

// 自定义协议消息头(固定16字节)
// | 魔数(4B) | 版本(1B) | 类型(1B) | 长度(4B) | 序列号(4B) | 保留(2B) |

// 消息类型
enum MessageType {
  HEARTBEAT = 0x01,
  DATA = 0x02,
  ACK = 0x03,
  ERROR = 0x04
}

// 协议常量
const MAGIC_NUMBER = 0x484D5350;  // 'HMSP'
const PROTOCOL_VERSION = 0x01;
const HEADER_SIZE = 16;

// 消息结构
interface ProtocolMessage {
  type: MessageType;
  sequence: number;
  payload: ArrayBuffer;
}

// 协议编解码器
class ProtocolCodec {
  private sequence: number = 0;
  
  // 编码消息
  encode(message: ProtocolMessage): ArrayBuffer {
    // 计算总长度
    let totalLength = HEADER_SIZE + message.payload.byteLength;
    let buffer = new ArrayBuffer(totalLength);
    let view = new DataView(buffer);
  
    // 写入消息头
    let offset = 0;
  
    // 魔数 (4字节)
    view.setUint32(offset, MAGIC_NUMBER, false);
    offset += 4;
  
    // 版本 (1字节)
    view.setUint8(offset, PROTOCOL_VERSION);
    offset += 1;
  
    // 消息类型 (1字节)
    view.setUint8(offset, message.type);
    offset += 1;
  
    // 负载长度 (4字节)
    view.setUint32(offset, message.payload.byteLength, false);
    offset += 4;
  
    // 序列号 (4字节)
    view.setUint32(offset, message.sequence, false);
    offset += 4;
  
    // 保留字段 (2字节)
    view.setUint16(offset, 0, false);
    offset += 2;
  
    // 写入消息体
    let payloadView = new Uint8Array(message.payload);
    let bodyView = new Uint8Array(buffer, HEADER_SIZE);
    for (let i = 0; i < payloadView.length; i++) {
      bodyView[i] = payloadView[i];
    }
  
    return buffer;
  }
  
  // 解码消息
  decode(buffer: ArrayBuffer): ProtocolMessage | null {
    if (buffer.byteLength < HEADER_SIZE) {
      console.error('数据长度不足');
      return null;
    }
  
    let view = new DataView(buffer);
    let offset = 0;
  
    // 读取魔数
    let magic = view.getUint32(offset, false);
    offset += 4;
  
    if (magic !== MAGIC_NUMBER) {
      console.error('魔数不匹配');
      return null;
    }
  
    // 读取版本
    let version = view.getUint8(offset);
    offset += 1;
  
    if (version !== PROTOCOL_VERSION) {
      console.error('协议版本不匹配');
      return null;
    }
  
    // 读取消息类型
    let type = view.getUint8(offset) as MessageType;
    offset += 1;
  
    // 读取负载长度
    let payloadLength = view.getUint32(offset, false);
    offset += 4;
  
    // 读取序列号
    let sequence = view.getUint32(offset, false);
    offset += 4;
  
    // 跳过保留字段
    offset += 2;
  
    // 读取负载
    let payload = new ArrayBuffer(payloadLength);
    if (payloadLength > 0 && buffer.byteLength >= HEADER_SIZE + payloadLength) {
      let payloadView = new Uint8Array(payload);
      let bodyView = new Uint8Array(buffer, HEADER_SIZE, payloadLength);
      for (let i = 0; i < payloadLength; i++) {
        payloadView[i] = bodyView[i];
      }
    }
  
    return {
      type: type,
      sequence: sequence,
      payload: payload
    };
  }
  
  // 创建心跳消息
  createHeartbeat(): ProtocolMessage {
    return {
      type: MessageType.HEARTBEAT,
      sequence: ++this.sequence,
      payload: new ArrayBuffer(0)
    };
  }
  
  // 创建数据消息
  createDataMessage(data: string | ArrayBuffer): ProtocolMessage {
    let payload: ArrayBuffer;
  
    if (typeof data === 'string') {
      let encoder = new TextEncoder();
      payload = encoder.encode(data).buffer;
    } else {
      payload = data;
    }
  
    return {
      type: MessageType.DATA,
      sequence: ++this.sequence,
      payload: payload
    };
  }
  
  // 创建ACK消息
  createAck(sequence: number): ProtocolMessage {
    let payload = new ArrayBuffer(4);
    let view = new DataView(payload);
    view.setUint32(0, sequence, false);
  
    return {
      type: MessageType.ACK,
      sequence: ++this.sequence,
      payload: payload
    };
  }
}

// 基于自定义协议的TCP客户端
class ProtocolTCPClient extends TCPClient {
  private codec: ProtocolCodec = new ProtocolCodec();
  private pendingMessages: Map<number, { resolve: Function; reject: Function; timer: number }> = new Map();
  private ackTimeout: number = 5000;
  
  // 发送数据消息(等待ACK)
  async sendDataWithAck(data: string | ArrayBuffer): Promise<boolean> {
    let message = this.codec.createDataMessage(data);
    let encoded = this.codec.encode(message);
  
    // 设置ACK等待
    return new Promise(async (resolve, reject) => {
      let timer = setTimeout(() => {
        this.pendingMessages.delete(message.sequence);
        reject(new Error('ACK超时'));
      }, this.ackTimeout);
    
      this.pendingMessages.set(message.sequence, {
        resolve: resolve,
        reject: reject,
        timer: timer
      });
    
      // 发送消息
      let success = await this.send(encoded);
    
      if (!success) {
        clearTimeout(timer);
        this.pendingMessages.delete(message.sequence);
        reject(new Error('发送失败'));
      }
    });
  }
  
  // 发送心跳
  async sendHeartbeat(): Promise<boolean> {
    let message = this.codec.createHeartbeat();
    let encoded = this.codec.encode(message);
    return await this.send(encoded);
  }
  
  // 处理接收数据
  protected handleReceivedData(data: ArrayBuffer): void {
    let message = this.codec.decode(data);
  
    if (!message) {
      console.error('消息解码失败');
      return;
    }
  
    switch (message.type) {
      case MessageType.ACK:
        // 处理ACK
        let view = new DataView(message.payload);
        let ackedSequence = view.getUint32(0, false);
      
        let pending = this.pendingMessages.get(ackedSequence);
        if (pending) {
          clearTimeout(pending.timer);
          this.pendingMessages.delete(ackedSequence);
          pending.resolve(true);
        }
        break;
      
      case MessageType.DATA:
        // 处理数据消息
        let decoder = new TextDecoder();
        let text = decoder.decode(message.payload);
        console.info(`收到数据: ${text}`);
      
        // 发送ACK
        this.sendAck(message.sequence);
        break;
      
      case MessageType.HEARTBEAT:
        // 心跳响应
        console.debug('收到心跳');
        break;
    }
  }
  
  // 发送ACK
  private async sendAck(sequence: number): Promise<void> {
    let message = this.codec.createAck(sequence);
    let encoded = this.codec.encode(message);
    await this.send(encoded);
  }
}

四、踩坑与注意事项

坑点一:数据粘包问题

TCP是流式协议,多次发送的数据可能被合并接收。

// ❌ 错误:假设一次发送对应一次接收
tcpClient.on('message', (data) => {
  // 可能收到多个消息的合并数据
  let message = parseMessage(data);  // 解析失败!
});

// ✅ 正确:使用长度前缀或分隔符
// 方案1:长度前缀
function encodeWithLength(data: ArrayBuffer): ArrayBuffer {
  let buffer = new ArrayBuffer(4 + data.byteLength);
  let view = new DataView(buffer);
  view.setUint32(0, data.byteLength, false);  // 写入长度
  
  let dataView = new Uint8Array(buffer, 4);
  let sourceView = new Uint8Array(data);
  for (let i = 0; i < sourceView.length; i++) {
    dataView[i] = sourceView[i];
  }
  
  return buffer;
}

// 方案2:分隔符
function encodeWithDelimiter(data: string): string {
  return data + '\n';  // 使用换行符分隔
}

坑点二:未设置超时导致无限等待

连接或发送操作可能永久阻塞。

// ❌ 错误:未设置超时
await tcpClient.connect({
  address: { address: '192.168.1.100', port: 8888, family: 1 }
  // 没有timeout,可能永久等待
});

// ✅ 正确:设置合理超时
await tcpClient.connect({
  address: { address: '192.168.1.100', port: 8888, family: 1 },
  timeout: 30000  // 30秒超时
});

坑点三:资源未释放

Socket对象未正确关闭,导致资源泄漏。

// ❌ 错误:忘记关闭
async function badExample() {
  let tcp = socket.constructTCPSocketInstance();
  await tcp.connect(...);
  // 使用后忘记关闭
}

// ✅ 正确:确保关闭
async function goodExample() {
  let tcp = socket.constructTCPSocketInstance();
  try {
    await tcp.connect(...);
    // 使用...
  } finally {
    try {
      await tcp.close();
    } catch (error) {
      // 忽略关闭错误
    }
  }
}

五、HarmonyOS 6适配指南

5.1 新增配置选项

HarmonyOS 6提供了更丰富的Socket配置。

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

let tcp = socket.constructTCPSocketInstance();

// HarmonyOS 6: 高级配置
await tcp.connect({
  address: {
    address: '192.168.1.100',
    port: 8888,
    family: 1
  },
  timeout: 30000,
  
  // 新增:TCP选项
  tcpOptions: {
    // 是否启用TCP_NODELAY(禁用Nagle算法)
    noDelay: true,
  
    // 保活选项
    keepAlive: true,
    keepIdle: 7200,     // 空闲多久开始探测
    keepInterval: 75,   // 探测间隔
    keepCount: 9,       // 探测次数
  
    // 接收缓冲区大小
    receiveBufferSize: 65536,
  
    // 发送缓冲区大小
    sendBufferSize: 65536
  }
});

5.2 异步回调优化

HarmonyOS 6优化了异步回调机制。

// 使用Promise风格
try {
  let state = await tcp.getState();
  console.info('本地地址:', state.localAddress);
  console.info('远程地址:', state.remoteAddress);
} catch (error) {
  console.error('获取状态失败:', error);
}

六、总结

TCP Socket是网络编程的基础,掌握它对深入理解网络通信至关重要。本文从三个场景展开:

TCP客户端:主动连接服务器,发送和接收数据。注意连接状态管理、数据缓冲、异常处理。

TCP服务端:监听端口,接受客户端连接。需要管理多个客户端连接,处理并发访问。

自定义协议:在TCP之上构建应用协议。设计消息格式、实现编解码、处理粘包问题。

三个常见坑点:数据粘包、未设置超时、资源未释放。遇到TCP问题时,先排查这三个方面。

HarmonyOS 6提供了更丰富的TCP配置选项,如TCP_NODELAY、Keep-Alive等,可以针对不同场景优化性能。

下一篇文章,我们将探索UDP Socket编程,了解无连接数据传输的特点!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐