engine.io客户端分析2--socket.io的基石

转载请注明: 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等,看过这几篇文章后,相信这些加上去的东西也不难分析了。

时间: 2024-11-06 07:21:30

engine.io客户端分析2--socket.io的基石的相关文章

websocket 实现 前端vue-socket.io 服务端 koa2(socket.io)

前端:(vue项目,main.js) // The Vue build version to load with the `import` command // (runtime-only or standalone) has been set in webpack.base.conf with an alias. import Vue from 'vue' import App from './App' import router from './router' // import VueNa

engine.io客户端分析1--socket.io的基石

转载请注明: TheViper http://www.cnblogs.com/TheViper  var socket = eio('http://localhost:8000'); socket.on('open', function(){ socket.on('message', function(data){ console.log(data); }); socket.on('close', function(){}); }); 源码很简单. socket.js function Sock

基于socket.io客户端与服务端的相互通讯

socket.io是对websocket的封装,用于客户端与服务端的相互通讯.官网:https://socket.io/. 下面是socket.io的用法: 1.由于使用express开的本地服务,先下载相关依赖 cnpm install express socket.io 2.服务端代码 const express = require("express"); const io = require("socket.io"); const app = express(

Socket.IO 概述

为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/SJQ. http://www.cnblogs.com/shijiaqi1066/p/3826251.html Socket.IO简述 Socket.IO用于浏览器与node.js之间实现实时通信.Socket.IO设计的目标是支持任何的浏览器,任何Mobile设备.支持主流的PC浏览器 (IE,Safari,Chrome,Firefox,Opera等),Mobile浏览器(iphone Safari/ipa

关于Socket.IO的知识点记录

最近因为项目的需要,开始学习nodejs,本着js的那点儿功底,nodejs学习起来还是挺快能上手的.随着深入学习,知道了express框架并那它写了一个小功能,作为一个php程序员哈,在express框架路由.模板渲染那里看到了Yii2的影子,所以便更加的亲切了.再接着便接触到了websocket,而今天谈论的socket.io 便是websocket的一个类库,说道这里了,我们先去了解下websocket和socket.io: 一  websocket WebSocket是html5新增加的

socket.io(转载)

socket.io 中文手册,socket.io 中文文档转载于:http://www.cnblogs.com/xiezhengcai/p/3956401.html 服务端 io.on('connection',function(socket));//监听客户端连接,回调函数会传递本次连接的socket io.sockets.emit('String',data);//给所有客户端广播消息 io.sockets.socket(socketid).emit('String', data);//给指

socket.io 中文手册 socket.io 中文文档

socket.io 中文手册,socket.io 中文文档转载于:http://www.cnblogs.com/xiezhengcai/p/3956401.html 服务端 io.on('connection',function(socket));//监听客户端连接,回调函数会传递本次连接的socket io.sockets.emit('String',data);//给所有客户端广播消息 io.sockets.socket(socketid).emit('String', data);//给指

[Nodejs]利用Socket.IO配合Express4搭建即时聊天

Socket.IO为WebSockets这个较新的web技术提供了必要的支持,包含客户端与服务端模块,以便建立通信通道,当然也可作为中间件而存在. 1 创建一个express项目 ????????? ????? 防工具盗链抓取[如果显示此文字,代表来自第三方转发] freddon所有 ??? ??????????? 可以使用命令行初始化一个express项目 先安装express npm install express npm install express-generator express 

Socket.IO学习之基础入门

原文:http://blog.csdn.net/weichuang_1/article/details/48831957 这里贴出Socket.IO官网 一.Socket.IO的介绍 Socket.IO支持及时.双向与基于事件的交流.它可以在每个平台.每个浏览器和每个设备上工作,可靠性和速度同样稳定. 实时分析:将数据推送到客户端,这些客户端会被表示为实时计数器,图表或日志客户. 实时通信和聊天:只需几行代码便可写成一个Socket.IO的”Hello,World”聊天应用. 二进制流传输:从1