转载请注明: TheViper http://www.cnblogs.com/TheViper
上一篇说到收到握手响应后的packet.type=open。接着是onHandshake()
Socket.prototype.onHandshake = function (data) { this.emit(‘handshake‘, data); this.id = data.sid; this.transport.query.sid = data.sid; this.upgrades = this.filterUpgrades(data.upgrades); this.pingInterval = data.pingInterval; this.pingTimeout = data.pingTimeout; this.onOpen(); // In case open handler closes socket if (‘closed‘ == this.readyState) return; this.setPing(); // Prolong liveness of socket on heartbeat this.removeListener(‘heartbeat‘, this.onHeartbeat); this.on(‘heartbeat‘, this.onHeartbeat); };
Socket.prototype.onOpen = function () { debug(‘socket open‘); this.readyState = ‘open‘; this.emit(‘open‘); this.flush(); };
Socket.prototype.flush = function () { if (‘closed‘ != this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { ....... this.transport.send(this.writeBuffer); } };
writeBuffer里面没有内容,不会走里面。
Polling.prototype.onData = function(data){ ..... // decode payload parser.decodePayload(data, this.socket.binaryType, callback); // if an event did not trigger closing if (‘closed‘ != this.readyState) { // if we got data we‘re not polling this.polling = false; this.emit(‘pollComplete‘); if (‘open‘ == this.readyState) { this.poll(); } else { debug(‘ignoring poll - transport state "%s"‘, this.readyState); } } };
新的get请求已经在之前onData里面的poll()建立了。
如果有数据,立刻用send,以post请求的方式,将数据传给服务端。比如,聊天的文字。
post请求除了用来维持心跳,还负责将客户端的数据传给服务端,长连接的get请求已经单独发出了,不能用其传递数据了,就只有用post请求了。
然后是setPing()
Socket.prototype.setPing = function () { var self = this; clearTimeout(self.pingIntervalTimer); self.pingIntervalTimer = setTimeout(function () { debug(‘writing ping packet - expecting pong within %sms‘, self.pingTimeout); self.ping(); self.onHeartbeat(self.pingTimeout); }, self.pingInterval); };
ping()用来向服务端发送心跳。
Socket.prototype.ping = function () { this.sendPacket(‘ping‘); };
至此,握手结束。
话说握手的时候,服务端会setPingTimeout();
Socket.prototype.setPingTimeout = function () { var self = this; clearTimeout(self.pingTimeoutTimer); self.pingTimeoutTimer = setTimeout(function () { self.onClose(‘ping timeout‘); }, self.server.pingTimeout); };
服务端会看在pingTimeout时间内,客户端有没有传送post请求,证明自己还在。
而客户端,在握手成功后,会setPing(),这个上面有。
然后等啊等,重要到pingInterval时间了,客户端发送post请求,this.sendPacket(‘ping‘);
另外,如果是客户端主动发送数据的话。
Socket.prototype.write = Socket.prototype.send = function (msg, fn) { this.sendPacket(‘message‘, msg, fn); return this; };
可以看到也是调用了sendPacket.
Socket.prototype.sendPacket = function (type, data, fn) { if (‘closing‘ == this.readyState || ‘closed‘ == this.readyState) { return; } var packet = { type: type, data: data }; this.emit(‘packetCreate‘, packet); this.writeBuffer.push(packet); this.callbackBuffer.push(fn); this.flush(); };
Socket.prototype.flush = function () { if (‘closed‘ != this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { debug(‘flushing %d packets in socket‘, this.writeBuffer.length); this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer // splice writeBuffer and callbackBuffer on `drain` this.prevBufferLen = this.writeBuffer.length; this.emit(‘flush‘); } };
然后flush()
Transport.prototype.send = function(packets){ if (‘open‘ == this.readyState) { this.write(packets); } else { throw new Error(‘Transport not open‘); } };
Polling.prototype.write = function(packets){ var self = this; this.writable = false; var callbackfn = function() { self.writable = true; self.emit(‘drain‘); }; var self = this; parser.encodePayload(packets, this.supportsBinary, function(data) { self.doWrite(data, callbackfn); }); };
XHR.prototype.doWrite = function(data, fn){ var isBinary = typeof data !== ‘string‘ && data !== undefined; var req = this.request({ method: ‘POST‘, data: data, isBinary: isBinary }); var self = this; req.on(‘success‘, fn); req.on(‘error‘, function(err){ self.onError(‘xhr post error‘, err); }); this.sendXhr = req; };
注意,这里request绑定了success事件,这个后面收到响应后会用到。
服务端收到ping后,结束长连接的get请求,并通过它发回pong响应。
xhr.onreadystatechange = function(){ if (4 != xhr.readyState) return; if (200 == xhr.status || 1223 == xhr.status) { self.onLoad(); } else { // make sure the `error` event handler that‘s user-set // does not throw in the same tick and gets caught here setTimeout(function(){ self.onError(xhr.status); }, 0); } };
Request.prototype.onLoad = function(){ var data; try { var contentType; try { contentType = this.xhr.getResponseHeader(‘Content-Type‘).split(‘;‘)[0]; } catch (e) {} if (contentType === ‘application/octet-stream‘) { data = this.xhr.response; } else { if (!this.supportsBinary) { data = this.xhr.responseText; } else { data = ‘ok‘; } } } catch (e) { this.onError(e); } if (null != data) { this.onData(data); } };
onLoad()取回发回的数据。
Request.prototype.onSuccess = function(){ this.emit(‘success‘); this.cleanup(); }; Request.prototype.onData = function(data){ this.emit(‘data‘, data); this.onSuccess(); };
注意,get请求上绑定data事件,用来接收数据;post请求上绑定success事件,用来确定接收服务端心跳成功。
对get请求,this.emit(‘data‘, data);这个在前面说收到握手响应的时候说过。只是最后解析出来的type是pong。
Socket.prototype.onPacket = function (packet) { if (‘opening‘ == this.readyState || ‘open‘ == this.readyState) { debug(‘socket receive: type "%s", data "%s"‘, packet.type, packet.data); this.emit(‘packet‘, packet); // Socket is live - any packet counts this.emit(‘heartbeat‘); switch (packet.type) { case ‘open‘: this.onHandshake(parsejson(packet.data)); break; case ‘pong‘: this.setPing(); break; case ‘error‘: var err = new Error(‘server error‘); err.code = packet.data; this.emit(‘error‘, err); break; case ‘message‘: this.emit(‘data‘, packet.data); this.emit(‘message‘, packet.data); break; } } else { debug(‘packet received with socket readyState "%s"‘, this.readyState); } };
然后setPing()设置发出post请求的定时器。
对post请求的响应。注意onSuccess()里面this.emit(‘success‘);。这个在前面说发出post请求时,说到在request上绑定了success事件,这里就触发。回调函数是
var callbackfn = function() { self.writable = true; self.emit(‘drain‘); };
transport .on(‘drain‘, function(){ self.onDrain(); })
Socket.prototype.onDrain = function() { ... this.writeBuffer.splice(0, this.prevBufferLen); this.callbackBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important // for example, when upgrading, upgrade packet is sent over, // and a nonzero prevBufferLen could cause problems on `drain` this.prevBufferLen = 0; if (this.writeBuffer.length == 0) { this.emit(‘drain‘); } else { this.flush(); } };
onDrain()里面会判断writeBuffer里有没有数据。道理和服务端onPollRequest()里面的this.emit("drain")一样,为了实时性。
最后说下,客户端新的get长连接请求是在什么时候发出的。
在解析get请求的响应时,self.onPacket()后并没有完,会调用poll()->doPoll().
Polling.prototype.onData = function(data){ var self = this; debug(‘polling got data %s‘, data); var callback = function(packet, index, total) { // if its the first message we consider the transport open if (‘opening‘ == self.readyState) { self.onOpen(); } // if its a close packet, we close the ongoing requests if (‘close‘ == packet.type) { self.onClose(); return false; } // otherwise bypass onData and handle the message self.onPacket(packet); }; // decode payload parser.decodePayload(data, this.socket.binaryType, callback); // if an event did not trigger closing if (‘closed‘ != this.readyState) { // if we got data we‘re not polling this.polling = false; this.emit(‘pollComplete‘); if (‘open‘ == this.readyState) { this.poll(); } else { debug(‘ignoring poll - transport state "%s"‘, this.readyState); } } };
至此,engine.io的客户端和服务端都简单的分析完了。
而里面的传输方式升级(polling->websocket),两端的jsonp传输方式具体的执行,由于本屌时间精力有限,就没有做了。
如果前面的东西理解的话,这些分析其实一点都不难。
最后,可以看到socket.io 1.x在engine.io上加了不少东西,比如,broadcast,room,namespace等,看过这几篇文章后,相信这些加上去的东西也不难分析了。