socket.io
https://socket.io/javascript
https://socket.io/docs/html
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:java
- 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-appropriategit
Recently I have been messing around with socket.io and found this interesting thing, that I can have emit function callback like this.github
I start emitting on client side like this:api
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.服務器
見老外的疑惑,也是本篇的主題, 爲何服務器端可以直接調用客戶端設置的回調函數。app
跨進程能夠調用函數,真是稀奇。 相似RPC。socket
官方文檔的解釋
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(); |
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 };
至此實現上完全明瞭了。