• socket.io emit callback调用探秘


    socket.io

    https://socket.io/

    https://socket.io/docs/

    What Socket.IO is

    Socket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server. It consists of:

    • a Node.js server: Source | API
    • a Javascript client library for the browser (which can be also run from Node.js): Source | API

    emit callback 用法

    https://stackoverflow.com/questions/20337832/is-socket-io-emit-callback-appropriate

    Recently I have been messing around with socket.io and found this interesting thing, that I can have emit function callback like this.

    I start emitting on client side like this:

    client.emit('eventToEmit', dataToEmit, function(error, message){
        console.log(error);
        console.log(message);
    });
    

    Then I can fire a callback from server-side like this:

    client.on('eventToEmit', function(data, callback){
        console.log(data);
        callback('error', 'message');
    });
    

    Everything works fine with no errors, but I am interested if doing something like this is appropriate since I have not seen anything similar in the documentation or any example so far.

    见老外的疑惑,也是本篇的主题, 为什么服务器端能够直接调用客户端设置的回调函数。

    跨进程可以调用函数,真是稀奇。 类似RPC。

    官方文档的解释

    https://socket.io/docs/#Sending-and-getting-data-acknowledgements

    Sending and getting data (acknowledgements)

    Sometimes, you might want to get a callback when the client confirmed the message reception.

    To do this, simply pass a function as the last parameter of .send or .emit. What’s more, when you use .emit, the acknowledgement is done by you, which means you can also pass data along:

    Server (app.js)

    var io = require('socket.io')(80);

    io.on('connection', function (socket) {
    socket.on('ferret', function (name, word, fn) {
    fn(name + ' says ' + word);
    });
    });

    Client (index.html)

    <script>
    var socket = io(); // TIP: io() with no args does auto-discovery
    socket.on('connect', function () { // TIP: you can avoid listening on `connect` and listen on events directly too!
    socket.emit('ferret', 'tobi', 'woot', function (data) { // args are sent in order to acknowledgement function
    console.log(data); // data will be 'tobi says woot'
    });
    });
    </script>

    client-代码跟踪

    https://github.com/socketio/socket.io-client

    以客户端源码为研究对象。

    在socket.js文件中,存在emit实现:

    如下代码中, 17-20行代码中, 会将callback函数存储到本地的acks数组中, 并将基数记为 packet.id,

    然后packet作为数据整体,传送的服务器端。

     1 Socket.prototype.emit = function (ev) {
     2   if (events.hasOwnProperty(ev)) {
     3     emit.apply(this, arguments);
     4     return this;
     5   }
     6 
     7   var args = toArray(arguments);
     8   var packet = {
     9     type: (this.flags.binary !== undefined ? this.flags.binary : hasBin(args)) ? parser.BINARY_EVENT : parser.EVENT,
    10     data: args
    11   };
    12 
    13   packet.options = {};
    14   packet.options.compress = !this.flags || false !== this.flags.compress;
    15 
    16   // event ack callback
    17   if ('function' === typeof args[args.length - 1]) {
    18     debug('emitting packet with ack id %d', this.ids);
    19     this.acks[this.ids] = args.pop();
    20     packet.id = this.ids++;
    21   }
    22 
    23   if (this.connected) {
    24     this.packet(packet);
    25   } else {
    26     this.sendBuffer.push(packet);
    27   }
    28 
    29   this.flags = {};
    30 
    31   return this;
    32 };

    服务器端处理完数据后, 调用callback接口后,服务器端调用的接口为包装接口, 包装了数据为packet, 并将id打在packet上, 表示此packet为emit时候的packet对应。

    服务器端数据到来后, 根据packet.id定位到 callback函数, 并将packet.data作为参数传递到callback中。

    /**
     * Called upon a server acknowlegement.
     *
     * @param {Object} packet
     * @api private
     */
    
    Socket.prototype.onack = function (packet) {
      var ack = this.acks[packet.id];
      if ('function' === typeof ack) {
        debug('calling ack %s with %j', packet.id, packet.data);
        ack.apply(this, packet.data);
        delete this.acks[packet.id];
      } else {
        debug('bad ack %s', packet.id);
      }
    };

    server-代码跟踪

    https://github.com/socketio/socket.io

    socket.js中 在onevent中 在数据的args数组之后, 添加了 acknowledge 回调函数

     1 /**
     2  * Called upon event packet.
     3  *
     4  * @param {Object} packet object
     5  * @api private
     6  */
     7 
     8 Socket.prototype.onevent = function(packet){
     9   var args = packet.data || [];
    10   debug('emitting event %j', args);
    11 
    12   if (null != packet.id) {
    13     debug('attaching ack callback to event');
    14     args.push(this.ack(packet.id));
    15   }
    16 
    17   this.dispatch(args);
    18 };
    19 
    20 /**
    21  * Produces an ack callback to emit with an event.
    22  *
    23  * @param {Number} id packet id
    24  * @api private
    25  */
    26 
    27 Socket.prototype.ack = function(id){
    28   var self = this;
    29   var sent = false;
    30   return function(){
    31     // prevent double callbacks
    32     if (sent) return;
    33     var args = Array.prototype.slice.call(arguments);
    34     debug('sending ack %j', args);
    35 
    36     self.packet({
    37       id: id,
    38       type: hasBin(args) ? parser.BINARY_ACK : parser.ACK,
    39       data: args
    40     });
    41 
    42     sent = true;
    43   };
    44 };

    在 dispatch 负责调用 emitter 原生接口 on 绑定的 事件处理函数:


    /**
     * `EventEmitter#emit` reference.
     */

    var emit = Emitter.prototype.emit;

     1 
    /** 2 * Dispatch incoming event to socket listeners. 3 * 4 * @param {Array} event that will get emitted 5 * @api private 6 */ 7 8 Socket.prototype.dispatch = function(event){ 9 debug('dispatching an event %j', event); 10 var self = this; 11 function dispatchSocket(err) { 12 process.nextTick(function(){ 13 if (err) { 14 return self.error(err.data || err.message); 15 } 16 emit.apply(self, event); 17 }); 18 } 19 this.run(event, dispatchSocket); 20 }; 21 22 /** 23 * Sets up socket middleware. 24 * 25 * @param {Function} middleware function (event, next) 26 * @return {Socket} self 27 * @api public 28 */ 29 30 Socket.prototype.use = function(fn){ 31 this.fns.push(fn); 32 return this; 33 }; 34 35 /** 36 * Executes the middleware for an incoming event. 37 * 38 * @param {Array} event that will get emitted 39 * @param {Function} last fn call in the middleware 40 * @api private 41 */ 42 Socket.prototype.run = function(event, fn){ 43 var fns = this.fns.slice(0); 44 if (!fns.length) return fn(null); 45 46 function run(i){ 47 fns[i](event, function(err){ 48 // upon error, short-circuit 49 if (err) return fn(err); 50 51 // if no middleware left, summon callback 52 if (!fns[i + 1]) return fn(null); 53 54 // go on to next 55 run(i + 1); 56 }); 57 } 58 59 run(0); 60 };

    至此实现上彻底明了了。

  • 相关阅读:
    docker/kubernetes国内源/镜像源解决方式
    logstash的filter之grok
    基于ambari搭建hadoop生态圈大数据组件
    Hadoop整体概述
    flume的配置详解
    Zabbix错误”zbx_mem_malloc(): out of memory”解决方法
    Windows环境下的RTKPlot_Qt版本编译时遇到的问题和解决方法
    基于 win7下虚拟机的 GNSS-SDR安装过程
    博客文章搬迁同步声明
    RTKLIB源码解析(一)——单点定位(pntpos.c)
  • 原文地址:https://www.cnblogs.com/lightsong/p/10226940.html
Copyright © 2020-2023  润新知