如何用NodeJS搭建适配Python异步WebSocket握手的服务器?
实现对接Python WebSocket客户端的Node.js服务器
不用慌,处理这种有序双向消息其实很简单——因为WebSocket基于TCP协议,消息本身就是按顺序传输的,我们只要在服务器端按照客户端发送消息的顺序依次接收、处理、响应就可以了。我推荐用ws这个最流行的Node.js WebSocket库来实现,下面直接给你完整的可运行代码,再拆解关键逻辑。
第一步:安装依赖
先安装ws库:
npm install ws
第二步:完整服务器代码
const WebSocket = require('ws'); const crypto = require('crypto'); // 必须和Python客户端保持一致的DH参数(g、N) const g = 2; const N = BigInt('ffffffffffffffffc90fdaa22168c234c4c6628b80dc1cd129024e088a67cc74020bbea63b139b22514a08798e3404ddef9519b3cd3a431b302b0a6df25f14374fe1356d6d51c245e485b576625e7ec6f44c42e9a637ed6b0bff5cb6f406b7edee386bfb5a899fa5ae9f24117c4b1fe649286651ece45b3dc2007cb8a163bf0598da48361c55d39a69163fa8fd24cf5f83655d23dca3ad961c62f356208552bb9ed529077096966d670c354e4abc9804f1746c08ca237327ffffffffffffffff'); // 创建WebSocket服务器,监听3000端口 const wss = new WebSocket.Server({ port: 3000 }); wss.on('connection', async (ws) => { console.log('新客户端连接'); try { // 1. 接收客户端发送的邮箱 const email = await waitForMessage(ws); console.log(`收到邮箱: ${email}`); // 2. 生成32字节随机数r,转十六进制发送给客户端 const r = crypto.randomBytes(32); ws.send(r.toString('hex')); // 3. 接收客户端发送的A(十六进制字符串),转成BigInt const AHex = await waitForMessage(ws); const A = BigInt(`0x${AHex}`); console.log(`收到A: ${AHex}`); // 4. 生成服务器端的b和B,将B转十六进制发送给客户端 const bBytes = crypto.randomBytes(32); const b = BigInt(`0x${bBytes.toString('hex')}`); const B = (BigInt(g) ** b) % N; ws.send(B.toString(16)); console.log(`发送B: ${B.toString(16)}`); // 5. 计算u、x、S(注意:实际应用中要从数据库取用户密码,这里为示例硬编码) const userPassword = 'user123'; // 替换为实际用户的密码 // 计算sha256(email:pw) const emailPwHash = crypto.createHash('sha256') .update(Buffer.from(`${email}:${userPassword}`)) .digest(); // 计算x = sha256(r + sha256(email:pw)) const xHash = crypto.createHash('sha256') .update(Buffer.concat([r, emailPwHash])) .digest(); const x = BigInt(`0x${xHash.toString('hex')}`); // 计算u = sha256(A字节 + B字节) const ABuf = hexToBigIntBuffer(A); const BBuf = hexToBigIntBuffer(B); const uHash = crypto.createHash('sha256') .update(Buffer.concat([ABuf, BBuf])) .digest(); const u = BigInt(`0x${uHash.toString('hex')}`); // 计算S = (B - g^x mod N)^(b + u*x) mod N const gxModN = (BigInt(g) ** x) % N; const base = (B - gxModN + N) % N; // 加N防止负数 const exponent = (b + u * x) % N; const S = (base ** exponent) % N; console.log(`计算得到S: ${S.toString(16)}`); // 6. 接收客户端发送的token,验证是否匹配H(A||B||S) const clientToken = await waitForMessage(ws); const SBuf = hexToBigIntBuffer(S); const expectedToken = crypto.createHash('sha256') .update(Buffer.concat([ABuf, BBuf, SBuf])) .digest('hex'); let result; if (clientToken === expectedToken) { result = 'success'; console.log('Token验证通过,握手成功'); } else { result = 'failure'; console.log('Token验证失败'); } // 7. 发送验证结果给客户端 ws.send(result); } catch (err) { console.error('握手过程出错:', err); ws.send('error'); ws.close(); } }); // 封装一个等待单条消息的工具函数,简化代码 function waitForMessage(ws) { return new Promise((resolve) => { ws.once('message', (data) => resolve(data.toString())); }); } // 把BigInt转成固定64字节的Buffer(和Python的int2bytes对齐) function hexToBigIntBuffer(num) { const hexStr = num.toString(16).padStart(64, '0'); return Buffer.from(hexStr, 'hex'); } console.log('WebSocket服务器运行在 ws://localhost:3000');
关键逻辑说明
1. 有序消息处理的核心
我封装了waitForMessage函数,用await依次等待每一条消息——这样就严格遵循了客户端的发送顺序:只有前一条消息处理完,才会等待下一条。加上WebSocket本身的TCP有序传输特性,完全不用担心消息乱序的问题。
2. 与Python客户端的参数对齐
- 确保
g和N这两个DH参数和Python客户端完全一致,否则密钥计算会彻底出错。 - 字节与十六进制的转换要和客户端对齐:比如Python里用
unhexlify接收r,所以服务器要发送十六进制字符串;Python用int(await websocket.recv(),16)接收B,服务器就发送B的十六进制字符串。 - 用Node.js的
BigInt处理大整数,避免普通Number的精度丢失问题。
3. 实际应用注意事项
- 密码存储:示例里硬编码了密码,实际要从数据库取用户的密码(如果按照客户端的逻辑,需要存储原始密码,但这很不安全——建议修改客户端逻辑,改用加盐哈希存储,服务器存储盐和哈希值,这样更安全)。
- 错误处理:代码里加了try-catch,避免单个连接的错误影响整个服务器。
- 连接管理:可以根据需求添加连接断开、心跳检测等逻辑。
内容的提问来源于stack exchange,提问作者Pro7ech
相关产品推荐
相关产品推荐

