WebSocket簡介
談到Web實時推送,就不得不說WebSocket。在WebSocket出現之前,很多網站為了實現實時推送技術,通常采用的方案是輪詢(Polling)和Comet技術,Comet又可細分為兩種實現方式,一種是長輪詢機制,一種稱為流技術,這兩種方式實際上是對輪詢技術的改進,這些方案帶來很明顯的缺點,需要由瀏覽器對服務器發出HTTP request,大量消耗服務器帶寬和資源。面對這種狀況,HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬并實現真正意義上的實時推送。
1. wbesocket是什么?
背景:WebSocket是HTML5出的東西(協議)
可以把 WebSocket 看成是 HTTP 協議為了支持長連接所打的一個大補丁,它和 HTTP 有一些共性,是為了解決 HTTP 本身無法解決的某些問題而做出的一個改良設計。WebSocket不是HTTP協議,HTTP只負責建立WebSocket連接。
WebSocket協議本質上是一個基于TCP的 獨立的協議,能夠在瀏覽器和服務器之間建立雙向連接,以基于事件的方式,賦予瀏覽器實時通信能力。就是服務器端和客戶端可以同時發送并響應請求,而不再像HTTP的請求和響應。
協議名為"ws",這意味著一個websocket連接地址會是這樣的寫法:ws://**。
websocket協議本質上是一個基于tcp的協議是服務器實現的。
客戶端通過html5與服務器交互。http是不持續連接的,而websocket是。必須服務器支持websocket協議,才有效。對于不支持websocket服務的服務器,你客戶端怎么寫代碼都沒用。
WebSocket實戰:
Web領域的實時推送技術,這種技術要達到的目的是讓用戶不需要刷新瀏覽器就可以獲得實時更新。它有著廣泛的應用場景,比如在線聊天室、在線客服系統、評論系統、WebIM等。
2.websocket解決了什么問題?
在了解這個之前我先來科普一下:
了解背景:
在以前 HTTP 協議中所謂的 **keep-alive connection** 是:
指在一次 TCP 連接中完成多個 HTTP 請求,但是對每個請求仍然要單獨發 header;
所謂的 polling :
是指從客戶端(一般就是瀏覽器)不斷主動的向服務器發 HTTP 請求查詢是否有新數據 。
這兩種模式有一個共同的缺點:
就是除了真正的數據部分外,服務器和客戶端還要大量交換 HTTP header,信息交換效率很低。
它們建立的“長連接”都是偽.長連接.
只不過好處是不需要對現有的 HTTP server 和瀏覽器架構做修改就能實現。
http long poll,或者ajax輪詢不都可以實現實時信息傳遞
ajax輪詢:
原理:讓瀏覽器隔個幾秒就發送一次請求,詢問服務器是否有新信息。
場景再現:
客戶端(發請求,建立鏈接):啦啦啦,有沒有新信息(Request)
服務端:沒有(Response)
客戶端(發請求,建立鏈接):啦啦啦,有沒有新信息(Request)
服務端:沒有。。(Response)
客戶端(發請求,建立鏈接):啦啦啦,有沒有新信息(Request)
服務端:你好煩啊,沒有啊。。(Response)
客戶端(發請求,建立鏈接):啦啦啦,有沒有新消息(Request)
服務端:好啦好啦,有啦給你。(Response)
客戶端(發請求,建立鏈接):啦啦啦,有沒有新消息(Request)
服務端:。。。。。沒。。。。沒。。。沒有(Response) ---- loop
http long poll :
原理:long poll 其實原理跟 ajax輪詢 差不多,都是采用輪詢的方式,不過采取的是阻塞模型(一直打電話,沒收到就不掛電話),也就是說,客戶端發起連接后,如果沒消息,服務器就一直不返回Response給客戶端。直到有消息才返回,返回完之后,客戶端再次建立連接,周而復始。
場景再現:
客戶端(發請求,建立鏈接):啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request)
等等等。。。。。
服務端:額。。 等待到有消息的時候。。來 給你(Response)
客戶端(發請求,建立鏈接):啦啦啦,有沒有新信息,沒有的話就等有了才返回給我吧(Request) -loop
從上面可以看出其實這兩種方式,都是在不斷地建立HTTP連接,然后等待服務端處理,可以體現HTTP協議的另外一個特點,被動性(服務端不能主動聯系客戶端,只能有客戶端發起。)。
簡單地說就是,服務器是一個很懶的冰箱(這是個梗)(不會、不能主動發起連接),但是上司有命令,如果有客戶來,不管多么累都要好好接待。
由此可以看出上面那種方式的缺陷:
非常消耗資源的:
ajax輪詢 需要服務器有很快的處理速度和資源。(速度)
long poll 需要有很高的并發,也就是說同時接待客戶的能力。(場地大小)
所以在這種情況下出現了,Websocket出現了。
它解決了HTTP的這幾個難題:
http協議缺點:
非持久性
同步有延遲
消耗資源
無狀態協議。
被動性
另外websocket也解決:
可以用于繞過大多數防火墻的限制。
實現實時信息傳遞么。
websocket建立持久連接的實現原理:
通過第一個 HTTP request 建立了 TCP 連接之后,之后的交換數據都不需要再發 HTTP request了,使得這個長連接變成了一個真.長連接。但是不需要發送 HTTP header就能交換數據顯然和原有的 HTTP 協議是有區>別的,所以它需要對服務器和客戶端都進行升級才能實現。
在此基礎上 WebSocket 還是一個雙通道的連接,在同一個 TCP 連接上既可以發也可以收信息。此外還有 multiplexing 功能,幾個不同的 URI 可以復用同一個 WebSocket 連接。這些都是原來的 HTTP 不能做到的。
當服務器完成協議升級后(HTTP->Websocket),服務端就可以主動推送信息給客戶端啦。
所以上面的情景可以做如下修改:
客戶端:啦啦啦,我要建立Websocket協議,需要的服務:chat,Websocket協議版本:17(HTTP Request)
服務端:ok,確認,已升級為Websocket協議(HTTP Protocols Switched)
客戶端:麻煩你有信息的時候推送給我噢。。
服務端:ok,有的時候會告訴你的。
服務端:balabalabalabala
服務端:balabalabalabala
服務端:哈哈哈哈哈啊哈哈哈哈
服務端:笑死我了哈哈哈哈哈哈哈
就變成了這樣,只需要經過一次HTTP請求,就可以做到源源不斷的信息傳送了。(在程序設計中,這種設計叫做回調,即:你有信息了再來通知我,而不是我傻乎乎的每次跑來問你)
那么為什么他會解決服務器上消耗資源的問題呢?
- 建立持久連接
其實我們所用的程序是要經過兩層代理的,即HTTP協議在Nginx等服務器的解析下,然后再傳送給相應的Handler(PHP等)來處理。簡單地說,我們有一個非常快速的接線員(Nginx),他負責把問題轉交給相應的客服(服務器)。本身接線員基本上速度是足夠的,但是每次都卡在客服(服務器)了,老有客服(服務器)處理速度太慢。,導致客服(服務器)不夠。
Websocket就解決了這樣一個難題,建立后,可以直接跟接線員(Nginx)建立持久連接,有信息的時候客服(服務器)想辦法通知接線員,然后接線員在統一轉交給客戶(客戶端)。這樣就可以解決客服處理速度過慢的問題了
- 只需要一次握手
傳統的方式上,http要不斷的建立,關閉HTTP協議,由于HTTP是非狀態性的,每次都要重新傳輸identity info(鑒別信息),來告訴服務端你是誰。
雖然接線員很快速,但是每次都要聽這么一堆,效率也會有所下降的,同時還得不斷把這些信息轉交給客服,不但浪費客服的處理時間,而且還會在網路傳輸中消耗過多的流量/時間。
但是Websocket只需要一次HTTP握手,所以說整個通訊過程是建立在一次連接/狀態中,也就避免了HTTP的非狀態性,服務端會一直知道你的信息,直到你關閉請求,這樣就解決了接線員要反復解析HTTP協議,還要查看identity info的信息。
- 服務器主動推送信息
由客戶主動詢問,轉換為服務器(推送)有信息的時候就發送(當然客戶端還是等主動發送信息過來的。。),沒有信息的時候就交給接線員(Nginx),不需要占用本身速度就慢的客服(Handler)了
3. websocket和HTTP協議的 聯系 和 區別?
Websocket其實是一個新協議
WebSocket 和 HTTP 都是基于 TCP 的協議
TCP是傳輸層協議, WebSocket 和 HTTP是應用層協議
WebSocket 本質上跟 HTTP 完全不一樣,WebSocket 協議和 HTTP 協議是兩種不同的東西
只是為了兼容現有瀏覽器的握手規范而已,WebSocket 的握手是以 HTTP 的形式發起的,如果服務器或者代理不支持 WebSocket,它們會把這當做一個不認識的 HTTP 請求從而優雅地拒絕掉。
聯系:
它們扯上關系是只是因為:
客戶端開始建立 WebSocket 連接時要發送一個 header 標記了 Upgrade 的 HTTP 請求,表示請求協議升級。
(或者說借用了HTTP的協議來完成一部分握手)
服務器端做出響應的簡便方法是:
直接在現有的 HTTP 服務器軟件和現有的端口上實現 WebSocket 協議,重用現有代碼(比如解析和認證這個 HTTP 請求。如果在 TCP 協議上實現,這兩個功能就要重新實現),然后再回一個狀態碼為 101 的 HTTP 響應完成握手,再往后發送數據時就沒 HTTP 的事了。
也就是說它是HTTP協議上的一種補充可以通過這樣一張圖理解 ,有交集,但是并不是全部。
和HTTP 的唯一關聯是使用HTTP 協議的101狀態碼進行協議切換,使用的TCP 端口是80。
區別:
-
持久性:
HTTP協議:
HTTP是非持久的協議(長連接,循環連接的不算)
websocket協議:
Websocket是一個持久化的協議
-
生命周期:
HTTP的生命周期通過Request來界定,也就是一個Request 一個Response
在HTTP1.0中,這次HTTP請求就結束了。
在HTTP1.1中進行了改進,使得有一個keep-alive,也就是說,在一個HTTP連接中,可以發送多個Request,接收多個Response。但是請記住 Request = Response
在HTTP中永遠是這樣,也就是說一個request只能有一個response。而且這個response也是被動的,不能主動發起。
4.Websocket具體有什么優點 ?
他解決了web實時化的問題,相比傳統http有如下好處:
- 實時交互
- 只需要建立一次連接(一個WEB客戶端只建立一個TCP連接)
- 服務器能夠主動推送內容(Websocket服務端可以推送(push)數據到web客戶端.)
- 有更加輕量級的頭,減少數據傳送量
- 快速(延遲小,每條消息可以小到兩個字節)
- 開發者友好(接口簡單,并是熟悉的事件模型)
5. websocket的原理和應用
websocket的原理:
websocket通信協議實現的是基于瀏覽器的原生socket,這樣原先只有在c/s模式下的大量開發模式都可以搬到web上來了,基本就是通過瀏覽器的支持在web上實現了與服務器端的socket通信。
WebSocket沒有試圖在HTTP之上模擬server推送,而是直接在TCP之上定義了幀協議,因此WebSocket能夠支持雙向的通信。
一旦取得 Web 服務器上的 Web Socket 連接之后,就可以通過調用 send() 方法從瀏覽器發送數據到服務器上,通過onmessage 事件處理程序從服務器接收數據到瀏覽器中。
下面是創建一個新的 WebSocket 對象的 API。
var Socket = new WebSocket(url, [protocal] );
第一個參數 url 用于指定要連接的 URL。第二個屬性 - 端口是可選的,如果提供,就會指定一個服務器必須支持連接成功的子協議。
屬性 | 描述 |
---|---|
Socket.readyState | 只讀屬性readyState表示連接的狀態。有以下取值: |
0 表示連接尚未建立。 | |
1 表示連接已建立,可以進行通信。 | |
2 表示連接正在進行關閉握手。 | |
3 表示連接已經關閉或者連接不能打開。 |
WebSocket 事件
下面是 WebSocket 對象相關的事件。假定我們已經創建了上述的 Socket 對象,客戶端通過websocket API提供的如下4個事件進行編程:
事件 | 事件處理程序 | 描述 |
---|---|---|
open | Socket.onopen | 建立 socket 連接時觸發這個事件。 |
message | Socket.onmessage | 客戶端從服務器接收數據時觸發。 |
error | Socket.onerror | 連接發生錯誤時觸發。 |
close | Socket.onclose | 連接被關閉時觸發。 |
WebSocket 方法
下面是 WebSocket 對象相關的方法。假定我們已經創建了上述的 Socket 對象:
方法 | 描述 |
---|---|
Socket.send() | send(data) 方法使用連接傳輸數據。 |
Socket.close() | close() 方法用于終止任何現有連接。 |
Websocket握手的實現
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
熟悉HTTP的可能發現了,這段類似HTTP協議的握手請求中,多了幾個東西。
對上面做出講解:
多了下面的東西,這個就是Websocket的核心:
Upgrade: websocket
Connection: Upgrade
告訴Apache、Nginx等服務器:注意啦,窩發起的是Websocket協議,快點幫我找到對應的助理處理~不是那個老土的HTTP。
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
#首先,Sec-WebSocket-Key 是一個Base64 encode的值,這個是瀏覽器隨機生成的
告訴服務器:泥煤,不要忽悠窩,我要驗證尼是不是真的是Websocket助理。
Sec-WebSocket-Protocol: chat, superchat
#Sec_WebSocket-Protocol 是一個用戶定義的字符串,用來區分同URL下,不同的服務所需要的協議。
簡單理解:今晚我要服務A,別搞錯啦~
Sec-WebSocket-Version: 13
#Sec-WebSocket-Version 是告訴服務器所使用的Websocket Draft(協議版本)
在最初的時候,Websocket協議還在 Draft 階段,各種奇奇怪怪的協議都有
然后服務器會返回下列東西,表示已經接受到請求, 成功建立Websocket啦!
HTTP/1.1 101 Switching Protocols
#這里開始就是HTTP最后負責的區域了,告訴客戶,我已經成功切換協議啦~
Upgrade: websocket
Connection: Upgrade
#告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket。
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
#Sec-WebSocket-Accept 這個則是經過服務器確認,并且加密過后Sec-WebSocket-Key。
#服務器:好啦好啦,知道啦,給你看我的ID CARD來證明行了吧。
Sec-WebSocket-Protocol: chat
#后面的,Sec-WebSocket-Protocol 則是表示最終使用的協議。
依然是固定的,
然后,。至此,HTTP已經完成它所有工作了,接下來就是完全按照Websocket協議進行了。
使用WebSocket
在客戶端使用websocket需要創建WebSocket對象,通過提供的open、send、message、close等方法實現創建、發送、監聽信息、關閉連接。例如下面的代碼:
if('WebSocket' in window){
// 創建websocket實例
var socket = new WebSocket('ws://localhost:8080');
//打開
socket.onopen = function(event)
{
// 發送
socket.send('I am the client and I\'m listening!');
// 監聽
socket.onmessage = function(event) {
console.log('Client received a message',event);
};
// 關閉監聽
socket.onclose = function(event) {
console.log('Client notified socket has closed',event);
};
// 關閉
//socket.close() };
}else{
alert('本瀏覽器不支持WebSocket哦~');
}
現在chrome、firefox等瀏覽器都已經支持了websocket,而IE卻沒有。下面我們來簡單說說服務器端對websocket的支持。
服務器端支持websocket的語言不少,而且都有相關的開源項目,例如php的。
socket.io
Socket.IO是一個開源的WebSocket庫,包括了客戶端的js和服務器端的nodejs。官方地址:http://socket.io
它通過Node.js實現WebSocket服務端,同時也提供客戶端JS庫。Socket.IO支持以事件為基礎的實時雙向通訊,它可以工作在任何平臺、瀏覽器或移動設備。
Socket.IO支持4種協議:WebSocket、htmlfile、xhr-polling、jsonp-polling,它會自動根據瀏覽器選擇適合的通訊方式,從而讓開發者可以聚焦到功能的實現而不是平臺的兼容性,同時Socket.IO具有不錯的穩定性和性能。
websocket 和 socket.io的區別
socket.io封裝 了websocket,同時包含了其它的連接方式,比如Ajax。原因在于不是所有的瀏覽器 都支持websocket,通過socket.io的封裝 ,你不用關心里面用了什么連接方式。你在任何瀏覽器 里都可以使用socket.io來建立異步 的連接。socket.io包含了服務端 和客戶端的庫,如果在[瀏覽器] 中使用了socket.io的js,服務端 也必須同樣適用。如果你很清楚你需要的就是websocket,那可以直接使用websocket。
socket.io是一個WebSocket協議的實現,用它你可以進行websocket通信,這是應用層 node.js net.socket是系統socket接口,用它你可以操作linux socket,這是傳輸層
websocket協議本質上也是使用系統socket,它是把socket引入了http通信,也就是不使用80端口進行http通信。它的目的是建立全雙工的連接,可以用來解決服務器客戶端保持長連接的問題。
客戶端使用socket.io
去github clone socket.io的最新版本,或者直接飲用使用socket.io的CDN服務:
<script src="http://cdn.socket.io/stable/socket.io.js"></script>
下面可以創建使用socket.io庫來創建客戶端js代碼了:
var socket = io.connect('http://localhost');
socket.on('news', function (data) {
console.log(data);
socket.emit('my other event', { my: 'data' });
});
socket.on是監聽,收到服務器端發來的news的內容,則運行function,其中data就是請求回來的數據,socket.emit是發送消息給服務器端的方法。
在使用Socket.IO類庫時,服務器端和客戶端之間除了可以互相發送消息之外,也可以使用socket端口對象的emit方法,互相發送事件。
socket.emit(event,data,[callback])
event表示:參數值為一個用于指定事件名的字符串。
data參數值:代表該事件中攜帶的數據。這個數據就是要發送給對方的數據。數據可以是字符串,也可以是對象。
callback參數:值為一個參數,用于指定一個當對方確定接收到數據時調用的回調函數。
一方使用emit發送事件后,另一方可以使用on,或者once方法,對該事件進行監聽。once和on不同的地方就是,once只監聽一次,會在回調函數執行完畢后,取消監聽。
socket.on(event,function(data,fn){})
socket.once(event,function(data,fn){})
再體會下emit的三個參數:首先是服務器端:
再是客戶端:
房間
房間是Socket.IO提供的一個非常好用的功能。房間相當于為指定的一些客戶端提供了一個命名空間,所有在房間里的廣播和通信都不會影響到房間以外的客戶端。
進入房間與離開房間
使用join()方法將socket加入房間:
io.on('connection', function(socket){
socket.join('some room');
});
使用leave()方法離開房間:
socket.leave('some room');
在房間中發送消息
在某個房間中發送消息:
io.to('some room').emit('some event');
to()方法用于在指定的房間中,對除了當前socket的其他socket發送消息。
socket.broadcast.to('game').emit('message','nice game');
in()方法用于在指定的房間中,為房間中的所有有socket發送消息。
io.sockets.in('game').emit('message','cool game');
當socket進入一個房間之后,可以通過以下兩種方式在房間里廣播消息:
7.怎么在不支持Websocket的客戶端上使用Websocket ?
答案是:
不能
但是可以通過上面說的 long poll 和 ajax 輪詢來 模擬出類似的效果