服务端
const http2 = require('http2');
const { streamToBuffer } = require('./buffer')
// 创建h2c服务器[3](@ref)
const server = http2.createServer();
// 注册RPC方法
const methods = {
add: (...nums) => nums.reduce((a, b) => a + b, 0),
add: (...nums) => nums.reduce((a, b) => a + b, 0),
getUser: (id) => ({id, name: `User${id}`})
};
// 多路复用处理[7](@ref)
server.on('stream', (stream, headers) => {
const methodPath = headers[':path'].substr(1); // 获取/rpc/add形式路径
streamToBuffer(stream).then((bodyBuffer)=>{
try {
const { params } = JSON.parse(bodyBuffer.toString());
const result = methods[methodPath](...params);
// 发送响应(复用同一TCP连接)
stream.respond({
':status': 200,
'content-type': 'application/json'
});
console.log(methodPath, JSON.stringify({params, result}));
stream.end(JSON.stringify({ result }));
} catch (error) {
stream.respond({ ':status': 500 });
stream.end(JSON.stringify({ error: e.message }));
}
},()=> {
console.error('Unhandled error');
});
});
server.listen(8080, () => {
console.log('h2c RPC server running on http://localhost:8080');
});
客户端
const http2 = require('http2');
const {streamToBuffer} = require('./buffer')
const CONNECTION_STATUS = {
"DISCONNECTED": 'DISCONNECTED',
"CONNECTED": 'CONNECTED',
"SCHEDULE_RECONNECT": 'SCHEDULE_RECONNECT',
"CONNECTING": 'CONNECTING',
"ERROR": 'ERROR',
"CLOSED": 'CLOSED',
}
class H2AutoReconnectClient {
constructor(host, options = {}) {
this.host = host;
this.connection = null;
this.requestQueue = []; // 连接中断期间的请求队列
this.reconnectOptions = {
baseInterval: 1000, // 基础重连间隔
maxInterval: 1000*60, // 最大重连间隔
maxAttempts: 1000000, // 最大重连尝试次数
backoffFactor: 1.5, // 指数退避因子
...options
};
this.connectionStatus = CONNECTION_STATUS.DISCONNECTED
this.reconnectAttempts = 0;
this._initializeConnection();
}
_connectionOnError = (err) => {
this.connectionStatus = CONNECTION_STATUS.ERROR;
console.error(`连接异常: ${err.message}`);
this._scheduleReconnect();
}
_connectionOnClose = () => {
console.log('连接已关闭');
this.connectionStatus = CONNECTION_STATUS.CLOSED;
this._scheduleReconnect();
}
_connectionOnConnect = () => {
console.log('HTTP/2连接已建立');
this.reconnectAttempts = 0;
this.connectionStatus = CONNECTION_STATUS.CONNECTED;
this._processQueue();
}
// 初始化连接(带自动重连)
_initializeConnection() {
if (this.connectionStatus === CONNECTION_STATUS.CONNECTING ||
this.connectionStatus === CONNECTION_STATUS.CONNECTED
) {
return;
}
this.connectionStatus = CONNECTION_STATUS.CONNECTING;
if (this.connection) {
console.log('清理旧连接');
this.connection.off('connect', this._connectionOnConnect);
this.connection.off('error', this._connectionOnError);
this.connection.off('close', this._connectionOnClose);
this.connection.destroy(); // 清理旧连接
this.connection = null
}
this.connection = http2.connect(`http://${this.host}`, {allowHTTP2: true});
this.connection.on('connect', this._connectionOnConnect);
this.connection.on('error', this._connectionOnError);
this.connection.on('close', this._connectionOnClose);
}
// 调度重连(指数退避算法)
_scheduleReconnect() {
if (this.connectionStatus === CONNECTION_STATUS.SCHEDULE_RECONNECT ||
this.connectionStatus === CONNECTION_STATUS.CONNECTING
) {
return;
}
this.connectionStatus = CONNECTION_STATUS.SCHEDULE_RECONNECT;
if (this.reconnectAttempts >= this.reconnectOptions.maxAttempts) {
console.error('达到最大重连次数');
return;
}
const delay = Math.min(
this.reconnectOptions.baseInterval * Math.pow(this.reconnectOptions.backoffFactor, this.reconnectAttempts),
this.reconnectOptions.maxInterval
);
this.reconnectAttempts++;
console.log(`将在${delay}ms后尝试第${this.reconnectAttempts}次重连`);
setTimeout(() => {
this._initializeConnection();
}, delay);
}
// 处理待发送请求队列
_processQueue() {
setTimeout(() => {
(async () => {
const requestQueue = this.requestQueue;
this.requestQueue = [];
for (let i = 0; i < requestQueue.length; i++) {
const requestQueueElement = requestQueue[i];
const {method, params, resolve, reject} = requestQueueElement
await this._executeRequest(method, params).then(resolve).catch(reject);
}
})()
}, 10)
}
// 执行实际请求
async _executeRequest(method, params) {
const stream = this.connection.request({
':method': 'POST',
':path': `/${method}`,
'content-type': 'application/json'
});
stream.end(JSON.stringify({params}));
const buffer = await streamToBuffer(stream);
const data = buffer.toString();
let result
try {
result = JSON.parse(data).result
return JSON.stringify({result, params});
} catch (e) {
console.error(`parse result error ${e.message}`, data);
}
}
// 对外暴露的调用接口
async call(method, ...params) {
if (!this.connection || this.connection.destroyed || CONNECTION_STATUS.CONNECTED !== this.connectionStatus) {
return new Promise((resolve, reject) => {
this.requestQueue.push({method, params, resolve, reject});
this._scheduleReconnect();
});
}
return this._executeRequest(method, params);
}
}
// 初始化客户端(带重试配置)
const client = new H2AutoReconnectClient('localhost:8080', {});
let reqId = 1;
// 批量调用测试
setInterval(() => {
reqId++;
// 业务调用(自动处理重连)
client.call('add', reqId, 1, 2, 3)
.then(console.log)
.catch(console.error);
}, 100);
工具函数
function streamToBuffer(stream, encoding = 'utf8') {
return new Promise((resolve, reject) => {
const chunks = [];
let totalLength = 0;
// 统一处理chunk类型
stream.on('data', (chunk) => {
let bufferChunk;
// 类型判断逻辑[1,4](@ref)
if (Buffer.isBuffer(chunk)) {
bufferChunk = chunk;
} else if (typeof chunk === 'string') {
bufferChunk = Buffer.from(chunk, encoding);
} else {
return reject(new Error('Unsupported chunk type'));
}
chunks.push(bufferChunk);
totalLength += bufferChunk.length;
});
// 流结束处理
stream.on('end', () => {
resolve(Buffer.concat(chunks, totalLength)); // 高效合并[2,4](@ref)
});
// 错误处理
stream.on('error', reject);
});
}
module.exports = {
streamToBuffer,
}