Socket通信指南(TCP/UDP)

数据由AI生成。

1. Socket通信概述

在鸿蒙系统中,Socket通信分为TCP和UDP两种协议:

  • TCP:面向连接、可靠传输、基于字节流的协议

  • UDP:无连接、不可靠传输、基于数据报的协议

2. 鸿蒙Socket API (API12+)

鸿蒙提供了@ohos.net.socket模块来实现Socket通信。

2.1 引入模块

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

3. TCP Socket实现

3.1 TCP客户端实现

// 创建TCP客户端Socket
let tcpClient: socket.TCPSocket = socket.constructTCPSocketInstance();

// 设置Socket选项
tcpClient.bind({
  address: '0.0.0.0',  // 绑定本地地址
  port: 0,             // 0表示系统自动分配端口
  family: 1            // 1表示IPv4
}, (err) => {
  if (err) {
    console.error('bind failed: ' + JSON.stringify(err));
    return;
  }
  console.log('bind success');
});

// 连接服务器
tcpClient.connect({
  address: '192.168.1.100', // 服务器IP
  port: 8080,              // 服务器端口
  timeout: 5000            // 超时时间(ms)
}, (err) => {
  if (err) {
    console.error('connect failed: ' + JSON.stringify(err));
    return;
  }
  console.log('connect success');
  
  // 发送byte数据
  let byteData = new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F]); // "Hello"的字节表示
  tcpClient.send({
    data: byteData.buffer
  }, (err) => {
    if (err) {
      console.error('send failed: ' + JSON.stringify(err));
      return;
    }
    console.log('send success');
  });
});

// 接收数据
tcpClient.on('message', (data: ArrayBuffer) => {
  console.log('received data length: ' + data.byteLength);
  
  // 将ArrayBuffer转换为Uint8Array
  let byteArray = new Uint8Array(data);
  
  // 处理接收到的byte数据
  processReceivedData(byteArray);
});

// 错误处理
tcpClient.on('error', (err) => {
  console.error('socket error: ' + JSON.stringify(err));
});

// 关闭连接
function closeTCPConnection() {
  tcpClient.close((err) => {
    if (err) {
      console.error('close failed: ' + JSON.stringify(err));
      return;
    }
    console.log('close success');
  });
}

3.2 TCP服务器实现

// 创建TCP服务器Socket
let tcpServer: socket.TCPSocketServer = socket.constructTCPSocketServerInstance();

// 监听端口
tcpServer.listen({
  address: '0.0.0.0',
  port: 8080,
  backlog: 10
}, (err) => {
  if (err) {
    console.error('listen failed: ' + JSON.stringify(err));
    return;
  }
  console.log('listen success');
});

// 接受客户端连接
tcpServer.on('connect', (client: socket.TCPSocketConnection) => {
  console.log('client connected');
  
  // 接收客户端数据
  client.on('message', (data: ArrayBuffer) => {
    console.log('received data length: ' + data.byteLength);
    
    // 将ArrayBuffer转换为Uint8Array
    let byteArray = new Uint8Array(data);
    
    // 处理接收到的byte数据
    processReceivedData(byteArray);
    
    // 发送响应
    let response = new Uint8Array([0x57, 0x6F, 0x72, 0x6C, 0x64]); // "World"的字节表示
    client.send({
      data: response.buffer
    }, (err) => {
      if (err) {
        console.error('send response failed: ' + JSON.stringify(err));
      }
    });
  });
  
  // 错误处理
  client.on('error', (err) => {
    console.error('client error: ' + JSON.stringify(err));
    client.close();
  });
  
  // 连接关闭
  client.on('close', () => {
    console.log('client disconnected');
  });
});

// 关闭服务器
function closeTCPServer() {
  tcpServer.close((err) => {
    if (err) {
      console.error('server close failed: ' + JSON.stringify(err));
      return;
    }
    console.log('server closed');
  });
}

4. UDP Socket实现

4.1 UDP发送和接收

// 创建UDP Socket
let udpSocket: socket.UDPSocket = socket.constructUDPSocketInstance();

// 绑定本地端口
udpSocket.bind({
  address: '0.0.0.0',
  port: 12345,
  family: 1 // IPv4
}, (err) => {
  if (err) {
    console.error('bind failed: ' + JSON.stringify(err));
    return;
  }
  console.log('bind success');
});

// 发送UDP数据报
function sendUDPMessage() {
  let byteData = new Uint8Array([0x48, 0x65, 0x6C, 0x6C, 0x6F]); // "Hello"的字节表示
  udpSocket.send({
    address: '192.168.1.100',
    port: 8080,
    data: byteData.buffer
  }, (err) => {
    if (err) {
      console.error('send failed: ' + JSON.stringify(err));
      return;
    }
    console.log('send success');
  });
}

// 接收UDP数据报
udpSocket.on('message', (data: ArrayBuffer, remoteInfo: socket.SocketRemoteInfo) => {
  console.log(`received from ${remoteInfo.address}:${remoteInfo.port}`);
  console.log('data length: ' + data.byteLength);
  
  // 将ArrayBuffer转换为Uint8Array
  let byteArray = new Uint8Array(data);
  
  // 处理接收到的byte数据
  processReceivedData(byteArray);
});

// 错误处理
udpSocket.on('error', (err) => {
  console.error('udp error: ' + JSON.stringify(err));
});

// 关闭UDP Socket
function closeUDPSocket() {
  udpSocket.close((err) => {
    if (err) {
      console.error('close failed: ' + JSON.stringify(err));
      return;
    }
    console.log('udp closed');
  });
}

5. 数据缓存与处理

5.1 数据缓存实现

// 定义数据缓存区
let dataBuffer: Uint8Array = new Uint8Array(0);

// 处理接收到的数据
function processReceivedData(newData: Uint8Array) {
  // 合并新数据到缓存区
  let mergedBuffer = new Uint8Array(dataBuffer.length + newData.length);
  mergedBuffer.set(dataBuffer);
  mergedBuffer.set(newData, dataBuffer.length);
  dataBuffer = mergedBuffer;
  
  // 尝试解析数据
  tryParseData();
}

// 尝试解析缓存中的数据
function tryParseData() {
  // 示例:假设我们的协议是前4字节表示数据长度
  if (dataBuffer.length >= 4) {
    // 读取数据长度 (大端序)
    let dataLength = (dataBuffer[0] << 24) | 
                    (dataBuffer[1] << 16) | 
                    (dataBuffer[2] << 8) | 
                    dataBuffer[3];
    
    // 检查是否收到完整数据包
    if (dataBuffer.length >= 4 + dataLength) {
      // 提取有效数据
      let packetData = dataBuffer.slice(4, 4 + dataLength);
      
      // 处理数据包
      handlePacket(packetData);
      
      // 移除已处理的数据
      dataBuffer = dataBuffer.slice(4 + dataLength);
      
      // 递归处理剩余数据
      if (dataBuffer.length > 0) {
        tryParseData();
      }
    }
  }
}

// 处理完整数据包
function handlePacket(packet: Uint8Array) {
  console.log('received packet length: ' + packet.length);
  // 这里可以根据实际协议进行解析
  // 例如转换为字符串:
  let str = String.fromCharCode.apply(null, Array.from(packet));
  console.log('packet content: ' + str);
  
  // 或者处理为其他格式...
}

5.2 高级缓存管理

对于更复杂的场景,可以实现环形缓冲区:

class CircularBuffer {
  private buffer: Uint8Array;
  private head: number = 0;
  private tail: number = 0;
  private capacity: number;
  
  constructor(size: number) {
    this.capacity = size;
    this.buffer = new Uint8Array(size);
  }
  
  // 写入数据
  write(data: Uint8Array): boolean {
    if (this.availableSpace() < data.length) {
      return false; // 空间不足
    }
    
    // 写入数据
    for (let i = 0; i < data.length; i++) {
      this.buffer[this.tail] = data[i];
      this.tail = (this.tail + 1) % this.capacity;
    }
    
    return true;
  }
  
  // 读取数据
  read(size: number): Uint8Array | null {
    if (this.availableData() < size) {
      return null; // 数据不足
    }
    
    let result = new Uint8Array(size);
    for (let i = 0; i < size; i++) {
      result[i] = this.buffer[this.head];
      this.head = (this.head + 1) % this.capacity;
    }
    
    return result;
  }
  
  // 查看数据但不移动指针
  peek(size: number): Uint8Array | null {
    if (this.availableData() < size) {
      return null;
    }
    
    let result = new Uint8Array(size);
    let tempHead = this.head;
    for (let i = 0; i < size; i++) {
      result[i] = this.buffer[tempHead];
      tempHead = (tempHead + 1) % this.capacity;
    }
    
    return result;
  }
  
  // 获取可用空间
  availableSpace(): number {
    if (this.tail >= this.head) {
      return this.capacity - this.tail + this.head - 1;
    } else {
      return this.head - this.tail - 1;
    }
  }
  
  // 获取可用数据量
  availableData(): number {
    if (this.tail >= this.head) {
      return this.tail - this.head;
    } else {
      return this.capacity - this.head + this.tail;
    }
  }
  
  // 清空缓冲区
  clear(): void {
    this.head = 0;
    this.tail = 0;
  }
}

// 使用环形缓冲区
const buffer = new CircularBuffer(1024 * 1024); // 1MB缓冲区

// 接收数据时写入缓冲区
socket.on('message', (data: ArrayBuffer) => {
  const byteData = new Uint8Array(data);
  if (!buffer.write(byteData)) {
    console.error('Buffer overflow!');
    // 处理缓冲区溢出情况
  }
  
  // 尝试解析数据
  processBufferData();
});

// 处理缓冲区数据
function processBufferData() {
  // 假设协议头是4字节长度
  const header = buffer.peek(4);
  if (!header) return; // 数据不足
  
  const packetLength = (header[0] << 24) | 
                      (header[1] << 16) | 
                      (header[2] << 8) | 
                      header[3];
  
  // 检查完整数据包是否到达
  if (buffer.availableData() >= 4 + packetLength) {
    // 跳过头部
    buffer.read(4);
    
    // 读取数据包
    const packet = buffer.read(packetLength);
    if (packet) {
      handlePacket(packet);
      
      // 继续处理可能存在的下一个数据包
      processBufferData();
    }
  }
}

6. 性能优化建议

6.1 Socket参数优化

// TCP参数优化
tcpClient.setExtraOptions({
  keepAlive: true,       // 启用KeepAlive
  TCPNoDelay: true,      // 禁用Nagle算法
  receiveBufferSize: 8192, // 接收缓冲区大小
  sendBufferSize: 8192    // 发送缓冲区大小
});

// UDP参数优化
udpSocket.setExtraOptions({
  receiveBufferSize: 65536, // 增大UDP接收缓冲区
  sendBufferSize: 65536     // 增大UDP发送缓冲区
});

6.2 多线程处理

对于高负载场景,可以使用Worker线程处理数据:

// 主线程
socket.on('message', (data: ArrayBuffer) => {
  // 将数据传递给Worker线程处理
  workerPort.postMessage(data, [data]);
});

// Worker线程中处理数据
workerPort.onmessage = (event: MessageEvent<ArrayBuffer>) => {
  const byteData = new Uint8Array(event.data);
  // 复杂的数据处理逻辑...
};

7. 错误处理与重连机制

7.1 TCP自动重连实现

let reconnectAttempts = 0;
const maxReconnectAttempts = 5;
const reconnectDelay = 2000; // 2秒

function connectToServer() {
  tcpClient.connect({
    address: '192.168.1.100',
    port: 8080,
    timeout: 5000
  }, (err) => {
    if (err) {
      console.error('Connect failed:', err);
      
      if (reconnectAttempts < maxReconnectAttempts) {
        reconnectAttempts++;
        console.log(`Retrying in ${reconnectDelay/1000} seconds... (Attempt ${reconnectAttempts}/${maxReconnectAttempts})`);
        
        setTimeout(() => {
          connectToServer();
        }, reconnectDelay);
      } else {
        console.error('Max reconnect attempts reached');
      }
      return;
    }
    
    console.log('Connected successfully');
    reconnectAttempts = 0; // 重置重连计数器
  });
}

tcpClient.on('close', () => {
  console.log('Connection closed, attempting to reconnect...');
  connectToServer();
});

8. 实际应用示例

8.1 实现一个简单的网络协议

假设我们需要实现一个简单的应用层协议:

  • 协议头:4字节长度字段(大端序)

  • 协议体:实际数据

  • 协议尾:2字节CRC校验

// 发送数据
function sendPacket(data: Uint8Array) {
  // 计算CRC16校验
  const crc = calculateCRC16(data);
  
  // 构造完整数据包
  const length = data.length;
  const packet = new Uint8Array(4 + length + 2);
  
  // 写入长度 (大端序)
  packet[0] = (length >> 24) & 0xFF;
  packet[1] = (length >> 16) & 0xFF;
  packet[2] = (length >> 8) & 0xFF;
  packet[3] = length & 0xFF;
  
  // 写入数据
  packet.set(data, 4);
  
  // 写入CRC (大端序)
  packet[4 + length] = (crc >> 8) & 0xFF;
  packet[4 + length + 1] = crc & 0xFF;
  
  // 发送数据
  tcpClient.send({
    data: packet.buffer
  }, (err) => {
    if (err) {
      console.error('Send packet failed:', err);
    }
  });
}

// 接收数据处理
function processReceivedData(data: Uint8Array) {
  // 检查最小长度 (4字节长度 + 2字节CRC)
  if (data.length < 6) {
    console.error('Invalid packet: too short');
    return;
  }
  
  // 读取长度 (大端序)
  const length = (data[0] << 24) | 
                (data[1] << 16) | 
                (data[2] << 8) | 
                data[3];
  
  // 检查数据完整性
  if (data.length < 4 + length + 2) {
    console.error('Incomplete packet');
    return;
  }
  
  // 提取数据部分
  const payload = data.slice(4, 4 + length);
  
  // 提取CRC
  const receivedCRC = (data[4 + length] << 8) | data[4 + length + 1];
  
  // 计算校验
  const calculatedCRC = calculateCRC16(payload);
  
  if (receivedCRC !== calculatedCRC) {
    console.error('CRC check failed');
    return;
  }
  
  // 处理有效数据
  handleApplicationData(payload);
}

// 简单的CRC16计算
function calculateCRC16(data: Uint8Array): number {
  let crc = 0xFFFF;
  for (let i = 0; i < data.length; i++) {
    crc ^= data[i] << 8;
    for (let j = 0; j < 8; j++) {
      crc = crc & 0x8000 ? (crc << 1) ^ 0x1021 : crc << 1;
    }
  }
  return crc & 0xFFFF;
}

9. 安全注意事项

  1. 数据验证:始终验证接收数据的长度和格式

  2. 缓冲区限制:设置合理的缓冲区大小限制,防止内存耗尽

  3. 超时处理:为所有网络操作设置合理的超时

  4. 错误处理:妥善处理所有可能的错误情况

  5. 敏感数据:避免在日志中打印原始二进制数据

// 安全发送示例
function safeSend(socket: socket.TCPSocket, data: Uint8Array) {
  try {
    if (data.length > 1024 * 1024) { // 限制1MB
      throw new Error('Data too large');
    }
    
    socket.send({
      data: data.buffer
    }, (err) => {
      if (err) {
        console.error('Send error:', err.message); // 不打印详细错误
        // 执行重试或恢复逻辑
      }
    });
  } catch (err) {
    console.error('Send failed:', err.message);
  }
}

10. 测试与调试建议

  1. 单元测试:为数据处理逻辑编写单元测试

  2. 模拟网络环境:测试在不同网络条件下的表现

  3. 压力测试:测试高负载情况下的性能

  4. 日志记录:记录关键操作和错误

// 调试日志示例
const debugLog: Array<string> = [];

function logDebug(message: string) {
  const timestamp = new Date().toISOString();
  debugLog.push(`${timestamp} - ${message}`);
  
  // 控制日志大小
  if (debugLog.length > 100) {
    debugLog.shift();
  }
}

// 网络状态监控
function monitorNetwork() {
  setInterval(() => {
    const stats = tcpClient.getStats();
    logDebug(`Sent: ${stats.bytesSent}, Received: ${stats.bytesReceived}`);
  }, 5000);
}

以上内容提供了鸿蒙ArkUI开发中使用Socket进行TCP/UDP通信的完整实现方案,包括数据缓存处理、协议解析、性能优化和安全考虑等方面。开发者可以根据实际需求调整和扩展这些基础实现。