http2实现的简单rpc

服务端

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,
}

  

 

posted on 2025-05-05 00:34  袜子破了  阅读(29)  评论(0)    收藏  举报