从零构建 AI 网关(一):WebSocket 服务器实战

从零构建 AI 网关(一):WebSocket 服务器实战

本系列文章将带你从零构建一个类似 OpenClaw 的多渠道 AI 网关系统。本文是第一篇,重点讲解 WebSocket 服务器的实现。

🎯 项目背景

在 AI 应用开发中,我们经常需要将 AI 智能体接入多个聊天平台(Telegram、Discord、微信等)。这就需要一个网关系统来:

  1. 统一消息协议:不同平台的 API 差异很大
  2. 管理连接状态:处理掉线、重连、心跳
  3. 路由消息:将消息分发给正确的处理器
  4. 接入 AI:调用 LLM API 并返回响应

这就是 MyClaw 项目的目标------一个从零构建的多渠道 AI 网关。


📐 整体架构设计

markdown 复制代码
┌─────────────────────────────────────────────────────────┐
│                    Chat Channels                         │
│         Telegram | Discord | WhatsApp | ...              │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│                   Channel Plugins                        │
│         适配不同渠道的消息格式和 API                       │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│                Message Router                            │
│         消息路由、会话管理、消息队列                        │
└─────────────────────┬───────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────────────────────┐
│                   Gateway (核心)                         │
│         WebSocket 服务器、认证、心跳                       │
└─────────────────────────────────────────────────────────┘

我们将整个项目分为 6 个阶段

Phase 内容 代码量
Phase 1 基础架构(WebSocket Gateway) ~300 行
Phase 2 消息系统(路由、会话) ~500 行
Phase 3 渠道集成(Telegram/Discord) ~800 行
Phase 4 AI 智能体(LLM 接入) ~1000 行
Phase 5 技能系统(工具调用) ~600 行
Phase 6 高级功能(Web UI、监控) ~700 行

本文聚焦 Phase 1,实现一个功能完整的 WebSocket Gateway。


🔧 技术栈选择

  • 运行时:Node.js 22+(ESM 模块)
  • 语言:JavaScript(可升级 TypeScript)
  • WebSocket 库ws(轻量、高性能)
  • HTTP 框架:Hono(可选,用于 Web UI)

为什么选择 ws 而不是 Socket.IO

  1. 更轻量,无额外协议开销
  2. 原生 WebSocket,兼容性更好
  3. 性能更高,适合网关场景

📁 项目结构

perl 复制代码
my-claw/
├── src/
│   ├── index.js          # 主入口
│   ├── gateway/
│   │   └── index.js      # Gateway 核心实现
│   └── test-client.js    # 测试客户端
├── config/
├── dist/
├── package.json
└── ROADMAP.md

🚀 实现步骤

Step 1:初始化项目

bash 复制代码
mkdir my-claw && cd my-claw
npm init -y
npm install ws hono

修改 package.json

json 复制代码
{
  "name": "my-claw",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "dev": "node --watch src/index.js",
    "start": "node src/index.js"
  }
}

注意 "type": "module" 启用 ESM 模块系统。


Step 2:设计消息协议

网关的核心是消息协议。我们设计一个类似 JSON-RPC 的请求-响应协议:

typescript 复制代码
// 请求消息
{
  type: 'req',
  id: 'req-1',        // 请求 ID,用于匹配响应
  method: 'send',     // 方法名
  params: { ... }     // 参数
}

// 成功响应
{
  type: 'res',
  id: 'req-1',
  ok: true,
  payload: { ... }
}

// 错误响应
{
  type: 'res',
  id: 'req-1',
  ok: false,
  error: '错误信息'
}

// 事件消息(服务端主动推送)
{
  type: 'event',
  event: 'message',
  payload: { ... }
}

同时定义消息类型常量:

javascript 复制代码
const MessageType = {
  REQ: 'req',       // 请求
  RES: 'res',       // 响应
  EVENT: 'event',   // 事件
  CONNECT: 'connect', // 连接
  PING: 'ping',     // 心跳请求
  PONG: 'pong',     // 心跳响应
};

Step 3:实现 Gateway 类

Gateway 是整个系统的核心,负责:

  1. WebSocket 服务器:监听连接
  2. 客户端管理:存储连接状态
  3. 请求路由:分发请求到处理器
  4. 心跳检测:清理断开的连接
javascript 复制代码
class Gateway {
  constructor(options = {}) {
    this.port = options.port || 18790;
    this.host = options.host || '127.0.0.1';
    this.token = options.token || null;
    
    this.server = null;
    this.wss = null;
    
    // 客户端管理
    this.clients = new Map(); // clientId -> { ws, info, lastPing }
    
    // 会话管理
    this.sessions = new Map();
    
    // 请求处理器
    this.requestHandlers = new Map();
  }
}
3.1 启动服务器
javascript 复制代码
start() {
  // 创建 HTTP 服务器
  this.server = createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('MyClaw Gateway is running\n');
  });

  // 创建 WebSocket 服务器
  this.wss = new WebSocketServer({ server: this.server });

  // 监听连接
  this.wss.on('connection', (ws, req) => {
    this._handleNewConnection(ws, req);
  });

  // 启动监听
  this.server.listen(this.port, this.host, () => {
    console.log(`🦞 MyClaw Gateway started on ws://${this.host}:${this.port}`);
  });

  // 启动心跳检测
  this._startHeartbeat();
}
3.2 处理新连接
javascript 复制代码
_handleNewConnection(ws, req) {
  const clientId = randomUUID();
  
  console.log(`📱 New connection: ${clientId}`);

  // 存储客户端信息
  this.clients.set(clientId, {
    ws,
    info: null,
    lastPing: Date.now(),
    authenticated: false,
  });

  // 消息处理
  ws.on('message', (data) => {
    this._handleMessage(clientId, data);
  });

  // 关闭处理
  ws.on('close', () => {
    console.log(`📱 Connection closed: ${clientId}`);
    this.clients.delete(clientId);
  });
}
3.3 消息处理与路由
javascript 复制代码
async _handleMessage(clientId, data) {
  const client = this.clients.get(clientId);
  if (!client) return;

  try {
    const message = JSON.parse(data.toString());
    
    switch (message.type) {
      case 'req':
        await this._handleRequest(clientId, message);
        break;
      case 'event':
        this._handleEvent(clientId, message);
        break;
      default:
        this._sendError(client.ws, message.id, `Unknown type: ${message.type}`);
    }
  } catch (error) {
    this._sendError(client.ws, null, error.message);
  }
}

async _handleRequest(clientId, message) {
  const client = this.clients.get(clientId);
  const { id, method, params } = message;
  
  const handler = this.requestHandlers.get(method);
  
  if (!handler) {
    this._sendError(client.ws, id, `Unknown method: ${method}`);
    return;
  }

  try {
    const result = await handler(clientId, params);
    this._sendResponse(client.ws, id, result);
  } catch (error) {
    this._sendError(client.ws, id, error.message);
  }
}
3.4 心跳检测
javascript 复制代码
_startHeartbeat() {
  setInterval(() => {
    const now = Date.now();
    
    for (const [clientId, client] of this.clients) {
      // 超过 60 秒没收到心跳,断开连接
      if (now - client.lastPing > 60000) {
        console.log(`⏰ Client timeout: ${clientId}`);
        client.ws.terminate();
        this.clients.delete(clientId);
      }
    }
  }, 30000); // 每 30 秒检查一次
}

Step 4:实现请求处理器

Gateway 需要处理多种请求类型:

javascript 复制代码
_registerDefaultHandlers() {
  // 连接认证
  this.onRequest('connect', this._handleConnect.bind(this));
  // 心跳
  this.onRequest('ping', this._handlePing.bind(this));
  // 状态查询
  this.onRequest('status', this._handleStatus.bind(this));
  // 消息发送
  this.onRequest('send', this._handleSend.bind(this));
}
4.1 连接认证
javascript 复制代码
_handleConnect(clientId, params) {
  const client = this.clients.get(clientId);
  
  // Token 验证
  if (this.token && params?.auth?.token !== this.token) {
    throw new Error('Invalid token');
  }

  // 更新客户端信息
  client.info = {
    role: params?.role || 'client',
    name: params?.name || 'unknown',
    platform: params?.platform || 'unknown',
  };
  client.authenticated = true;

  return {
    clientId,
    status: 'connected',
    serverTime: new Date().toISOString(),
  };
}
4.2 状态查询
javascript 复制代码
_handleStatus(clientId, params) {
  return {
    status: 'running',
    uptime: process.uptime(),
    clients: this.clients.size,
    sessions: this.sessions.size,
    memory: process.memoryUsage(),
  };
}

Step 5:主入口

javascript 复制代码
// src/index.js
import { Gateway } from './gateway/index.js';

function parseArgs() {
  const args = process.argv.slice(2);
  const options = {};

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--port') {
      options.port = parseInt(args[++i], 10);
    } else if (args[i] === '--token') {
      options.token = args[++i];
    }
  }
  return options;
}

function main() {
  console.log('🦞 MyClaw v0.1.0');
  const options = parseArgs();
  const gateway = new Gateway(options);

  process.on('SIGINT', () => {
    gateway.stop();
    process.exit(0);
  });

  gateway.start();
}

main();

Step 6:测试客户端

javascript 复制代码
// src/test-client.js
import WebSocket from 'ws';

class TestClient {
  constructor(options = {}) {
    this.url = `ws://${options.host || '127.0.0.1'}:${options.port || 18790}`;
    this.ws = null;
    this.requestId = 0;
    this.pendingRequests = new Map();
  }

  connect() {
    return new Promise((resolve, reject) => {
      this.ws = new WebSocket(this.url);
      this.ws.on('open', resolve);
      this.ws.on('error', reject);
    });
  }

  request(method, params = {}) {
    return new Promise((resolve, reject) => {
      const id = `req-${++this.requestId}`;
      this.pendingRequests.set(id, { resolve, reject });

      this.ws.send(JSON.stringify({
        type: 'req',
        id,
        method,
        params,
      }));

      // 超时处理
      setTimeout(() => {
        if (this.pendingRequests.has(id)) {
          this.pendingRequests.delete(id);
          reject(new Error('Request timeout'));
        }
      }, 10000);
    });
  }
}

// 测试
async function runTests() {
  const client = new TestClient();
  await client.connect();

  // 测试连接
  const connectResult = await client.request('connect', {
    role: 'test-client',
    name: 'test-1',
  });
  console.log('Connect:', connectResult);

  // 测试状态
  const statusResult = await client.request('status');
  console.log('Status:', statusResult);

  client.close();
}

runTests();

🎯 运行测试

启动服务器:

bash 复制代码
node src/index.js --port 18790

运行测试客户端:

bash 复制代码
node src/test-client.js

输出:

css 复制代码
🔌 Connecting to ws://127.0.0.1:18790...
✅ Connected!

📋 Test 1: Connect
   Result: {
     "clientId": "xxx-xxx-xxx",
     "status": "connected",
     "serverTime": "2026-03-16T..."
   }

📋 Test 2: Status
   Status: {
     "status": "running",
     "uptime": 12.5,
     "clients": 1,
     "sessions": 0
   }

✅ All tests passed!

📊 本阶段成果

✅ 完成的功能:

  • WebSocket 服务器(ws 库)
  • 客户端连接管理
  • 请求-响应协议设计
  • 心跳检测机制
  • Token 认证支持
  • 测试客户端

📁 代码统计:

文件 行数
gateway/index.js ~200 行
index.js ~60 行
test-client.js ~80 行
总计 ~340 行

🔜 下期预告

Phase 2:消息系统

  • 设计消息路由架构
  • 实现会话管理器(SessionManager)
  • 支持消息广播
  • 添加消息队列

敬请期待!🦞


📚 参考资源


本文是「从零构建 AI 网关」系列的第一篇,完整代码见 GitHub 仓库

相关推荐
酉鬼女又兒2 小时前
HTML基础实例样式详解零基础快速入门Web开发(可备赛蓝桥杯Web应用开发赛道) 助力快速拿奖
前端·javascript·职场和发展·蓝桥杯·html·html5·web
Watermelo6172 小时前
【前端实战】构建 Vue 全局错误处理体系,实现业务与错误的清晰解耦
前端·javascript·vue.js·信息可视化·性能优化·前端框架·设计规范
A923A2 小时前
【Vue3大事件 | 项目笔记】第二天
前端·vue.js·笔记·前端框架·前端项目
万码社2 小时前
小程序开发实战:我手写日历组件踩过的那些坑
前端
工藤新一¹2 小时前
《操作系统》第一章(1)
java·服务器·前端
用户9714171814272 小时前
Flex 和 Grid 详细使用指南:从入门到实战避坑
前端·css
不会敲代码12 小时前
使用 Mock.js 模拟 API 数据,实现前后端并行开发
前端·javascript
琛説2 小时前
Web-Rooter:一种 IR + Lint 模式的 AI Agent 创新尝试【或许是下一个 AI 爆火方向】
前端·人工智能
用户9714171814273 小时前
absolute 元素的包含块(containing block)怎么找
前端·css