最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Node.js:RPC通信

    正文概述 掘金(路明非_)   2021-02-24   549

    RPC调用

    和ajax相同点

    第一个相同点:总之都是两个设备之间到调用
    1. ajax是浏览器到服务器
    2. rpc是服务器到服务器
    
    第二个相同点:需要双方约定一种数据格式
    

    和ajax不同点

    第一点:
    1. ajax使用dns做为寻址服务到
    2. rpc一般是在内网寻址
    第二点:
    1. ajax应用层使用http(html/json)
    2. rpc通信到时候一般会使用二进制协议,性能优势
    第三点
    rcp是基于tcp或udp协议通信
    
    

    寻址/负载均衡

    ajax寻址过程是拿域名去dns进行解析,然后在发起真正请求
    
    rpc发起网络请求之前也是需要进行寻址到,因为不一定会使用ip进行请求,
    可能会使用id(l5,vip)之类的统一标识符,拿这个id去寻址服务器解析,然后通过返回的ip发起真正的请求
    

    TCP通信

    单工通信:
    永远只能是一端往另一端发送数据(client -- server)
    
    半双工通信:
    双方都可以发数据,但同一时间内只有一端可以发送数据,也可以理解为‘轮番单工通信’
    
    全双工通信:
    在client发送数据的时候,server也可以发送数据
    

    二进制协议

    . 更小的数据包体积
    . 更快的编码速度
    rpc [0001 0000 1111 0101]
    

    Node.js Buffer编解码二进制数据包

    buffer模块介绍

    创建buffer的三种方式:
    1. Buffer.alloc
    2. Buffer.from
    3. Buffer.allocUnsafe
    
    // from方法表示通过现有的数据结构创建一个buffer
    const buffer1 = Buffer.from('test');
    const buffer2 = Buffer.from([1, 2, 3]);
    
    // alloc方法表示通过指定长度创建一个buffer·
    const buffer3 = Buffer.alloc(10);
    
    console.log(buffer1); // <Buffer 74 65 73 74>
    console.log(buffer2); // <Buffer 01 02 03>
    console.log(buffer3); // <Buffer 00 00 00 00 00 00 00 00 00 00>
    

    Buffer.allocUnsafe:该方法会涉及buffer模块的内存管理机制

    buffer写入的方法:
    
    const buffer2 = Buffer.from([1, 2, 3]);
    
    buffer2.writeInt8(12, 0)
    console.log(buffer2); // <Buffer 0c 02 03>
    
    buffer2.writeInt16BE(512, 1)
    console.log(buffer2);
    // <Buffer 0c 02 00> BE/LE的区别是 高位和地位的排布顺序,请仔细观察该行和下面的结果区别
    
    
    buffer2.writeInt16LE(512, 1)
    console.log(buffer2); // <Buffer 0c 00 02>  00 02 和上面  02 00
    
    使用BE/LE的原因在于不同设备会使用不同标准,具体要使用什么参数需要跟后台协商
    

    protocol buffers库:

    可以达到类似JSON.stringfly这样简单的操作,不需要像上面那样麻烦的写二进制
    
    https://github.com/protocolbuffers/protobuf
    这个兼容了前端情况,所以没有上面那个好
    
    // test.proto
    
    message Test {
      required float num  = 1;
      required string payload = 2;
    }
    
    // index.js
    
    const fs = require('fs');
    const protobuf = require('protocol-buffers');
    const schema = protobuf(fs.readFileSync(__dirname + '/test.proto', 'utf-8'));
    
    console.log(schema)
    // Messages {
    //   Test: {
    //     type: 2,
    //     message: true,
    //     name: 'Test',
    //     buffer: true,
    //     encode: [Function: encode] { bytes: 0 },
    //     decode: [Function: decode] { bytes: 0 },
    //     encodingLength: [Function: encodingLength],
    //     dependencies: [ [Object], [Object] ]
    //   }
    // }
    const buffer = schema.Test.encode({
      num: 2,
      payload: 'test'
    })
    console.log(buffer) // <Buffer 0d 00 00 00 40 12 04 74 65 73 74>
    console.log(schema.Test.decode(buffer)) // { num: 2, payload: 'test' }
    

    Node.js net建立多路复用的rpc通道

    net模块

    net模块类似http模块、比较简单
    
    既然是通道,就会有两端,这里我们用clinet和server来代表
    

    单工通信案例

    Node.js:RPC通信

    半双工通信案例

    Node.js:RPC通信

    全双工通信

    • server.js
    const net = require('net');
    
    const server = net.createServer((socket) => {
    
        let oldBuffer = null;
        socket.on('data', function (buffer) {
            // 把上一次data事件使用残余的buffer接上来
            if (oldBuffer) {
                buffer = Buffer.concat([oldBuffer, buffer]);
            }
    
            let packageLength = 0;
            // 只要还存在可以解成完整包的包长
            while (packageLength = checkComplete(buffer)) {
                const package = buffer.slice(0, packageLength);
                buffer = buffer.slice(packageLength);
    
                // 把这个包解成数据和seq
                const result = decode(package);
    
                // 计算得到要返回的结果,并write返回
                socket.write(
                    encode(LESSON_DATA[result.data], result.seq)
                );
            }
    
            // 把残余的buffer记下来
            oldBuffer = buffer;
        })
    
    });
    
    server.listen(4000);
    
    /**
     * 二进制包编码函数
     * 在一段rpc调用里,服务端需要经常编码rpc调用时,业务数据的返回包
     */
    function encode(data, seq) {
        // 正常情况下,这里应该是使用 protobuf 来encode一段代表业务数据的数据包
        // 为了不要混淆重点,这个例子比较简单,就直接把课程标题转buffer返回
        const body = Buffer.from(data)
    
        // 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分
        // 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信
        const header = Buffer.alloc(6);
        header.writeInt16BE(seq)
        header.writeInt32BE(body.length, 2);
    
        const buffer = Buffer.concat([header, body])
    
        return buffer;
    }
    
    /**
     * 二进制包解码函数
     * 在一段rpc调用里,服务端需要经常解码rpc调用时,业务数据的请求包
     */
    function decode(buffer) {
        const header = buffer.slice(0, 6);
        const seq = header.readInt16BE();
    
        // 正常情况下,这里应该是使用 protobuf 来decode一段代表业务数据的数据包
        // 为了不要混淆重点,这个例子比较简单,就直接读一个Int32即可
        const body = buffer.slice(6).readInt32BE()
    
        // 这里把seq和数据返回出去
        return {
            seq,
            data: body
        }
    }
    
    /**
     * 检查一段buffer是不是一个完整的数据包。
     * 具体逻辑是:判断header的bodyLength字段,看看这段buffer是不是长于header和body的总长
     * 如果是,则返回这个包长,意味着这个请求包是完整的。
     * 如果不是,则返回0,意味着包还没接收完
     * @param {} buffer 
     */
    function checkComplete(buffer) {
        if (buffer.length < 6) {
            return 0;
        }
        const bodyLength = buffer.readInt32BE(2);
        return 6 + bodyLength
    }
    
    // 假数据
    const LESSON_DATA = {
        136797: "01 | 课程介绍",
        136798: "02 | 内容综述",
        136799: "03 | Node.js是什么?",
        136800: "04 | Node.js可以用来做什么?",
        136801: "05 | 课程实战项目介绍",
        136803: "06 | 什么是技术预研?",
        136804: "07 | Node.js开发环境安装",
        136806: "08 | 第一个Node.js程序:石头剪刀布游戏",
        136807: "09 | 模块:CommonJS规范",
        136808: "10 | 模块:使用模块规范改造石头剪刀布游戏",
        136809: "11 | 模块:npm",
        141994: "12 | 模块:Node.js内置模块",
        143517: "13 | 异步:非阻塞I/O",
        143557: "14 | 异步:异步编程之callback",
        143564: "15 | 异步:事件循环",
        143644: "16 | 异步:异步编程之Promise",
        146470: "17 | 异步:异步编程之async/await",
        146569: "18 | HTTP:什么是HTTP服务器?",
        146582: "19 | HTTP:简单实现一个HTTP服务器"
    }
    
    • client.js
    const net = require('net');
    
    const socket = new net.Socket({});
    
    socket.connect({
        host: '127.0.0.1',
        port: 4000
    });
    
    const LESSON_IDS = [
      "136797",
      "136798",
      "136799",
      "136800",
      "136801",
      "136803",
      "136804",
      "136806",
      "136807",
      "136808",
      "136809",
      "141994",
      "143517",
      "143557",
      "143564",
      "143644",
      "146470",
      "146569",
      "146582"
    ]
    
    let id = Math.floor(Math.random() * LESSON_IDS.length);
    
    let oldBuffer = null;
    socket.on('data', (buffer) => {
        // 把上一次data事件使用残余的buffer接上来
        if (oldBuffer) {
            buffer = Buffer.concat([oldBuffer, buffer]);
        }
        let completeLength = 0;
    
        // 只要还存在可以解成完整包的包长
        while (completeLength = checkComplete(buffer)) {
            const package = buffer.slice(0, completeLength);
            buffer = buffer.slice(completeLength);
    
            // 把这个包解成数据和seq
            const result = decode(package);
            console.log(`包${result.seq},返回值是${result.data}`);
        }
    
        // 把残余的buffer记下来
        oldBuffer = buffer;
    })
    
    
    let seq = 0;
    /**
     * 二进制包编码函数
     * 在一段rpc调用里,客户端需要经常编码rpc调用时,业务数据的请求包
     */
    function encode(data) {
        // 正常情况下,这里应该是使用 protobuf 来encode一段代表业务数据的数据包
        // 为了不要混淆重点,这个例子比较简单,就直接把课程id转buffer发送
        const body = Buffer.alloc(4);
        body.writeInt32BE(LESSON_IDS[data.id]);
    
        // 一般来说,一个rpc调用的数据包会分为定长的包头和不定长的包体两部分
        // 包头的作用就是用来记载包的序号和包的长度,以实现全双工通信
        const header = Buffer.alloc(6);
        header.writeInt16BE(seq)
        header.writeInt32BE(body.length, 2);
    
        // 包头和包体拼起来发送
        const buffer = Buffer.concat([header, body])
    
        console.log(`包${seq}传输的课程id为${LESSON_IDS[data.id]}`);
        seq++;
        return buffer;
    }
    
    /**
     * 二进制包解码函数
     * 在一段rpc调用里,客户端需要经常解码rpc调用时,业务数据的返回包
     */
    function decode(buffer) {
        const header = buffer.slice(0, 6);
        const seq = header.readInt16BE();
    
        const body = buffer.slice(6)
    
        return {
            seq,
            data: body.toString()
        }
    }
    
    /**
     * 检查一段buffer是不是一个完整的数据包。
     * 具体逻辑是:判断header的bodyLength字段,看看这段buffer是不是长于header和body的总长
     * 如果是,则返回这个包长,意味着这个请求包是完整的。
     * 如果不是,则返回0,意味着包还没接收完
     * @param {} buffer 
     */
    function checkComplete(buffer) {
        if (buffer.length < 6) {
            return 0;
        }
        const bodyLength = buffer.readInt32BE(2);
        return 6 + bodyLength
    }
    
    
    for (let k = 0; k < 100; k++) {
        id = Math.floor(Math.random() * LESSON_IDS.length);
        socket.write(encode({ id }));
    }
    

    起源地下载网 » Node.js:RPC通信

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元