Node.js之TCP服务(net模块)

15 阅读11分钟

Node.js之TCP服务(net模块)

引言

TCP(传输控制协议)是互联网的基础通信协议之一,它提供可靠的、面向连接的字节流服务。在Node.js中,net模块为创建TCP服务器和客户端提供了强大的API支持。

TCP与 UDP 区别

核心对比

特性TCPUDP
连接方式面向连接无连接
可靠性可靠传输不可靠传输
传输方式字节流数据报
速度较慢较快
头部开销20字节8字节
顺序保证保证顺序不保证顺序
流量控制支持不支持
拥塞控制支持不支持

TCP连接建立过程

sequenceDiagram
    participant C as Client
    participant S as Server
    
    Note over C,S: TCP三次握手
    C->>+S: SYN seq=100
    S-->>+C: SYN-ACK seq=200, ack=101
    C->>S: ACK seq=101, ack=201
    Note over C,S: 连接建立完成
    
    Note over C,S: 数据传输阶段
    C->>S: Data seq=101
    S-->>C: ACK ack=102
    
    Note over C,S: TCP四次挥手
    C->>+S: FIN seq=102
    S-->>+C: ACK ack=103
    S->>+C: FIN seq=201
    C-->>S: ACK ack=202
    Note over C,S: 连接关闭完成

适用场景对比

// TCP 适用场景
const tcpScenarios = [
  'HTTP/HTTPS 网站访问',
  '文件传输 (FTP)',
  '邮件服务 (SMTP/IMAP)',
  '数据库连接',
  '远程登录 (SSH)',
  '聊天应用(需要消息完整性)'
];

// UDP 适用场景
const udpScenarios = [
  'DNS 查询',
  '视频/音频流媒体',
  '在线游戏',
  '实时监控',
  'DHCP 服务',
  '广播/组播通信'
];

创建 TCP 服务器

创建简单的TCP 服务器

const net = require('net');

// 创建基础TCP服务器
const server = net.createServer((socket) => {
  console.log('客户端连接成功:', socket.remoteAddress + ':' + socket.remotePort);
  
  // 向客户端发送欢迎消息
  socket.write('欢迎连接到TCP服务器!\n');
  
  // 监听客户端数据
  socket.on('data', (data) => {
    console.log('接收到数据:', data.toString().trim());
    // 回显数据给客户端
    socket.write('服务器收到: ' + data);
  });
  
  // 监听连接关闭
  socket.on('close', () => {
    console.log('客户端断开连接');
  });
  
  // 监听错误事件
  socket.on('error', (err) => {
    console.error('Socket错误:', err);
  });
});

// 启动服务器监听
const PORT = 8080;
const HOST = '127.0.0.1';

server.listen(PORT, HOST, () => {
  console.log(`TCP服务器启动,监听 ${HOST}:${PORT}`);
});

// 处理服务器错误
server.on('error', (err) => {
  console.error('服务器错误:', err);
});

服务器的事件

const net = require('net');

class TCPServer {
  constructor(options = {}) {
    this.port = options.port || 8080;
    this.host = options.host || '127.0.0.1';
    this.server = null;
    this.connections = new Map(); // 存储连接信息
    this.connectionId = 0;
  }
  
  start() {
    this.server = net.createServer();
    
    // 监听连接事件
    this.server.on('connection', (socket) => {
      this.handleConnection(socket);
    });
    
    // 监听服务器启动事件
    this.server.on('listening', () => {
      const address = this.server.address();
      console.log(`TCP服务器启动成功: ${address.address}:${address.port}`);
    });
    
    // 监听关闭事件
    this.server.on('close', () => {
      console.log('TCP服务器已关闭');
    });
    
    // 监听错误事件
    this.server.on('error', (err) => {
      console.error('TCP服务器错误:', err);
      if (err.code === 'EADDRINUSE') {
        console.error(`端口 ${this.port} 已被占用`);
      }
    });
    
    // 开始监听
    this.server.listen(this.port, this.host);
  }
  
  handleConnection(socket) {
    const connectionId = ++this.connectionId;
    const clientInfo = `${socket.remoteAddress}:${socket.remotePort}`;
    
    // 存储连接信息
    this.connections.set(connectionId, {
      socket: socket,
      clientInfo: clientInfo,
      connectedAt: new Date(),
      bytesReceived: 0,
      bytesSent: 0
    });
    
    console.log(`[${connectionId}] 新连接: ${clientInfo}`);
    console.log(`当前连接数: ${this.connections.size}`);
    
    // 设置socket编码
    socket.setEncoding('utf8');
    
    // 数据接收事件
    socket.on('data', (data) => {
      this.handleData(connectionId, data);
    });
    
    // 连接关闭事件
    socket.on('close', (hadError) => {
      this.handleDisconnection(connectionId, hadError);
    });
    
    // 错误事件
    socket.on('error', (err) => {
      this.handleSocketError(connectionId, err);
    });
    
    // 超时事件
    socket.on('timeout', () => {
      console.log(`[${connectionId}] 连接超时`);
      socket.end('连接超时\n');
    });
    
    // 设置超时时间(30秒)
    socket.setTimeout(30000);
    
    // 发送欢迎消息
    this.sendMessage(connectionId, `欢迎连接! 连接ID: ${connectionId}\n`);
  }
  
  handleData(connectionId, data) {
    const connection = this.connections.get(connectionId);
    if (!connection) return;
    
    const message = data.toString().trim();
    connection.bytesReceived += data.length;
    
    console.log(`[${connectionId}] 收到数据: ${message}`);
    
    // 处理特殊命令
    if (message.startsWith('/')) {
      this.handleCommand(connectionId, message);
    } else {
      // 普通消息回显
      this.sendMessage(connectionId, `回显: ${message}\n`);
    }
  }
  
  handleCommand(connectionId, command) {
    const connection = this.connections.get(connectionId);
    if (!connection) return;
    
    switch (command) {
      case '/info':
        const info = {
          connectionId: connectionId,
          clientInfo: connection.clientInfo,
          connectedAt: connection.connectedAt,
          bytesReceived: connection.bytesReceived,
          bytesSent: connection.bytesSent,
          uptime: Date.now() - connection.connectedAt.getTime()
        };
        this.sendMessage(connectionId, `连接信息: ${JSON.stringify(info, null, 2)}\n`);
        break;
        
      case '/list':
        const activeConnections = Array.from(this.connections.keys());
        this.sendMessage(connectionId, `活跃连接: ${activeConnections.join(', ')}\n`);
        break;
        
      case '/quit':
        this.sendMessage(connectionId, '再见!\n');
        connection.socket.end();
        break;
        
      default:
        this.sendMessage(connectionId, `未知命令: ${command}\n`);
    }
  }
  
  handleDisconnection(connectionId, hadError) {
    const connection = this.connections.get(connectionId);
    if (connection) {
      console.log(`[${connectionId}] 连接断开: ${connection.clientInfo}${hadError ? ' (有错误)' : ''}`);
      this.connections.delete(connectionId);
    }
    console.log(`当前连接数: ${this.connections.size}`);
  }
  
  handleSocketError(connectionId, err) {
    console.error(`[${connectionId}] Socket错误:`, err);
    const connection = this.connections.get(connectionId);
    if (connection) {
      connection.socket.destroy();
      this.connections.delete(connectionId);
    }
  }
  
  sendMessage(connectionId, message) {
    const connection = this.connections.get(connectionId);
    if (connection && !connection.socket.destroyed) {
      connection.socket.write(message);
      connection.bytesSent += Buffer.byteLength(message);
    }
  }
  
  // 广播消息给所有连接
  broadcast(message, excludeId = null) {
    this.connections.forEach((connection, id) => {
      if (id !== excludeId && !connection.socket.destroyed) {
        connection.socket.write(message);
        connection.bytesSent += Buffer.byteLength(message);
      }
    });
  }
  
  // 优雅关闭服务器
  close() {
    if (this.server) {
      // 关闭所有连接
      this.connections.forEach((connection) => {
        connection.socket.end('服务器即将关闭\n');
      });
      
      // 关闭服务器
      this.server.close();
    }
  }
}

// 使用示例
const server = new TCPServer({ port: 8080, host: '127.0.0.1' });
server.start();

// 优雅关闭处理
process.on('SIGINT', () => {
  console.log('\n正在关闭服务器...');
  server.close();
  process.exit(0);
});

处理多个客户端

const net = require('net');

class MultiClientTCPServer {
  constructor(options = {}) {
    this.port = options.port || 8080;
    this.host = options.host || '127.0.0.1';
    this.server = null;
    this.clients = new Map();
    this.rooms = new Map(); // 聊天室功能
    this.clientCounter = 0;
  }
  
  start() {
    this.server = net.createServer((socket) => {
      this.addClient(socket);
    });
    
    this.server.on('listening', () => {
      console.log(`多客户端TCP服务器启动: ${this.host}:${this.port}`);
    });
    
    this.server.on('error', (err) => {
      console.error('服务器错误:', err);
    });
    
    this.server.listen(this.port, this.host);
  }
  
  addClient(socket) {
    const clientId = ++this.clientCounter;
    const clientInfo = {
      id: clientId,
      socket: socket,
      address: socket.remoteAddress,
      port: socket.remotePort,
      nickname: `用户${clientId}`,
      room: null,
      connectedAt: new Date()
    };
    
    this.clients.set(clientId, clientInfo);
    console.log(`客户端 ${clientId} 连接: ${clientInfo.address}:${clientInfo.port}`);
    
    // 设置socket事件
    socket.setEncoding('utf8');
    socket.setTimeout(60000); // 60秒超时
    
    socket.on('data', (data) => {
      this.handleMessage(clientId, data.toString().trim());
    });
    
    socket.on('close', () => {
      this.removeClient(clientId);
    });
    
    socket.on('error', (err) => {
      console.error(`客户端 ${clientId} 错误:`, err);
      this.removeClient(clientId);
    });
    
    socket.on('timeout', () => {
      console.log(`客户端 ${clientId} 超时`);
      this.sendToClient(clientId, '连接超时,断开连接\n');
      socket.end();
    });
    
    // 发送欢迎消息和帮助信息
    this.sendWelcomeMessage(clientId);
  }
  
  removeClient(clientId) {
    const client = this.clients.get(clientId);
    if (client) {
      // 如果在房间中,离开房间
      if (client.room) {
        this.leaveRoom(clientId);
      }
      
      console.log(`客户端 ${clientId} (${client.nickname}) 断开连接`);
      this.clients.delete(clientId);
      
      // 通知其他客户端
      this.broadcastToAll(`${client.nickname} 离开了服务器\n`, clientId);
    }
  }
  
  handleMessage(clientId, message) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    console.log(`[${client.nickname}]: ${message}`);
    
    // 处理命令
    if (message.startsWith('/')) {
      this.handleCommand(clientId, message);
    } else {
      // 普通消息处理
      if (client.room) {
        // 房间内广播
        this.broadcastToRoom(client.room, `[${client.nickname}]: ${message}\n`, clientId);
      } else {
        // 全局广播
        this.broadcastToAll(`[${client.nickname}]: ${message}\n`, clientId);
      }
    }
  }
  
  handleCommand(clientId, command) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    const parts = command.split(' ');
    const cmd = parts[0].toLowerCase();
    const args = parts.slice(1);
    
    switch (cmd) {
      case '/help':
        this.sendHelpMessage(clientId);
        break;
        
      case '/nick':
        if (args.length > 0) {
          const oldNick = client.nickname;
          client.nickname = args.join(' ');
          this.sendToClient(clientId, `昵称已更改为: ${client.nickname}\n`);
          this.broadcastToAll(`${oldNick} 更改昵称为 ${client.nickname}\n`, clientId);
        } else {
          this.sendToClient(clientId, '用法: /nick <新昵称>\n');
        }
        break;
        
      case '/list':
        this.sendClientList(clientId);
        break;
        
      case '/rooms':
        this.sendRoomList(clientId);
        break;
        
      case '/join':
        if (args.length > 0) {
          this.joinRoom(clientId, args[0]);
        } else {
          this.sendToClient(clientId, '用法: /join <房间名>\n');
        }
        break;
        
      case '/leave':
        this.leaveRoom(clientId);
        break;
        
      case '/whisper':
      case '/w':
        if (args.length >= 2) {
          this.sendPrivateMessage(clientId, args[0], args.slice(1).join(' '));
        } else {
          this.sendToClient(clientId, '用法: /whisper <用户> <消息>\n');
        }
        break;
        
      case '/quit':
        this.sendToClient(clientId, '再见!\n');
        client.socket.end();
        break;
        
      default:
        this.sendToClient(clientId, `未知命令: ${cmd}. 输入 /help 查看帮助\n`);
    }
  }
  
  sendWelcomeMessage(clientId) {
    const welcomeMsg = `
╔══════════════════════════════════╗
║       欢迎来到TCP聊天服务器       ║
║                                  ║
║ 您的ID: ${clientId.toString().padEnd(24)} ║
║ 输入 /help 查看可用命令          ║
╚══════════════════════════════════╝
`;
    this.sendToClient(clientId, welcomeMsg + '\n');
  }
  
  sendHelpMessage(clientId) {
    const helpMsg = `
可用命令:
/help          - 显示帮助信息
/nick <名称>   - 更改昵称
/list          - 显示在线用户
/rooms         - 显示房间列表
/join <房间>   - 加入房间
/leave         - 离开当前房间
/whisper <用户> <消息> - 私聊
/quit          - 退出服务器
`;
    this.sendToClient(clientId, helpMsg + '\n');
  }
  
  sendClientList(clientId) {
    const clients = Array.from(this.clients.values());
    let list = '\n在线用户列表:\n';
    list += '─'.repeat(30) + '\n';
    
    clients.forEach(client => {
      const status = client.room ? `[房间: ${client.room}]` : '[大厅]';
      list += `${client.id}. ${client.nickname} ${status}\n`;
    });
    
    list += `\n总计: ${clients.length} 人在线\n`;
    this.sendToClient(clientId, list);
  }
  
  sendRoomList(clientId) {
    let list = '\n房间列表:\n';
    list += '─'.repeat(30) + '\n';
    
    if (this.rooms.size === 0) {
      list += '当前没有活跃的房间\n';
    } else {
      this.rooms.forEach((members, roomName) => {
        list += `${roomName} (${members.size} 人)\n`;
      });
    }
    
    this.sendToClient(clientId, list);
  }
  
  joinRoom(clientId, roomName) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    // 离开当前房间
    if (client.room) {
      this.leaveRoom(clientId);
    }
    
    // 加入新房间
    if (!this.rooms.has(roomName)) {
      this.rooms.set(roomName, new Set());
    }
    
    this.rooms.get(roomName).add(clientId);
    client.room = roomName;
    
    this.sendToClient(clientId, `已加入房间: ${roomName}\n`);
    this.broadcastToRoom(roomName, `${client.nickname} 加入了房间\n`, clientId);
  }
  
  leaveRoom(clientId) {
    const client = this.clients.get(clientId);
    if (!client || !client.room) return;
    
    const roomName = client.room;
    const room = this.rooms.get(roomName);
    
    if (room) {
      room.delete(clientId);
      
      // 通知房间内其他用户
      this.broadcastToRoom(roomName, `${client.nickname} 离开了房间\n`, clientId);
      
      // 如果房间为空,删除房间
      if (room.size === 0) {
        this.rooms.delete(roomName);
      }
    }
    
    client.room = null;
    this.sendToClient(clientId, `已离开房间: ${roomName}\n`);
  }
  
  sendPrivateMessage(senderId, targetNick, message) {
    const sender = this.clients.get(senderId);
    if (!sender) return;
    
    // 查找目标用户
    const target = Array.from(this.clients.values()).find(
      client => client.nickname.toLowerCase() === targetNick.toLowerCase()
    );
    
    if (target) {
      this.sendToClient(target.id, `[私聊来自 ${sender.nickname}]: ${message}\n`);
      this.sendToClient(senderId, `[私聊发送给 ${target.nickname}]: ${message}\n`);
    } else {
      this.sendToClient(senderId, `用户 "${targetNick}" 不在线\n`);
    }
  }
  
  sendToClient(clientId, message) {
    const client = this.clients.get(clientId);
    if (client && !client.socket.destroyed) {
      client.socket.write(message);
    }
  }
  
  broadcastToAll(message, excludeId = null) {
    this.clients.forEach((client, clientId) => {
      if (clientId !== excludeId && !client.socket.destroyed) {
        client.socket.write(message);
      }
    });
  }
  
  broadcastToRoom(roomName, message, excludeId = null) {
    const room = this.rooms.get(roomName);
    if (room) {
      room.forEach(clientId => {
        if (clientId !== excludeId) {
          this.sendToClient(clientId, message);
        }
      });
    }
  }
  
  close() {
    this.clients.forEach(client => {
      client.socket.end('服务器关闭\n');
    });
    
    if (this.server) {
      this.server.close();
    }
  }
}

// 启动服务器
const chatServer = new MultiClientTCPServer({ port: 8080 });
chatServer.start();

// 优雅关闭
process.on('SIGINT', () => {
  console.log('\n关闭聊天服务器...');
  chatServer.close();
  process.exit(0);
});

创建 TCP 客户端

创建简单的 TCP 客户端

const net = require('net');

// 创建基础TCP客户端
const client = net.createConnection({ port: 8080, host: '127.0.0.1' });

// 连接成功事件
client.on('connect', () => {
  console.log('成功连接到服务器');
  
  // 发送数据到服务器
  client.write('Hello Server!');
});

// 接收服务器数据
client.on('data', (data) => {
  console.log('收到服务器数据:', data.toString());
});

// 连接关闭事件
client.on('close', () => {
  console.log('与服务器断开连接');
});

// 错误事件
client.on('error', (err) => {
  console.error('连接错误:', err);
});

// 5秒后关闭连接
setTimeout(() => {
  client.end();
}, 5000);

客户端的事件

const net = require('net');
const readline = require('readline');

class TCPClient {
  constructor(options = {}) {
    this.host = options.host || '127.0.0.1';
    this.port = options.port || 8080;
    this.client = null;
    this.connected = false;
    this.reconnectDelay = options.reconnectDelay || 3000;
    this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
    this.reconnectAttempts = 0;
    this.autoReconnect = options.autoReconnect !== false;
    
    // 创建命令行接口
    this.rl = readline.createInterface({
      input: process.stdin,
      output: process.stdout
    });
  }
  
  connect() {
    console.log(`正在连接到 ${this.host}:${this.port}...`);
    
    this.client = net.createConnection({
      port: this.port,
      host: this.host
    });
    
    this.setupEventHandlers();
  }
  
  setupEventHandlers() {
    // 连接成功事件
    this.client.on('connect', () => {
      console.log(`✅ 成功连接到服务器 ${this.host}:${this.port}`);
      this.connected = true;
      this.reconnectAttempts = 0;
      this.onConnect();
    });
    
    // 接收数据事件
    this.client.on('data', (data) => {
      this.onData(data);
    });
    
    // 连接关闭事件
    this.client.on('close', (hadError) => {
      console.log('❌ 连接已关闭' + (hadError ? ' (有错误)' : ''));
      this.connected = false;
      this.onClose(hadError);
    });
    
    // 连接结束事件
    this.client.on('end', () => {
      console.log('📡 服务器结束了连接');
      this.connected = false;
      this.onEnd();
    });
    
    // 错误事件
    this.client.on('error', (err) => {
      console.error('💥 连接错误:', err.message);
      this.connected = false;
      this.onError(err);
    });
    
    // 超时事件
    this.client.on('timeout', () => {
      console.log('⏰ 连接超时');
      this.onTimeout();
    });
    
    // Lookup事件 (DNS解析)
    this.client.on('lookup', (err, address, family, host) => {
      if (err) {
        console.error('DNS解析失败:', err);
      } else {
        console.log(`🔍 DNS解析: ${host} -> ${address} (IPv${family})`);
      }
    });
    
    // Ready事件
    this.client.on('ready', () => {
      console.log('🚀 连接就绪,可以发送数据');
      this.onReady();
    });
  }
  
  // 事件处理方法
  onConnect() {
    this.startInputHandler();
  }
  
  onData(data) {
    const message = data.toString().trim();
    
    // 清除当前行,显示服务器消息,然后重新显示提示符
    process.stdout.write('\r\x1b[K'); // 清除当前行
    console.log(message);
    this.showPrompt();
  }
  
  onClose(hadError) {
    if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
      this.attemptReconnect();
    } else {
      this.cleanup();
    }
  }
  
  onEnd() {
    if (this.autoReconnect) {
      this.attemptReconnect();
    } else {
      this.cleanup();
    }
  }
  
  onError(err) {
    switch (err.code) {
      case 'ECONNREFUSED':
        console.error('🚫 连接被拒绝,请检查服务器是否运行');
        break;
      case 'ENOTFOUND':
        console.error('🔍 找不到主机,请检查主机名');
        break;
      case 'ETIMEDOUT':
        console.error('⏱️ 连接超时');
        break;
      case 'ECONNRESET':
        console.error('🔄 连接被重置');
        break;
      default:
        console.error('❌ 未知错误:', err.code);
    }
    
    if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
      this.attemptReconnect();
    } else {
      this.cleanup();
    }
  }
  
  onTimeout() {
    console.log('断开超时连接...');
    this.client.destroy();
  }
  
  onReady() {
    // 可以在这里发送初始化数据
  }
  
  attemptReconnect() {
    this.reconnectAttempts++;
    console.log(`🔄 尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
    
    setTimeout(() => {
      this.connect();
    }, this.reconnectDelay);
  }
  
  startInputHandler() {
    this.showPrompt();
    
    this.rl.on('line', (input) => {
      const command = input.trim();
      
      if (command === '/quit' || command === '/exit') {
        this.disconnect();
        return;
      }
      
      if (command === '/status') {
        this.showStatus();
        this.showPrompt();
        return;
      }
      
      if (command === '/clear') {
        console.clear();
        this.showPrompt();
        return;
      }
      
      if (command.length > 0) {
        this.sendMessage(command);
      }
      
      this.showPrompt();
    });
    
    this.rl.on('close', () => {
      this.disconnect();
    });
  }
  
  sendMessage(message) {
    if (this.connected && this.client && !this.client.destroyed) {
      this.client.write(message + '\n');
    } else {
      console.log('❌ 未连接到服务器,无法发送消息');
    }
  }
  
  showPrompt() {
    if (this.connected) {
      process.stdout.write('💬 > ');
    } else {
      process.stdout.write('❌ [断开] > ');
    }
  }
  
  showStatus() {
    console.log('\n📊 连接状态:');
    console.log(`   服务器: ${this.host}:${this.port}`);
    console.log(`   状态: ${this.connected ? '✅ 已连接' : '❌ 未连接'}`);
    console.log(`   重连次数: ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
    console.log(`   自动重连: ${this.autoReconnect ? '启用' : '禁用'}`);
    
    if (this.client) {
      console.log(`   本地地址: ${this.client.localAddress}:${this.client.localPort}`);
      console.log(`   字节读取: ${this.client.bytesRead}`);
      console.log(`   字节写入: ${this.client.bytesWritten}`);
    }
    console.log();
  }
  
  disconnect() {
    console.log('\n👋 正在断开连接...');
    this.autoReconnect = false;
    
    if (this.client && !this.client.destroyed) {
      this.client.end();
    }
    
    this.cleanup();
  }
  
  cleanup() {
    if (this.rl) {
      this.rl.close();
    }
    
    setTimeout(() => {
      process.exit(0);
    }, 100);
  }
  
  // 设置超时
  setTimeout(timeout) {
    if (this.client) {
      this.client.setTimeout(timeout);
    }
  }
  
  // 设置保持活跃
  setKeepAlive(enable, initialDelay) {
    if (this.client) {
      this.client.setKeepAlive(enable, initialDelay);
    }
  }
  
  // 设置无延迟
  setNoDelay(noDelay) {
    if (this.client) {
      this.client.setNoDelay(noDelay);
    }
  }
}

// 使用示例
const client = new TCPClient({
  host: '127.0.0.1',
  port: 8080,
  autoReconnect: true,
  maxReconnectAttempts: 5,
  reconnectDelay: 3000
});

console.log('TCP客户端启动');
console.log('命令: /quit - 退出, /status - 状态, /clear - 清屏');
client.connect();

// 优雅关闭处理
process.on('SIGINT', () => {
  console.log('\n收到中断信号...');
  client.disconnect();
});

客户端与服务器的交互

// 客户端与服务器交互流程
const interactionFlow = {
  // 连接建立流程
  connectionFlow: `
    客户端 -> 服务器: 建立TCP连接
    服务器 -> 客户端: 发送欢迎消息
    客户端 -> 服务器: 发送认证信息
    服务器 -> 客户端: 确认认证结果
  `,
  
  // 数据交换流程
  dataExchangeFlow: `
    客户端 -> 服务器: 发送请求数据
    服务器: 处理请求
    服务器 -> 客户端: 返回响应数据
    客户端: 处理响应
  `
};
sequenceDiagram
    participant C as TCP Client
    participant S as TCP Server
    
    Note over C,S: 连接建立阶段
    C->>+S: TCP连接请求
    S-->>-C: 连接确认
    S->>C: 发送欢迎消息
    
    Note over C,S: 认证阶段
    C->>S: 发送用户信息
    S->>S: 验证用户信息
    S-->>C: 认证结果
    
    Note over C,S: 正常通信阶段
    loop 消息交换
        C->>S: 发送消息/命令
        S->>S: 处理消息
        S-->>C: 返回响应
    end
    
    Note over C,S: 连接关闭阶段
    C->>S: 发送关闭请求
    S-->>C: 确认关闭
    Note over C,S: 连接终止

TCP 数据传输

数据流处理

const net = require('net');
const { Transform } = require('stream');

class DataProcessor {
  constructor() {
    this.buffer = Buffer.alloc(0);
    this.expectedLength = 0;
    this.headerSize = 4; // 4字节长度头
  }
  
  // 处理粘包和拆包问题
  processData(chunk) {
    // 将新数据追加到缓冲区
    this.buffer = Buffer.concat([this.buffer, chunk]);
    
    const messages = [];
    
    while (this.buffer.length >= this.headerSize) {
      // 如果还没有读取消息长度
      if (this.expectedLength === 0) {
        // 读取消息长度(前4字节)
        this.expectedLength = this.buffer.readUInt32BE(0);
        this.buffer = this.buffer.slice(this.headerSize);
      }
      
      // 检查是否有完整的消息
      if (this.buffer.length >= this.expectedLength) {
        // 提取完整消息
        const messageData = this.buffer.slice(0, this.expectedLength);
        this.buffer = this.buffer.slice(this.expectedLength);
        this.expectedLength = 0;
        
        // 解析消息
        try {
          const message = JSON.parse(messageData.toString());
          messages.push(message);
        } catch (error) {
          console.error('消息解析失败:', error);
        }
      } else {
        // 数据不完整,等待更多数据
        break;
      }
    }
    
    return messages;
  }
  
  // 封装消息
  packMessage(data) {
    const message = JSON.stringify(data);
    const messageBuffer = Buffer.from(message);
    const lengthBuffer = Buffer.alloc(this.headerSize);
    
    // 写入消息长度
    lengthBuffer.writeUInt32BE(messageBuffer.length, 0);
    
    // 组合长度头和消息体
    return Buffer.concat([lengthBuffer, messageBuffer]);
  }
}

// TCP服务器 - 数据流处理
class StreamTCPServer {
  constructor(port = 8080) {
    this.port = port;
    this.server = null;
    this.clients = new Map();
  }
  
  start() {
    this.server = net.createServer((socket) => {
      const processor = new DataProcessor();
      const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
      
      this.clients.set(clientId, { socket, processor });
      console.log(`客户端连接: ${clientId}`);
      
      // 数据处理
      socket.on('data', (chunk) => {
        const messages = processor.processData(chunk);
        messages.forEach(message => {
          this.handleMessage(clientId, message);
        });
      });
      
      // 连接关闭
      socket.on('close', () => {
        this.clients.delete(clientId);
        console.log(`客户端断开: ${clientId}`);
      });
      
      // 错误处理
      socket.on('error', (err) => {
        console.error(`客户端错误 ${clientId}:`, err);
        this.clients.delete(clientId);
      });
      
      // 发送欢迎消息
      this.sendMessage(clientId, {
        type: 'welcome',
        message: '欢迎连接到流式TCP服务器',
        timestamp: Date.now()
      });
    });
    
    this.server.listen(this.port, () => {
      console.log(`流式TCP服务器启动,端口: ${this.port}`);
    });
  }
  
  handleMessage(clientId, message) {
    console.log(`[${clientId}] 收到消息:`, message);
    
    switch (message.type) {
      case 'ping':
        this.sendMessage(clientId, {
          type: 'pong',
          timestamp: Date.now(),
          original: message
        });
        break;
        
      case 'echo':
        this.sendMessage(clientId, {
          type: 'echo_response',
          data: message.data,
          timestamp: Date.now()
        });
        break;
        
      case 'broadcast':
        this.broadcast(message, clientId);
        break;
        
      default:
        this.sendMessage(clientId, {
          type: 'error',
          message: '未知消息类型',
          timestamp: Date.now()
        });
    }
  }
  
  sendMessage(clientId, data) {
    const client = this.clients.get(clientId);
    if (client && !client.socket.destroyed) {
      const packedData = client.processor.packMessage(data);
      client.socket.write(packedData);
    }
  }
  
  broadcast(message, excludeId = null) {
    const broadcastData = {
      type: 'broadcast_message',
      sender: excludeId,
      data: message.data,
      timestamp: Date.now()
    };
    
    this.clients.forEach((client, clientId) => {
      if (clientId !== excludeId && !client.socket.destroyed) {
        const packedData = client.processor.packMessage(broadcastData);
        client.socket.write(packedData);
      }
    });
  }
}

// TCP客户端 - 数据流处理
class StreamTCPClient {
  constructor(host = '127.0.0.1', port = 8080) {
    this.host = host;
    this.port = port;
    this.client = null;
    this.processor = new DataProcessor();
    this.connected = false;
  }
  
  connect() {
    this.client = net.createConnection({ port: this.port, host: this.host });
    
    this.client.on('connect', () => {
      console.log('连接成功');
      this.connected = true;
      this.onConnect();
    });
    
    this.client.on('data', (chunk) => {
      const messages = this.processor.processData(chunk);
      messages.forEach(message => {
        this.handleMessage(message);
      });
    });
    
    this.client.on('close', () => {
      console.log('连接关闭');
      this.connected = false;
    });
    
    this.client.on('error', (err) => {
      console.error('连接错误:', err);
      this.connected = false;
    });
  }
  
  onConnect() {
    // 连接成功后发送测试消息
    this.sendMessage({
      type: 'ping',
      message: 'Hello Server',
      timestamp: Date.now()
    });
    
    // 定期发送心跳
    this.heartbeatInterval = setInterval(() => {
      if (this.connected) {
        this.sendMessage({
          type: 'ping',
          message: 'heartbeat',
          timestamp: Date.now()
        });
      }
    }, 30000);
  }
  
  handleMessage(message) {
    console.log('收到消息:', message);
    
    switch (message.type) {
      case 'welcome':
        console.log('服务器欢迎消息:', message.message);
        break;
        
      case 'pong':
        console.log('收到心跳响应');
        break;
        
      case 'broadcast_message':
        console.log(`广播消息来自 ${message.sender}:`, message.data);
        break;
        
      default:
        console.log('其他消息:', message);
    }
  }
  
  sendMessage(data) {
    if (this.connected && this.client && !this.client.destroyed) {
      const packedData = this.processor.packMessage(data);
      this.client.write(packedData);
    }
  }
  
  disconnect() {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }
    
    if (this.client && !this.client.destroyed) {
      this.client.end();
    }
  }
}

传输二进制数据

const net = require('net');
const fs = require('fs');
const path = require('path');

class BinaryDataServer {
  constructor(port = 8080) {
    this.port = port;
    this.server = null;
  }
  
  start() {
    this.server = net.createServer((socket) => {
      console.log('客户端连接:', socket.remoteAddress);
      
      let fileStream = null;
      let fileName = '';
      let fileSize = 0;
      let receivedSize = 0;
      let headerReceived = false;
      let headerBuffer = Buffer.alloc(0);
      
      socket.on('data', (chunk) => {
        if (!headerReceived) {
          // 处理文件头信息
          headerBuffer = Buffer.concat([headerBuffer, chunk]);
          
          // 查找头部结束标记 (\r\n\r\n)
          const headerEnd = headerBuffer.indexOf('\r\n\r\n');
          if (headerEnd !== -1) {
            const headerData = headerBuffer.slice(0, headerEnd).toString();
            const fileData = headerBuffer.slice(headerEnd + 4);
            
            // 解析头部信息
            const headers = this.parseHeaders(headerData);
            fileName = headers.filename || 'unknown';
            fileSize = parseInt(headers.filesize) || 0;
            
            console.log(`开始接收文件: ${fileName}, 大小: ${fileSize} bytes`);
            
            // 创建文件写入流
            const uploadsDir = path.join(__dirname, 'uploads');
            if (!fs.existsSync(uploadsDir)) {
              fs.mkdirSync(uploadsDir, { recursive: true });
            }
            
            const filePath = path.join(uploadsDir, fileName);
            fileStream = fs.createWriteStream(filePath);
            
            headerReceived = true;
            receivedSize = fileData.length;
            
            // 写入首次接收到的文件数据
            if (fileData.length > 0) {
              fileStream.write(fileData);
            }
            
            // 发送接收确认
            this.sendResponse(socket, {
              status: 'receiving',
              filename: fileName,
              received: receivedSize,
              total: fileSize
            });
          }
        } else {
          // 接收文件数据
          receivedSize += chunk.length;
          fileStream.write(chunk);
          
          // 发送进度更新
          if (receivedSize % 1024 === 0) { // 每1KB发送一次进度
            this.sendResponse(socket, {
              status: 'progress',
              received: receivedSize,
              total: fileSize,
              percentage: Math.round((receivedSize / fileSize) * 100)
            });
          }
          
          // 文件接收完成
          if (receivedSize >= fileSize) {
            fileStream.end();
            console.log(`文件接收完成: ${fileName}`);
            
            this.sendResponse(socket, {
              status: 'completed',
              filename: fileName,
              size: receivedSize,
              message: '文件上传成功'
            });
            
            // 重置状态
            headerReceived = false;
            headerBuffer = Buffer.alloc(0);
            receivedSize = 0;
            fileSize = 0;
            fileName = '';
          }
        }
      });
      
      socket.on('close', () => {
        console.log('客户端断开连接');
        if (fileStream) {
          fileStream.end();
        }
      });
      
      socket.on('error', (err) => {
        console.error('Socket错误:', err);
        if (fileStream) {
          fileStream.end();
        }
      });
    });
    
    this.server.listen(this.port, () => {
      console.log(`二进制数据服务器启动,端口: ${this.port}`);
    });
  }
  
  parseHeaders(headerString) {
    const headers = {};
    const lines = headerString.split('\r\n');
    
    lines.forEach(line => {
      const [key, value] = line.split(': ');
      if (key && value) {
        headers[key.toLowerCase()] = value;
      }
    });
    
    return headers;
  }
  
  sendResponse(socket, data) {
    const response = JSON.stringify(data) + '\n';
    socket.write(response);
  }
}

class BinaryDataClient {
  constructor(host = '127.0.0.1', port = 8080) {
    this.host = host;
    this.port = port;
    this.client = null;
  }
  
  connect() {
    return new Promise((resolve, reject) => {
      this.client = net.createConnection({ port: this.port, host: this.host });
      
      this.client.on('connect', () => {
        console.log('连接成功');
        resolve();
      });
      
      this.client.on('data', (data) => {
        // 处理服务器响应
        const responses = data.toString().split('\n').filter(line => line.trim());
        responses.forEach(response => {
          try {
            const parsed = JSON.parse(response);
            this.handleServerResponse(parsed);
          } catch (error) {
            console.log('服务器响应:', response);
          }
        });
      });
      
      this.client.on('error', (err) => {
        console.error('连接错误:', err);
        reject(err);
      });
    });
  }
  
  handleServerResponse(response) {
    switch (response.status) {
      case 'receiving':
        console.log(`服务器开始接收文件: ${response.filename}`);
        break;
      case 'progress':
        process.stdout.write(`\r上传进度: ${response.percentage}% (${response.received}/${response.total})`);
        break;
      case 'completed':
        console.log(`\n✅ 文件上传成功: ${response.filename}`);
        break;
      default:
        console.log('服务器响应:', response);
    }
  }
  
  async uploadFile(filePath) {
    if (!fs.existsSync(filePath)) {
      throw new Error('文件不存在');
    }
    
    const fileName = path.basename(filePath);
    const fileSize = fs.statSync(filePath).size;
    
    console.log(`准备上传文件: ${fileName}, 大小: ${fileSize} bytes`);
    
    // 构建文件头
    const headers = [
      `Filename: ${fileName}`,
      `Filesize: ${fileSize}`,
      `Content-Type: application/octet-stream`
    ].join('\r\n') + '\r\n\r\n';
    
    // 发送文件头
    this.client.write(headers);
    
    // 创建文件读取流
    const readStream = fs.createReadStream(filePath);
    let uploadedSize = 0;
    
    return new Promise((resolve, reject) => {
      readStream.on('data', (chunk) => {
        this.client.write(chunk);
        uploadedSize += chunk.length;
        
        // 显示上传进度
        const percentage = Math.round((uploadedSize / fileSize) * 100);
        process.stdout.write(`\r上传进度: ${percentage}% (${uploadedSize}/${fileSize})`);
      });
      
      readStream.on('end', () => {
        console.log('\n文件数据发送完成');
        resolve();
      });
      
      readStream.on('error', (err) => {
        console.error('文件读取错误:', err);
        reject(err);
      });
    });
  }
  
  disconnect() {
    if (this.client && !this.client.destroyed) {
      this.client.end();
    }
  }
}

// 使用示例
if (require.main === module) {
  const args = process.argv.slice(2);
  
  if (args[0] === 'server') {
    // 启动服务器
    const server = new BinaryDataServer(8080);
    server.start();
    
    process.on('SIGINT', () => {
      console.log('\n关闭服务器...');
      process.exit(0);
    });
    
  } else if (args[0] === 'client' && args[1]) {
    // 启动客户端并上传文件
    const client = new BinaryDataClient();
    
    (async () => {
      try {
        await client.connect();
        await client.uploadFile(args[1]);
        client.disconnect();
      } catch (error) {
        console.error('错误:', error.message);
        client.disconnect();
      }
    })();
    
  } else {
    console.log('使用方法:');
    console.log('  启动服务器: node script.js server');
    console.log('  上传文件:   node script.js client <文件路径>');
  }
}

半关闭连接

const net = require('net');

// 半关闭连接示例 - 服务器端
class HalfCloseServer {
  constructor(port = 8080) {
    this.port = port;
    this.server = null;
  }
  
  start() {
    this.server = net.createServer((socket) => {
      console.log('客户端连接:', socket.remoteAddress);
      
      socket.on('data', (data) => {
        const message = data.toString().trim();
        console.log('收到消息:', message);
        
        if (message === 'CLOSE_WRITE') {
          // 客户端请求半关闭写端
          console.log('客户端请求半关闭写端');
          socket.end('服务器确认关闭写端'); // 关闭写端
          
        } else if (message === 'SHUTDOWN') {
          // 优雅关闭演示
          console.log('开始优雅关闭流程...');
          
          // 发送最终数据
          socket.write('开始关闭连接...\n');
          socket.write('发送最后的数据...\n');
          socket.write('再见!\n');
          
          // 关闭写端,但保持读端开放
          socket.end();
          
        } else {
          // 普通消息处理
          socket.write(`回显: ${message}\n`);
        }
      });
      
      // 客户端关闭读端事件
      socket.on('end', () => {
        console.log('客户端关闭了写端');
        
        // 服务器可以继续发送数据
        setTimeout(() => {
          if (!socket.destroyed) {
            socket.write('服务器在客户端关闭写端后发送的数据\n');
            
            // 服务器也关闭写端
            setTimeout(() => {
              socket.end();
            }, 1000);
          }
        }, 500);
      });
      
      socket.on('close', (hadError) => {
        console.log('连接完全关闭', hadError ? '(有错误)' : '');
      });
      
      socket.on('error', (err) => {
        console.error('Socket错误:', err);
      });
    });
    
    this.server.listen(this.port, () => {
      console.log(`半关闭演示服务器启动,端口: ${this.port}`);
    });
  }
}

// 半关闭连接示例 - 客户端
class HalfCloseClient {
  constructor(host = '127.0.0.1', port = 8080) {
    this.host = host;
    this.port = port;
    this.client = null;
  }
  
  connect() {
    return new Promise((resolve, reject) => {
      this.client = net.createConnection({ port: this.port, host: this.host });
      
      this.client.on('connect', () => {
        console.log('连接成功');
        resolve();
      });
      
      this.client.on('data', (data) => {
        console.log('收到服务器数据:', data.toString().trim());
      });
      
      // 服务器关闭写端
      this.client.on('end', () => {
        console.log('服务器关闭了写端');
        
        // 客户端可以继续发送数据
        setTimeout(() => {
          if (!this.client.destroyed) {
            console.log('客户端在服务器关闭写端后发送数据');
            this.client.write('客户端最后的消息\n');
            
            // 客户端也关闭写端
            setTimeout(() => {
              this.client.end();
            }, 500);
          }
        }, 200);
      });
      
      this.client.on('close', (hadError) => {
        console.log('连接完全关闭', hadError ? '(有错误)' : '');
      });
      
      this.client.on('error', (err) => {
        console.error('连接错误:', err);
        reject(err);
      });
    });
  }
  
  // 演示正常通信
  async testNormalCommunication() {
    console.log('\n=== 正常通信测试 ===');
    this.client.write('Hello Server!\n');
    
    await this.sleep(500);
    this.client.write('How are you?\n');
  }
  
  // 演示半关闭 - 客户端先关闭写端
  async testClientHalfClose() {
    console.log('\n=== 客户端半关闭测试 ===');
    this.client.write('CLOSE_WRITE\n');
    
    await this.sleep(1000);
    
    // 客户端关闭写端
    console.log('客户端关闭写端');
    this.client.end();
  }
  
  // 演示服务器主导的优雅关闭
  async testServerGracefulShutdown() {
    console.log('\n=== 服务器优雅关闭测试 ===');
    this.client.write('SHUTDOWN\n');
    
    // 等待服务器响应并关闭
    await this.sleep(3000);
  }
  
  sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 连接状态图
const connectionStates = `
连接状态转换:

ESTABLISHED (建立) 
    ↓ (一方调用 end())
CLOSE_WAIT (等待关闭)
    ↓ (另一方也调用 end())
CLOSED (关闭)

半关闭状态:
- 客户端调用 end(): 客户端写端关闭,但仍可接收数据
- 服务器调用 end(): 服务器写端关闭,但仍可接收数据
- 双方都调用 end(): 连接完全关闭
`;

console.log(connectionStates);
stateDiagram-v2
    [*] --> CLOSED
    CLOSED --> LISTEN: server.listen()
    LISTEN --> SYN_RCVD: receive SYN
    SYN_RCVD --> ESTABLISHED: send SYN-ACK, receive ACK
    
    CLOSED --> SYN_SENT: client.connect()
    SYN_SENT --> ESTABLISHED: receive SYN-ACK, send ACK
    
    ESTABLISHED --> FIN_WAIT_1: socket.end()
    ESTABLISHED --> CLOSE_WAIT: receive FIN
    
    FIN_WAIT_1 --> FIN_WAIT_2: receive ACK
    FIN_WAIT_2 --> TIME_WAIT: receive FIN, send ACK
    
    CLOSE_WAIT --> LAST_ACK: socket.end(), send FIN
    LAST_ACK --> CLOSED: receive ACK
    
    TIME_WAIT --> CLOSED: timeout
    
    note right of CLOSE_WAIT
        半关闭状态:
        可接收数据,
        但不能发送
    end note

TCP 与心跳机制

实现心跳机制

const net = require('net');

class HeartbeatTCPServer {
  constructor(options = {}) {
    this.port = options.port || 8080;
    this.host = options.host || '127.0.0.1';
    this.heartbeatInterval = options.heartbeatInterval || 30000; // 30秒
    this.heartbeatTimeout = options.heartbeatTimeout || 60000;   // 60秒超时
    
    this.server = null;
    this.clients = new Map();
    this.heartbeatTimer = null;
  }
  
  start() {
    this.server = net.createServer((socket) => {
      this.handleNewClient(socket);
    });
    
    this.server.on('listening', () => {
      console.log(`心跳TCP服务器启动: ${this.host}:${this.port}`);
      this.startHeartbeatCheck();
    });
    
    this.server.on('error', (err) => {
      console.error('服务器错误:', err);
    });
    
    this.server.listen(this.port, this.host);
  }
  
  handleNewClient(socket) {
    const clientId = `${socket.remoteAddress}:${socket.remotePort}`;
    const clientInfo = {
      id: clientId,
      socket: socket,
      lastHeartbeat: Date.now(),
      lastPing: 0,
      rtt: 0, // 往返时间
      isAlive: true,
      connectTime: new Date(),
      heartbeatCount: 0,
      missedHeartbeats: 0
    };
    
    this.clients.set(clientId, clientInfo);
    console.log(`客户端连接: ${clientId}, 总连接数: ${this.clients.size}`);
    
    socket.setEncoding('utf8');
    
    // 发送欢迎消息
    this.sendMessage(clientId, {
      type: 'welcome',
      message: '欢迎连接到心跳服务器',
      clientId: clientId,
      heartbeatInterval: this.heartbeatInterval
    });
    
    socket.on('data', (data) => {
      this.handleMessage(clientId, data.toString().trim());
    });
    
    socket.on('close', () => {
      console.log(`客户端断开: ${clientId}`);
      this.clients.delete(clientId);
    });
    
    socket.on('error', (err) => {
      console.error(`客户端错误 ${clientId}:`, err);
      this.clients.delete(clientId);
    });
  }
  
  handleMessage(clientId, rawMessage) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    try {
      const message = JSON.parse(rawMessage);
      
      switch (message.type) {
        case 'heartbeat':
        case 'pong':
          this.handleHeartbeatResponse(clientId, message);
          break;
          
        case 'ping':
          this.handlePingRequest(clientId, message);
          break;
          
        case 'status':
          this.sendClientStatus(clientId);
          break;
          
        default:
          console.log(`[${clientId}] 收到消息:`, message);
          this.sendMessage(clientId, {
            type: 'echo',
            original: message,
            timestamp: Date.now()
          });
      }
      
    } catch (error) {
      console.error(`[${clientId}] 消息解析错误:`, error);
    }
  }
  
  handleHeartbeatResponse(clientId, message) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    const now = Date.now();
    client.lastHeartbeat = now;
    client.isAlive = true;
    client.heartbeatCount++;
    client.missedHeartbeats = 0;
    
    // 计算RTT(往返时间)
    if (message.timestamp) {
      client.rtt = now - message.timestamp;
    }
    
    console.log(`[${clientId}] 心跳响应, RTT: ${client.rtt}ms`);
  }
  
  handlePingRequest(clientId, message) {
    // 客户端主动ping,立即响应pong
    this.sendMessage(clientId, {
      type: 'pong',
      timestamp: message.timestamp || Date.now(),
      serverTime: Date.now()
    });
  }
  
  sendClientStatus(clientId) {
    const client = this.clients.get(clientId);
    if (!client) return;
    
    const status = {
      type: 'status_response',
      clientId: clientId,
      connectTime: client.connectTime,
      uptime: Date.now() - client.connectTime.getTime(),
      lastHeartbeat: new Date(client.lastHeartbeat),
      rtt: client.rtt,
      heartbeatCount: client.heartbeatCount,
      missedHeartbeats: client.missedHeartbeats,
      isAlive: client.isAlive
    };
    
    this.sendMessage(clientId, status);
  }
  
  sendMessage(clientId, data) {
    const client = this.clients.get(clientId);
    if (client && !client.socket.destroyed) {
      client.socket.write(JSON.stringify(data) + '\n');
    }
  }
  
  startHeartbeatCheck() {
    this.heartbeatTimer = setInterval(() => {
      this.checkClientHeartbeats();
    }, this.heartbeatInterval);
    
    console.log(`心跳检查启动,间隔: ${this.heartbeatInterval}ms`);
  }
  
  checkClientHeartbeats() {
    const now = Date.now();
    const clientsToRemove = [];
    
    console.log(`\n=== 心跳检查 (${new Date().toLocaleTimeString()}) ===`);
    console.log(`当前连接数: ${this.clients.size}`);
    
    this.clients.forEach((client, clientId) => {
      const timeSinceLastHeartbeat = now - client.lastHeartbeat;
      
      if (timeSinceLastHeartbeat > this.heartbeatTimeout) {
        // 超时,标记为死连接
        console.log(`[${clientId}] 心跳超时 (${timeSinceLastHeartbeat}ms)`);
        client.isAlive = false;
        client.missedHeartbeats++;
        
        // 尝试发送最后一次ping
        if (client.missedHeartbeats <= 3) {
          console.log(`[${clientId}] 发送紧急ping (第${client.missedHeartbeats}次)`);
          this.sendPing(clientId, true);
        } else {
          // 超过重试次数,断开连接
          console.log(`[${clientId}] 超过最大重试次数,断开连接`);
          clientsToRemove.push(clientId);
        }
        
      } else {
        // 正常发送心跳ping
        console.log(`[${clientId}] 发送心跳ping, 上次心跳: ${timeSinceLastHeartbeat}ms前`);
        this.sendPing(clientId, false);
      }
    });
    
    // 移除死连接
    clientsToRemove.forEach(clientId => {
      const client = this.clients.get(clientId);
      if (client) {
        client.socket.destroy();
        this.clients.delete(clientId);
        console.log(`[${clientId}] 连接已移除`);
      }
    });
  }
  
  sendPing(clientId, isUrgent = false) {
    const ping = {
      type: 'ping',
      timestamp: Date.now(),
      urgent: isUrgent,
      message: isUrgent ? '紧急心跳检查' : '心跳检查'
    };
    
    this.sendMessage(clientId, ping);
    
    // 记录ping时间用于RTT计算
    const client = this.clients.get(clientId);
    if (client) {
      client.lastPing = Date.now();
    }
  }
  
  // 广播服务器状态
  broadcastServerStatus() {
    const serverStatus = {
      type: 'server_status',
      timestamp: Date.now(),
      connectedClients: this.clients.size,
      uptime: process.uptime(),
      memory: process.memoryUsage(),
      heartbeatInterval: this.heartbeatInterval,
      heartbeatTimeout: this.heartbeatTimeout
    };
    
    this.clients.forEach((client, clientId) => {
      this.sendMessage(clientId, serverStatus);
    });
  }
  
  stop() {
    if (this.heartbeatTimer) {
      clearInterval(this.heartbeatTimer);
    }
    
    // 通知所有客户端服务器即将关闭
    this.clients.forEach(client => {
      this.sendMessage(client.id, {
        type: 'server_shutdown',
        message: '服务器即将关闭',
        timestamp: Date.now()
      });
      
      setTimeout(() => {
        client.socket.end();
      }, 1000);
    });
    
    if (this.server) {
      this.server.close();
    }
  }
}

// 使用示例
const heartbeatServer = new HeartbeatTCPServer({
  port: 8080,
  heartbeatInterval: 10000, // 10秒检查一次
  heartbeatTimeout: 30000   // 30秒超时
});

heartbeatServer.start();

// 每分钟广播一次服务器状态
setInterval(() => {
  heartbeatServer.broadcastServerStatus();
}, 60000);

// 优雅关闭
process.on('SIGINT', () => {
  console.log('\n正在关闭心跳服务器...');
  heartbeatServer.stop();
  
  setTimeout(() => {
    process.exit(0);
  }, 2000);
});

客户端响应心跳

const net = require('net');

class HeartbeatTCPClient {
  constructor(options = {}) {
    this.host = options.host || '127.0.0.1';
    this.port = options.port || 8080;
    this.client = null;
    this.connected = false;
    
    // 心跳配置
    this.heartbeatInterval = options.heartbeatInterval || 30000;
    this.heartbeatTimer = null;
    this.pingTimer = null;
    
    // 连接状态
    this.reconnectDelay = options.reconnectDelay || 5000;
    this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
    this.reconnectAttempts = 0;
    this.autoReconnect = options.autoReconnect !== false;
    
    // 统计信息
    this.stats = {
      connectTime: null,
      lastHeartbeat: null,
      heartbeatsSent: 0,
      heartbeatsReceived: 0,
      averageRTT: 0,
      rttSamples: []
    };
  }
  
  connect() {
    console.log(`正在连接到 ${this.host}:${this.port}...`);
    
    this.client = net.createConnection({ port: this.port, host: this.host });
    
    this.client.setEncoding('utf8');
    
    this.client.on('connect', () => {
      console.log('✅ 连接成功');
      this.connected = true;
      this.reconnectAttempts = 0;
      this.stats.connectTime = new Date();
      this.startHeartbeat();
    });
    
    this.client.on('data', (data) => {
      // 处理可能的多条消息
      const messages = data.trim().split('\n').filter(msg => msg.length > 0);
      messages.forEach(message => {
        try {
          const parsed = JSON.parse(message);
          this.handleServerMessage(parsed);
        } catch (error) {
          console.error('消息解析错误:', error);
        }
      });
    });
    
    this.client.on('close', (hadError) => {
      console.log('❌ 连接关闭' + (hadError ? ' (有错误)' : ''));
      this.connected = false;
      this.stopHeartbeat();
      
      if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
        this.attemptReconnect();
      }
    });
    
    this.client.on('error', (err) => {
      console.error('💥 连接错误:', err.message);
      this.connected = false;
      this.stopHeartbeat();
      
      if (this.autoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
        this.attemptReconnect();
      }
    });
  }
  
  handleServerMessage(message) {
    const now = Date.now();
    
    switch (message.type) {
      case 'welcome':
        console.log('🎉 服务器欢迎:', message.message);
        if (message.heartbeatInterval) {
          this.heartbeatInterval = message.heartbeatInterval;
          console.log(`⏱️ 心跳间隔设置为: ${this.heartbeatInterval}ms`);
        }
        break;
        
      case 'ping':
        // 服务器发送ping,回复pong
        console.log(`💓 收到服务器ping${message.urgent ? ' (紧急)' : ''}`);
        this.sendPong(message.timestamp);
        this.stats.heartbeatsReceived++;
        this.stats.lastHeartbeat = new Date();
        break;
        
      case 'pong':
        // 服务器回复pong
        const rtt = now - (message.timestamp || 0);
        console.log(`💓 收到服务器pong, RTT: ${rtt}ms`);
        this.updateRTTStats(rtt);
        break;
        
      case 'server_status':
        console.log('\n📊 服务器状态:');
        console.log(`   连接客户端数: ${message.connectedClients}`);
        console.log(`   服务器运行时间: ${Math.round(message.uptime)}秒`);
        console.log(`   内存使用: ${Math.round(message.memory.rss / 1024 / 1024)}MB`);
        break;
        
      case 'server_shutdown':
        console.log('🔴 服务器即将关闭:', message.message);
        this.autoReconnect = false;
        break;
        
      case 'status_response':
        this.displayClientStatus(message);
        break;
        
      default:
        console.log('📨 服务器消息:', message);
    }
  }
  
  sendPong(timestamp) {
    const pong = {
      type: 'pong',
      timestamp: timestamp,
      clientTime: Date.now()
    };
    
    this.sendMessage(pong);
  }
  
  sendPing() {
    const ping = {
      type: 'ping',
      timestamp: Date.now(),
      message: '客户端主动ping'
    };
    
    this.sendMessage(ping);
    this.stats.heartbeatsSent++;
  }
  
  sendMessage(data) {
    if (this.connected && this.client && !this.client.destroyed) {
      this.client.write(JSON.stringify(data) + '\n');
    }
  }
  
  startHeartbeat() {
    // 定期发送ping到服务器
    this.pingTimer = setInterval(() => {
      if (this.connected) {
        this.sendPing();
      }
    }, this.heartbeatInterval);
    
    console.log(`💗 心跳机制启动,间隔: ${this.heartbeatInterval}ms`);
  }
  
  stopHeartbeat() {
    if (this.pingTimer) {
      clearInterval(this.pingTimer);
      this.pingTimer = null;
    }
    
    console.log('💔 心跳机制停止');
  }
  
  updateRTTStats(rtt) {
    this.stats.rttSamples.push(rtt);
    
    // 保持最近20个样本
    if (this.stats.rttSamples.length > 20) {
      this.stats.rttSamples.shift();
    }
    
    // 计算平均RTT
    const sum = this.stats.rttSamples.reduce((a, b) => a + b, 0);
    this.stats.averageRTT = Math.round(sum / this.stats.rttSamples.length);
  }
  
  displayClientStatus(status) {
    console.log('\n📈 客户端状态:');
    console.log(`   客户端ID: ${status.clientId}`);
    console.log(`   连接时间: ${new Date(status.connectTime).toLocaleString()}`);
    console.log(`   运行时间: ${Math.round(status.uptime / 1000)}秒`);
    console.log(`   最后心跳: ${new Date(status.lastHeartbeat).toLocaleString()}`);
    console.log(`   平均RTT: ${status.rtt}ms`);
    console.log(`   心跳计数: ${status.heartbeatCount}`);
    console.log(`   错过心跳: ${status.missedHeartbeats}`);
    console.log(`   连接状态: ${status.isAlive ? '✅ 正常' : '❌ 异常'}`);
  }
  
  displayStats() {
    console.log('\n📊 本地统计:');
    console.log(`   连接时间: ${this.stats.connectTime?.toLocaleString() || '未连接'}`);
    console.log(`   最后心跳: ${this.stats.lastHeartbeat?.toLocaleString() || '无'}`);
    console.log(`   发送心跳: ${this.stats.heartbeatsSent}`);
    console.log(`   接收心跳: ${this.stats.heartbeatsReceived}`);
    console.log(`   平均RTT: ${this.stats.averageRTT}ms`);
    console.log(`   RTT样本数: ${this.stats.rttSamples.length}`);
  }
  
  requestStatus() {
    this.sendMessage({ type: 'status' });
  }
  
  attemptReconnect() {
    this.reconnectAttempts++;
    console.log(`🔄 尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
    
    setTimeout(() => {
      this.connect();
    }, this.reconnectDelay);
  }
  
  disconnect() {
    console.log('👋 主动断开连接...');
    this.autoReconnect = false;
    this.stopHeartbeat();
    
    if (this.client && !this.client.destroyed) {
      this.client.end();
    }
  }
}

// 心跳机制流程图
const heartbeatFlow = `
心跳机制工作流程:

1. 客户端连接到服务器
2. 服务器发送心跳配置
3. 客户端启动心跳定时器
4. 定期发送ping到服务器
5. 服务器回复pong
6. 客户端计算RTT
7. 服务器定期检查客户端状态
8. 超时则断开连接
`;

console.log(heartbeatFlow);
sequenceDiagram
    participant C as Client
    participant S as Server
    participant T as Timer
    
    Note over C,S: 连接建立
    C->>+S: TCP连接请求
    S-->>-C: 连接确认 + 心跳配置
    
    Note over C,S: 心跳机制启动
    C->>T: 启动心跳定时器
    S->>T: 启动心跳检查定时器
    
    Note over C,S: 正常心跳流程
    loop 每个心跳周期
        T-->>C: 定时器触发
        C->>S: 发送 PING
        S-->>C: 回复 PONG (包含RTT信息)
        C->>C: 更新RTT统计
        
        T-->>S: 心跳检查触发
        S->>S: 检查客户端状态
        alt 客户端正常
            S->>S: 更新客户端活跃时间
        else 客户端超时
            S->>S: 标记客户端为超时
            S->>C: 发送紧急PING
            alt 客户端响应
                C-->>S: 回复PONG
                S->>S: 恢复客户端状态
            else 多次无响应
                S->>S: 断开客户端连接
                S-->>C: 关闭连接
            end
        end
    end
    
    Note over C,S: 异常处理
    alt 网络异常
        C->>C: 检测到连接断开
        C->>C: 启动重连机制
        C->>S: 重新建立连接
    end

总结

Node.js的net模块为TCP网络编程提供了完整而强大的API支持。通过本文的深入分析,我们可以看到:

核心特性

  1. 可靠连接 - TCP提供面向连接的可靠数据传输
  2. 事件驱动 - Node.js采用事件驱动模型处理网络IO
  3. 流式处理 - 支持数据流的读写操作
  4. 连接管理 - 完善的连接生命周期管理

实践要点

  • 粘包处理 - 合理处理TCP数据流的粘包和拆包问题
  • 错误处理 - 全面的错误处理和异常恢复机制
  • 性能优化 - 合理设置缓冲区和连接参数
  • 资源管理 - 及时清理连接和相关资源

应用场景

TCP服务器特别适用于需要可靠数据传输的场景,如:

  • 聊天应用和即时通讯
  • 文件传输服务
  • 数据库连接
  • 物联网设备通信
  • 实时数据同步