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 Socket(uri, opts){
  if (!(this instanceof Socket)) return new Socket(uri, opts);
....
  this.open();
}

先是各种传递参数,初始化。然后open().

Socket.prototype.open = function () {

    var  transport = this.transports[0];
  this.readyState = ‘opening‘;

  // Retry with the next transport if the transport is disabled (jsonp: false)
  var transport;
  try {
    transport = this.createTransport(transport);
  } catch (e) {
    this.transports.shift();
    this.open();
    return;
  }

  transport.open();
  this.setTransport(transport);
};

设置readyState,这个是表示全局状态的变量,值有opening,open,close.然后createTransport()

Socket.prototype.createTransport = function (name) {
  debug(‘creating transport "%s"‘, name);
  var query = clone(this.query);

  // append engine.io protocol identifier
  query.EIO = parser.protocol;

  // transport name
  query.transport = name;

  // session id if we already have one
  if (this.id) query.sid = this.id;

  var transport = new transports[name]({
    agent: this.agent,
 。。。
  });

  return transport;
};

这里我们不考虑websocket.

new transprots[name]在/transports/index.js.

function polling(opts){
 ....
  xhr = new XMLHttpRequest(opts);

  if (‘open‘ in xhr && !opts.forceJSONP) {
    return new XHR(opts);
  } else {
    if (!jsonp) throw new Error(‘JSONP disabled‘);
    return new JSONP(opts);
  }
}

如果要强制执行jsonp传输的话,要这样设置。

    var socket = eio(‘http://localhost:8000‘,{
        forceJSONP:true
    });

没有设置的话,默认走xhr(ajax).如果跨域的话,会发出options请求,请求服务端同意跨域。这里的细节我不明白。

function XHR(opts){
  Polling.call(this, opts);
....
}

/**
 * Inherits from Polling.
 */

inherit(XHR, Polling);

XHR继承Polling,Polling又继承Transport。这里调用构造函数时都调用了父类的构造函数,里面就是初始化了一些参数。

这样createTransport()就走完了。接着是transport.open();

Transport.prototype.open = function () {
  if (‘closed‘ == this.readyState || ‘‘ == this.readyState) {
    this.readyState = ‘opening‘;
    this.doOpen();
  }

  return this;
};
Polling.prototype.doOpen = function(){
  this.poll();
};
Polling.prototype.poll = function(){
  debug(‘polling‘);
  this.polling = true;
  this.doPoll();
  this.emit(‘poll‘);
};
XHR.prototype.doPoll = function(){
  debug(‘xhr poll‘);
  var req = this.request();
  var self = this;
  req.on(‘data‘, function(data){
    self.onData(data);
  });
  req.on(‘error‘, function(err){
    self.onError(‘xhr poll error‘, err);
  });
  this.pollXhr = req;
};

doPoll()里对req进行了事件绑定,后面ajax有数据成功返回时会触发上面的data事件,执行onData().this.request()返回的是ajax的封装。

XHR.prototype.request = function(opts){
  opts = opts || {};
  opts.uri = this.uri();
。。。

  return new Request(opts);
};
function Request(opts){
  this.method = opts.method || ‘GET‘;
  this.uri = opts.uri;
 ....

  this.create();
}
Request.prototype.create = function(){
  var opts = { agent: this.agent, xdomain: this.xd, xscheme: this.xs, enablesXDR: this.enablesXDR };

  // SSL options for Node.js client
  opts.pfx = this.pfx;
.........

  var xhr = this.xhr = new XMLHttpRequest(opts);
  var self = this;

  try {
    debug(‘xhr open %s: %s‘, this.method, this.uri);
    xhr.open(this.method, this.uri, this.async);
    if (this.supportsBinary) {
      // This has to be done after open because Firefox is stupid
      // http://stackoverflow.com/questions/13216903/get-binary-data-with-xmlhttprequest-in-a-firefox-extension
      xhr.responseType = ‘arraybuffer‘;
    }

    if (‘POST‘ == this.method) {
      try {
        if (this.isBinary) {
          xhr.setRequestHeader(‘Content-type‘, ‘application/octet-stream‘);
        } else {
          xhr.setRequestHeader(‘Content-type‘, ‘text/plain;charset=UTF-8‘);
        }
      } catch (e) {}
    }

    // ie6 check
    if (‘withCredentials‘ in xhr) {
      xhr.withCredentials = true;
    }

    if (this.hasXDR()) {
      xhr.onload = function(){
        self.onLoad();
      };
      xhr.onerror = function(){
        self.onError(xhr.responseText);
      };
    } else {
      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);
        }
      };
    }

    debug(‘xhr data %s‘, this.data);
    xhr.send(this.data);
  } catch (e) {
    // Need to defer since .create() is called directly fhrom the constructor
    // and thus the ‘error‘ event can only be only bound *after* this exception
    // occurs.  Therefore, also, we cannot throw here at all.
    setTimeout(function() {
      self.onError(e);
    }, 0);
    return;
  }
};

create()发出ajax请求,这时请求方法是默认的get.前面的文章中说过,服务端会针对不同的请求方法执行不同的策略。这里在创建第一次请求(握手)。

如果响应成功,self.onLoad();,取出数据。onData()

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);
  }
};
Request.prototype.onSuccess = function(){
  this.emit(‘success‘);
  this.cleanup();
};

Request.prototype.onData = function(data){
  this.emit(‘data‘, data);
  this.onSuccess();
};

this.emit(‘data‘, data);会触发前面doPoll()里面的onData().这里的onData()不是Request里面的onData().

  req.on(‘data‘, function(data){
    self.onData(data);
  });

注意,这里的emit不是服务端的emit,在源码后面可以看到,它相当于jquery里面的fire(),用来触发自定义事件。

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);
    }
  }
};

parser.decodePayload()和服务端里面的一样的作用,就是解析数据,可以简单的认为它把数据解析成关联数值,然后执行回调。

回调里面,先判断是不是握手响应,根据readystate是不是opening.如果是的话,onOpen()

Transport.prototype.onOpen = function () {
  this.readyState = ‘open‘;
  this.writable = true;
  this.emit(‘open‘);
};

改变readystate,触发客户端的open事件。

这时readystate是open了,然后是onData()里面的poll().然后执行poll()->doPoll()->request()发出xhr(get方法)请求。

get方法用来做长连接的,当然广播返回的数据也是通过这个get方法。

post方法是向服务端传送心跳的,服务端在一定时间内(pingInterval)收到这个post请求,则认定这个客户端还在。具体的后面会说到。

回到回调函数里面,self.onPacket(packet);

Transport.prototype.onPacket = function (packet) {
  this.emit(‘packet‘, packet);
};

packet绑定和服务端一样,

Socket.prototype.setTransport = function(transport){
  // set up transport
  this.transport = transport;

  // set up transport listeners
  transport
  .on(‘drain‘, function(){
    self.onDrain();
  })
  .on(‘packet‘, function(packet){
    self.onPacket(packet);
  })
  .on(‘error‘, function(e){
    self.onError(e);
  })
  .on(‘close‘, function(){
    self.onClose(‘transport close‘);
  });
};

触发socket.js里面的onPacket()

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);
  }
};

收到握手响应后的packet.type=open.至此,我们分析了,从客户端建立到收到握手响应的过程。

时间: 2024-10-12 15:22:01

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

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 = t

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

基于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