天天看點

Socket.io詳解

socket.io是一個跨浏覽器支援WebSocket的實時通訊的JS。

http://socket.io/docs/

由于HTTP是無狀态的協定,要實作即時通訊非常困難。因為當對方發送一條消息時,伺服器并不知道目前有哪些使用者等着接收消息,目前實作即時通訊功能最為普遍的方式就是輪詢機制。即用戶端定期發起一個請求,看看有沒有人發送消息到伺服器,如果有服務端就将消息發給用戶端。這種做法的缺點顯而易見,那麼多的請求将消耗大量資源,大量的請求其實是浪費的。

現在,我們有了WebSocket,它是HTML5的新API。WebSocket連接配接本質上就是建立一個TCP連接配接,WebSocket會通過HTTP請求建立,建立後的WebSocket會在用戶端和服務端建立一個持久的連接配接,直到有一方主動關閉該連接配接。是以,現在伺服器就知道有哪些使用者正在連接配接了,這樣通訊就變得相對容易了。

Socket.io支援及時、雙向、基于事件的交流,可在不同平台、浏覽器、裝置上工作,可靠性和速度穩定。最典型的應用場景如:

  • 實時分析:将資料推送到用戶端,用戶端表現為實時計數器、圖表、日志客戶。
  • 實時通訊:聊天應用
  • 二進制流傳輸:socket.io支援任何形式的二進制檔案傳輸,例如圖檔、視訊、音頻等。
  • 文檔合并:允許多個使用者同時編輯一個文檔,并能夠看到每個使用者做出的修改。

Socket.io實際上是WebSocket的父集,Socket.io封裝了WebSocket和輪詢等方法,會根據情況選擇方法來進行通訊。

Node.js提供了高效的服務端運作環境,但由于Browser對HTML5的支援不一,為了相容所有浏覽器,提供實時的使用者體驗,并為開發者提供用戶端與服務端一緻的程式設計體驗,于是Socket.io誕生了。

npm安裝socket.op
 npm install --save socket.io      

Socket.io将WebSocket和Polling機制以及其它的實時通信方式封裝成通用的接口,并在服務端實作了這些實時機制相應代碼。這就是說,WebSocket僅僅是Socket.io實作實時通信的一個子集,那麼Socket.io都實作了Polling中那些通信機制呢?

  • Adobe Flash Socket

    大部分PC浏覽器都支援的Socket模式,不過是通過第三方嵌入到浏覽器,不在W3C規範内,可能将逐漸被淘汰。況且,大部分手機浏覽器并不支援此種模式。

  • AJAX Long Polling

    定時向服務端發送請求,缺點是給服務端帶來壓力并出現資訊更新不及時的現象。

  • AJAX multipart streaming

    在XMLHttpRequest對象上使用某些浏覽器支援的multi-part标志,AJAX請求被發送給服務端并保持打開狀态(挂起狀态),每次需要向用戶端發送資訊,就尋找一個挂起的HTTP請求響應給用戶端,并且所有的響應都會通過統一連接配接來寫入。

  • Forever Iframem

    永存的Iframe設計了一個置于頁面中隐藏的iframe标簽,該标簽的src屬性指向傳回服務端時間的Servlet路徑。每次在事件到達時,Servlet寫入并重新整理一個新的Script标簽,該标簽内部帶有JS代碼,iframe的内容被附加上script标簽,标簽中的内容就會得到執行。這種方式的缺點是接收資料都是由浏覽器通過HTML标簽來處理的,是以無法知道連接配接何時在哪一端被斷開,而且iframe标簽在浏覽器中将被逐漸取消。

  • JSONP Polling

    JSONP輪詢基本與HTTP輪詢一樣,不同之處則是JSONP可發出跨域請求。

Socket.io 基本應用

socket.io提供了基于事件的實時雙向通訊,它同時提供了服務端和用戶端的API。

服務端

服務端socket.io必須綁定一個

http.Server

執行個體,因為WebSocket協定是建構在HTTP協定之上的,是以在建立WebSocket服務時需調用HTTP子產品并調用其下

createServer()

方法,将生成的server作為參數傳入socket.io。

var httpServer = require('http').createServer();
var io = require('socket.io')(httpServer);
httpServer.listen(3000);      

綁定

http.Server

可使用隐式綁定和顯式綁定

  • 隐式綁定

socket.io内部執行個體化并監聽

http.Server

,通過執行個體化時傳入端口或者在執行個體化後調用

listen

attach

函數進行隐式綁定。

// 執行個體化時傳入端口
require('socket.io')(3000)

// 通過listen或attach函數綁定
let io = require('socket.io')
io.listen(3000);
// io.attach(3000);      
  • 顯式綁定
// 執行個體化時綁定
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);

//通過listen或attach綁定
let httpServer = require('http').Server();
let io = require('socket.io')();
io.listen(httpServer);
// io.attach(httpServer);
httpServer.listen(3000);      

Express架構中使用

let app = require('express');

let httpServer= require('http').Server(app);
let io = require('socket.io')(httpServer);

app.listen(3000);      

KOA架構中使用

let app = require('koa')();

let httpServer = require('http').Server(app.callback());
let io = require('socket.io')(httpServer);

app.listen(3000);      

建立連接配接

當服務端和用戶端連接配接成功時,服務端會監聽到

connection

connect

事件,用戶端會監聽到

connect

事件,斷開連接配接時服務端對應到用戶端的socket與用戶端均會監聽到

disconcect

事件。

/*用戶端*/
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
// socket.io引入成功後,可通過io()生成用戶端所需的socket對象。
let socket = io('http://127.0.0.0:3000');

// socket.emmit()使用者用戶端向服務端發送消息,服務端與之對應的是socket.on()來接收資訊。
socket.emmit('client message', {msg:'hi, server'});

// socket.on()用于接收服務端發來的消息
socket.on('connect',  ()=>{
  console.log('client connect server');
});
socket.on('disconnect', ()=>{
  console.log('client disconnect');
});
</script>

/*服務端*/
// 服務端綁定HTTP伺服器執行個體
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);

// 服務端監聽連接配接狀态:io的connection事件表示用戶端與服務端成功建立連接配接,它接收一個回調函數,回調函數會接收一個socket參數。
io.on('connection',  (socket)=>{
  console.log('client connect server, ok!');

  // io.emit()方法用于向服務端發送消息,參數1表示自定義的資料名,參數2表示需要配合事件傳入的參數
  io.emmit('server message', {msg:'client connect server success'});

  // socket.broadcast.emmit()表示向除了自己以外的用戶端發送消息
  socket.broadcast.emmit('server message', {msg:'broadcast'});

  // 監聽斷開連接配接狀态:socket的disconnect事件表示用戶端與服務端斷開連接配接
  socket.on('disconnect', ()=>{
    console.log('connect disconnect');
  });

  // 與用戶端對應的接收指定的消息
  socket.on('client message', (data)=>{
    cosnole.log(data);// hi server
  });

  socket.disconnect();
});      

傳輸資料

服務端和用戶端的socket是一個關聯的

EventEmitter

對象,用戶端socket派發的事件可以通過被服務端的socket接收,服務端socket派發的事件也可以被用戶端接收。基于這種機制,可以實作雙向交流。

# 模拟:用戶端不斷發送随機數,當随機數大于0.95時,服務端延遲1s後向用戶端發送警告以及警告次數。           
/*用戶端*/
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
<script>
let socket = io('http://127.0.0.1:3000');

let interval = setTimeInterval(()=>{
  socket.emit('random', Math.random());
}, 500);

socket.on('warn', count=>{
  console.log('warning count : '+count);
});

socket.on('disconnect', ()=>{
  clearInterval(interval);
});
</script>

/*服務端*/
let httpServer = require('http').Server();
let io = require('socket.io')(httpServer);
httpServer.listen(3000);

io.on('connection', socket=>{
  socket.on('random', value=>{
    console.log(value);
    if(value>0.95){
      if(typeof socket.warnign==='undefined'){
        socket.warning = 0;// socket對象可用來存儲狀态和自定義資料
      }
      setTimeout(()=>{
        socket.emit('warn', ++socket.warning);
      }, 1000);
    }
  });
});      

繼續閱讀