最近由於項目的須要,開始學習nodejs,本着js的那點兒功底,nodejs學習起來仍是挺快能上手的。隨着深刻學習,知道了express框架並那它寫了一個小功能,做爲一個php程序員哈,在express框架路由、模板渲染那裏看到了Yii2的影子,因此便更加的親切了。再接着便接觸到了websocket,而今天談論的socket.io 即是websocket的一個類庫,說道這裏了,咱們先去了解下websocket和socket.io:javascript
一 websocket php
WebSocket是html5新增長的一種通訊協議,目前流行的瀏覽器都支持這個協議,例如 Chrome,Safrie,Firefox,Opera,IE等等,對該協議支持最先的應該是chrome,從chrome12就已經開始支持,隨着協 議草案的不斷變化,各個瀏覽器對協議的實現也在不停的更新。該協議仍是草案,沒有成爲標準,不過成爲標準應該只是時間問題了。html
1. WebSocket APIhtml5
首先看一段簡單的javascript代碼,該代碼調用了WebSockets的API。java
var ws = new WebSocket(「ws://echo.websocket.org」);node
ws.onopen = function(){ws.send(「Test!」); };git
ws.onmessage = function(evt){console.log(evt.data);ws.close();};程序員
ws.onclose = function(evt){console.log(「WebSocketClosed!」);};github
ws.onerror = function(evt){console.log(「WebSocketError!」);};web
這份代碼總共只有5行,如今簡單概述一下這5行代碼的意義。
第一行代碼是在申請一個WebSocket對象,參數是須要鏈接的服務器端的地址,同http協議使用http://開頭同樣,WebSocket協議的URL使用ws://開頭,另外安全的WebSocket協議使用wss://開頭。
第二行到第五行爲WebSocket對象註冊消息的處理函數,WebSocket對象一共支持四個消息 onopen, onmessage, onclose和onerror,當Browser和WebSocketServer鏈接成功後,會觸發onopen消息;若是鏈接失敗,發送、接收數據 失敗或者處理數據出現錯誤,browser會觸發onerror消息;當Browser接收到WebSocketServer發送過來的數據時,就會觸發 onmessage消息,參數evt中包含server傳輸過來的數據;當Browser接收到WebSocketServer端發送的關閉鏈接請求時, 就會觸發onclose消息。咱們能夠看出全部的操做都是採用消息的方式觸發的,這樣就不會阻塞UI,使得UI有更快的響應時間,獲得更好的用戶體驗。
2 爲何引入WebSocket協議?
Browser已經支持http協議,爲何還要開發一種新的WebSocket協議呢?咱們知道http協議是一種單向的網絡協議,在創建鏈接後,它只 容許Browser/UA(UserAgent)向WebServer發出請求資源後,WebServer才能返回相應的數據。而WebServer不能 主動的推送數據給Browser/UA,當初這麼設計http協議也是有緣由的,假設WebServer能主動的推送數據給Browser/UA,那 Browser/UA就太容易受到攻擊,一些廣告商也會主動的把一些廣告信息在不經意間強行的傳輸給客戶端,這不能不說是一個災難。那麼單向的http協 議給如今的網站或Web應用程序開發帶來了哪些問題呢?
讓咱們來看一個案例,如今假設咱們想開發一個基於Web的應用程序去獲取當前Web服務器的實時數據,例如股票的實時行情,火車票的剩餘票數等等,這就需 要Browser/UA與WebServer端之間反覆的進行http通訊,Browser不斷的發送Get請求,去獲取當前的實時數據。下面介紹幾種常 見的方式:
1. Polling
這種方式就是經過Browser/UA定時的向Web服務器發送http的Get請求,服務器收到請求後,就把最新的數據發回給客戶端(Browser /UA),Browser/UA獲得數據後,就將其顯示出來,而後再按期的重複這一過程。雖然這樣能夠知足需求,可是也仍然存在一些問題,例如在某段時間 內Web服務器端沒有更新的數據,可是Browser/UA仍然須要定時的發送Get請求過來詢問,那麼Web服務器就把之前的老數據再傳送過 來,Browser/UA把這些沒有變化的數據再顯示出來,這樣顯然既浪費了網絡帶寬,又浪費了CPU的利用率。若是說把Browser發送Get請求的 週期調大一些,就能夠緩解這一問題,可是若是在Web服務器端的數據更新很快時,這樣又不能保證Web應用程序獲取數據的實時性。
2. Long Polling
上面介紹了Polling遇到的問題,如今介紹一下LongPolling,它是對Polling的一種改進。
Browser/UA發送Get請求到Web服務器,這時Web服務器能夠作兩件事情,第一,若是服務器端有新的數據須要傳送,就當即把數據發回給 Browser/UA,Browser/UA收到數據後,當即再發送Get請求給Web Server;第二,若是服務器端沒有新的數據須要發送,這裏與Polling方法不一樣的是,服務器不是當即發送迴應給Browser/UA,而是把這個 請求保持住,等待有新的數據到來時,再來響應這個請求;固然了,若是服務器的數據長期沒有更新,一段時間後,這個Get請求就會超 時,Browser/UA收到超時消息後,再當即發送一個新的Get請求給服務器。而後依次循環這個過程。
這種方式雖然在某種程度上減少了網絡帶寬和CPU利用率等問題,可是仍然存在缺陷,例如假設服務器端的數據更新速率較快,服務器在傳送一個數據包給 Browser後必須等待Browser的下一個Get請求到來,才能傳遞第二個更新的數據包給Browser,那麼這樣的話,Browser顯示實時數 據最快的時間爲2×RTT(往返時間),另外在網絡擁塞的狀況下,這個應該是不能讓用戶接受的。另外,因爲http數據包的頭部數據量每每很大(一般有 400多個字節),可是真正被服務器須要的數據卻不多(有時只有10個字節左右),這樣的數據包在網絡上週期性的傳輸,不免對網絡帶寬是一種浪費。
經過上面的分析可知,要是在Browser能有一種新的網絡協議,能支持客戶端和服務器端的雙向通訊,並且協議的頭部又不那麼龐大就行了。WebSocket就是肩負這樣一個使命登上舞臺的。
3 websocket協議
WebSocket協議是一種雙向通訊協議,它創建在TCP之上,同http同樣經過TCP來傳輸數據,可是它和http最大的不一樣有兩 點:1.WebSocket是一種雙向通訊協議,在創建鏈接後,WebSocket服務器和Browser/UA都能主動的向對方發送或接收數據,就像 Socket同樣,不一樣的是WebSocket是一種創建在Web基礎上的一種簡單模擬Socket的協議;2.WebSocket須要經過握手鍊接,類 似於TCP它也須要客戶端和服務器端進行握手鍊接,鏈接成功後才能相互通訊。
下面是一個簡單的創建握手的時序圖:
這裏簡單說明一下WebSocket握手的過程。
當Web應用程序調用new WebSocket(url)接口時,Browser就開始了與地址爲url的WebServer創建握手鍊接的過程。
1. Browser與WebSocket服務器經過TCP三次握手創建鏈接,若是這個創建鏈接失敗,那麼後面的過程就不會執行,Web應用程序將收到錯誤消息通知。
2. 在TCP創建鏈接成功後,Browser/UA經過http協議傳送WebSocket支持的版本號,協議的字版本號,原始地址,主機地址等等一些列字段給服務器端。
例如:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat,superchat
Sec-WebSocket-Version: 13
3. WebSocket服務器收到Browser/UA發送來的握手請求後,若是數據包數據和格式正確,客戶端和服務器端的協議版本號匹配等等,就接受本次握手鍊接,並給出相應的數據回覆,一樣回覆的數據包也是採用http協議傳輸。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
4. Browser收到服務器回覆的數據包後,若是數據包內容、格式都沒有問題的話,就表 示本次鏈接成功,觸發onopen消息,此時Web開發者就能夠在此時經過send接口想服務器發送數據。不然,握手鍊接失敗,Web應用程序會收到 onerror消息,而且能知道鏈接失敗的緣由。
4 websocket與TCP,HTTP的關係
WebSocket與http協議同樣都是基於TCP的,因此他們都是可靠的協議,Web開發者調用的WebSocket的send函數在browser 的實現中最終都是經過TCP的系統接口進行傳輸的。WebSocket和Http協議同樣都屬於應用層的協議,那麼他們之間有沒有什麼關係呢?答案是確定 的,WebSocket在創建握手鍊接時,數據是經過http協議傳輸的,正如咱們上一節所看到的「GET/chat HTTP/1.1」,這裏面用到的只是http協議一些簡單的字段。可是在創建鏈接以後,真正的數據傳輸階段是不須要http協議參與的。
具體關係能夠參考下圖:
5 websocket server
若是要搭建一個Web服務器,咱們會有不少選擇,市場上也有不少成熟的產品供咱們應用,好比開源的Apache,安裝後只需簡單的配置(或者默認配置)就可 以工做了。可是若是想搭建一個WebSocket服務器就沒有那麼輕鬆了,由於WebSocket是一種新的通訊協議,目前仍是草案,沒有成爲標準,市場 上也沒有成熟的WebSocket服務器或者Library實現WebSocket協議,咱們就必須本身動手寫代碼去解析和組裝WebSocket的數據 包。要這樣完成一個WebSocket服務器,估計全部的人都想放棄,幸虧的是市場上有幾款比較好的開源庫供咱們使用,好比 PyWebSocket,WebSocket-Node, LibWebSockets等等,這些庫文件已經實現了WebSocket數據包的封裝和解析,咱們能夠調用這些接口,這在很大程度上減小了咱們的工做 量。如
下面就簡單介紹一下這些開源的庫文件。
1. PyWebSocket
PyWebSocket採用Python語言編寫,能夠很好的跨平臺,擴展起來也比較簡單,目前WebKit採用它搭建WebSocket服務器來作LayoutTest。
咱們能夠獲取源碼經過下面的命令
svn checkouthttp://pywebsocket.googlecode.com/svn/trunk/ pywebsocket-read-only
更多的詳細信息能夠從http://code.google.com/p/pywebsocket/獲取。
2. WebSocket-Node
WebSocket-Node採用JavaScript語言編寫,這個庫是創建在nodejs之上的,對於熟悉JavaScript的朋友可參考一下,另外Html5和Web應用程序受歡迎的程度愈來愈高,nodejs也正受到普遍的關注。
咱們能夠從下面的鏈接中獲取源碼
https://github.com/Worlize/Websocket-Node
3. LibWebSockets
LibWebSockets採用C/C++語言編寫,可定製化的力度更大,從TCP監聽開始到封包的完成咱們均可以參與編程。
咱們能夠從下面的命令獲取源代碼
git clone git://git.warmcat.com/libwebsockets
值得一提的是:websocket是能夠和http共用監聽端口的,也就是它能夠公用端口完成socket任務。
二 Socket.io
node.js提供了高效的服務端運行環境,可是因爲瀏覽器端對HTML5的支持不一,爲了兼容全部瀏覽器,提供卓越的實時的用戶體驗,而且爲程序員提供客戶端與服務端一致的編程體驗,因而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對象上使用某些瀏覽器(好比說Firefox)支持的multi-part標誌。Ajax請求被髮送給服務器端並保 持打開狀態(掛起狀態),每次須要向客戶端發送信息,就尋找一個掛起的的http請求響應給客戶端,而且全部的響應都會經過統一鏈接來寫入
扯多了哈..........接着我就開始想寫一個聊天室的demo出來,因爲demo還在開發中,今天小編就不貼出來了,等寫好了,我會在寫一篇寫的博客的,並會附上git clone 地址供你們參考學習交流的。
接下來是關於SOKCET.IO的知識記錄:
$ npm install socket.io --save
Server (app.js)
var app = require('http').createServer(handler) var io = require('socket.io')(app); var fs = require('fs'); app.listen(80); function handler (req, res) { fs.readFile(__dirname + '/index.html', function (err, data) { if (err) { res.writeHead(500); return res.end('Error loading index.html'); } res.writeHead(200); res.end(data); }); } io.on('connection', function (socket) { socket.emit('news', { hello: 'world' }); socket.on('my other event', function (data) { console.log(data); }); }); Client (index.html) <script src="/socket.io/socket.io.js"></script> <script> var socket = io('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event', { my: 'data' }); }); </script>
服務器端(/app.js)Server (app.js)
var app = require('express')(); var server = require('http').Server(app); var io = require('socket.io')(server); server.listen(80); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); io.on('connection', function (socket) { socket.emit('news', { hello: 'world' }); socket.on('my other event', function (data) { console.log(data); }); });
客戶端(/public/javascript/chat.js & /views/index.html)Client (index.html)
<script src="/socket.io/socket.io.js"></script> <script> var socket = io.connect('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event', { my: 'data' }); }); </script>
Server (app.js)
var app = require('express').createServer(); var io = require('socket.io')(app); app.listen(80); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); }); io.on('connection', function (socket) { socket.emit('news', { hello: 'world' }); socket.on('my other event', function (data) { console.log(data); }); });
客戶端Client (index.html)
<script src="/socket.io/socket.io.js"></script> <script> var socket = io.connect('http://localhost'); socket.on('news', function (data) { console.log(data); socket.emit('my other event', { my: 'data' }); }); </script>
Socket.IO 提供了從服務器發佈和接受定製事件的途徑。不只僅可以完成鏈接、發信、斷開鏈接這樣通常事件的收發,開發人員能夠發佈以下例所示的定製事件。
Socket.IO allows you to emit and receive custom events. Besides connect, message and disconnect, you can emit custom events:
服務端Server
//注意,io(<port>) 將會爲您的應用建立一個http服務進程。 //note, io(<port>) will create a http server for you var io = require('socket.io')(80); io.on('connection', function (socket) { io.emit('this', { will: 'be received by everyone'}); socket.on('private message', function (from, msg) { console.log('I received a private message by ', from, ' saying ', msg); }); socket.on('disconnect', function () { io.emit('user disconnected'); }); });
若是你但願在一個特定的web應用中控制多個消息與事件的收發,推薦使用切換方式(默認/其餘命名空間)來實現。若是你但願調用第三方的代碼,或者編寫一些但願與他人共享的代碼,socket.io提供了一種途徑:規定socket的命名空間。這有助於建立多線鏈接:實現了不去建立兩個websocket鏈接,而是使用一個鏈接完成多線操做。
If you have control over all the messages and events emitted for a particular application, using the default / namespace works. If you want to leverage 3rd-party code, or produce code to share with others, socket.io provides a way of namespacing a socket.
This has the benefit of multiplexing a single connection. Instead of socket.io using two WebSocket connections, it’ll use one.
服務端Server (app.js)
var io = require('socket.io')(80); var chat = io .of('/chat')//!of(namespace)肯定了這個鏈接所在的命名空間 .on('connection', function (socket) { socket.emit('a message', { that: 'only' , '/chat': 'will get' }); chat.emit('a message', { everyone: 'in' , '/chat': 'will get' }); }); var news = io .of('/news')//!of(namespace)肯定了這個鏈接所在的命名空間 .on('connection', function (socket) { socket.emit('item', { news: 'item' }); });
客戶端Client (index.html)
<script> var chat = io.connect('http://localhost/chat')//!選擇命名空間接受/發送特定的信息。 , news = io.connect('http://localhost/news');//!選擇命名空間接受/發送特定的信息。 chat.on('connect', function () { chat.emit('hi!'); }); news.on('news', function () { news.emit('woot'); }); </script>
有時候,某些消息的獲取不是很是必要,甚至能夠被拋棄。假想你有一個實時顯示比伯這個關鍵詞的全部推特的應用,若是某個客戶端並無準備好接受信息(由於網速慢或其餘緣由,又或者由於這些客戶端正嘗試經過長輪詢的方式而且偏偏處在請求迴應的過程之中)。若是這一次他們不可以完徹底全地接收到比伯的實時推特不會影響這個應用的正常使用,在這種狀況下,你就會但願將那些推特的信息看成易變信息(volatile messages)來發送。
Sometimes certain messages can be dropped. Let’s say you have an app that shows realtime tweets for the keyword bieber.
If a certain client is not ready to receive messages (because of network slowness or other issues, or because they’re connected through long polling and is in the middle of a request-response cycle), if they doesn’t receive ALL the tweets related to bieber your application won’t suffer.
In that case, you might want to send those messages as volatile messages.
服務端Server
var io = require('socket.io')(80); io.on('connection', function (socket) { var tweets = setInterval(function () { getBieberTweet(function (tweet) { socket.volatile.emit('bieber tweet', tweet);//soket.volatile.emit就是這樣的方式。 }); }, 100); socket.on('disconnect', function () { clearInterval(tweets); }); });
有些時候,你可能但願獲得客戶端已經接收到信息的狀態響應。
爲了實現這樣的指望,十分簡單,只須要在.send或.emit函數的最後一個參量傳遞一個函數。不只如此,當你使用.emit函數時,確認信息是你所指定的,意味着你還能夠傳遞確認信息的內容。
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, fn) {//傳遞一個fn函數 fn('woot');//指定用於響應的確認信息 }); });
客戶端Client (index.html)
<script> var socket = io(); // 小貼士:沒有任何的參量的io()會進行自動檢查。 //TIP: io() with no args does auto-discovery socket.on('connect', function () { //小貼士:你能夠監聽connect事件,而是直接監聽ferret事件。 // TIP: you can avoid listening on `connect` and listen on events directly too! socket.emit('ferret', 'tobi', function (data) { console.log(data); // data will be 'woot' }); }); </script>
想要廣播一則消息,只須要添加一個broadcast標記給emit或send方法。廣播意味着把消息發送給除了消息發送者之外的全部客戶端。
To broadcast, simply add a broadcast flag to emit and send method calls. Broadcasting means sending a message to everyone else except for the socket that starts it.
服務端Server
var io = require('socket.io')(80); io.on('connection', function (socket) { socket.broadcast.emit('user connected'); });
若是你只是但願使用websocket的語義,你也能夠作到。只須要在消息的事件裏調用send和listen方法。
If you just want the WebSocket semantics, you can do that too. Simply leverage send and listen on the messageevent:
服務端Server (app.js)
var io = require('socket.io')(80); io.on('connection', function (socket) { socket.on('message', function () { }); socket.on('disconnect', function () { }); });
客戶端Client (index.html)
<script> var socket = io('http://localhost/'); socket.on('connect', function () { socket.send('hi'); socket.on('message', function (msg) { // my msg }); }); </script>
########################################以上知識都是轉載#########################################3
最後給你們推薦一篇socket.io簡易教程(羣聊,發送圖片,分組,私聊)的文章,地址:http://blog.csdn.net/neuq_zxy/article/details/77531126