从零构建 AI 网关(一):WebSocket 服务器实战
本系列文章将带你从零构建一个类似 OpenClaw 的多渠道 AI 网关系统。本文是第一篇,重点讲解 WebSocket 服务器的实现。
🎯 项目背景
在 AI 应用开发中,我们经常需要将 AI 智能体接入多个聊天平台(Telegram、Discord、微信等)。这就需要一个网关系统来:
- 统一消息协议:不同平台的 API 差异很大
- 管理连接状态:处理掉线、重连、心跳
- 路由消息:将消息分发给正确的处理器
- 接入 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?
- 更轻量,无额外协议开销
- 原生 WebSocket,兼容性更好
- 性能更高,适合网关场景
📁 项目结构
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 是整个系统的核心,负责:
- WebSocket 服务器:监听连接
- 客户端管理:存储连接状态
- 请求路由:分发请求到处理器
- 心跳检测:清理断开的连接
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 仓库。