目錄
四、爲什麼WebSocket連接可以實現全雙工通信而HTTP連接不行呢?
一、爲什麼要有websocket?
websocket的出現是爲了彌補http協議服務端無法向客戶端主動推送消息。所以以前實現這種場景都是通過用輪詢或者Comet。輪詢是指瀏覽器通過JavaScript啓動一個定時器,然後以固定的間隔給服務器發請求,詢問服務器有沒有新消息。這個機制的缺點一是實時性不夠,二是頻繁的請求會給服務器帶來極大的壓力。
Comet本質上也是輪詢,但是在沒有消息的情況下,服務器先拖一段時間,等到有消息了再回復。這個機制暫時地解決了實時性問題,但是它帶來了新的問題:以多線程模式運行的服務器會讓大部分線程大部分時間都處於掛起狀態,極大地浪費服務器資源。另外,一個HTTP連接在長時間沒有數據傳輸的情況下,鏈路上的任何一個網關都可能關閉這個連接,而網關是我們不可控的,這就要求Comet連接必須定期發一些ping數據表示連接“正常工作”。
二、關於websocket
WebSocket 協議在2008年誕生,2011年成爲國際標準。所有瀏覽器都已經支持。
它的最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的全雙工通信。
三、websocket的請求響應過程
首先,WebSocket連接必須由瀏覽器發起,因爲請求協議是一個標準的HTTP請求,格式如下:
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13
該請求和普通的HTTP請求有幾點不同:
- GET請求的地址不是類似
/path/
,而是以ws://
開頭的地址; - 請求頭
Upgrade: websocket
和Connection: Upgrade
表示這個連接將要被轉換爲WebSocket連接; Sec-WebSocket-Key
是用於標識這個連接,並非用於加密數據;Sec-WebSocket-Version
指定了WebSocket的協議版本。
隨後,服務器如果接受該請求,就會返回如下響應:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string
該響應代碼101
表示本次連接的HTTP協議即將被更改,更改後的協議就是Upgrade: websocket
指定的WebSocket協議。
版本號和子協議規定了雙方能理解的數據格式,以及是否支持壓縮等等。如果僅使用WebSocket的API,就不需要關心這些。
現在,一個WebSocket連接就建立成功,瀏覽器和服務器就可以隨時主動發送消息給對方。消息有兩種,一種是文本,一種是二進制數據。通常,我們可以發送JSON格式的文本,這樣,在瀏覽器處理起來就十分容易。
四、爲什麼WebSocket連接可以實現全雙工通信而HTTP連接不行呢?
實際上HTTP協議是建立在TCP協議之上的,TCP協議本身就實現了全雙工通信,但是HTTP協議的請求-應答機制限制了全雙工通信。WebSocket連接建立以後,其實只是簡單規定了一下:接下來,咱們通信就不使用HTTP協議了,直接互相發數據吧。
安全的WebSocket連接機制和HTTPS類似。首先,瀏覽器用wss://xxx
創建WebSocket連接時,會先通過HTTPS創建安全的連接,然後,該HTTPS連接升級爲WebSocket連接,底層通信走的仍然是安全的SSL/TLS協議。
五、WebSocket 的用法
var ws = new WebSocket("wss://echo.websocket.org");
ws.onopen = function(evt) {
console.log("Connection open ...");
ws.send("Hello WebSockets!");
};
ws.onmessage = function(evt) {
console.log( "Received Message: " + evt.data);
ws.close();
};
ws.onclose = function(evt) {
console.log("Connection closed.");
};
websocket API 詳見MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
六、服務端的實現
WebSocket 服務器的實現,可以查看維基百科的列表。
常用的 Node 實現有以下三種。
具體的用法請查看它們的文檔。
七、websocket的踩坑
1、IP訪問和域名訪問在瀏覽器中存儲的是兩個sessionid
類似二級域名,一級域名,ip訪問都會建立不同的session,比如我們在建立連接的時候地址是一個域名地址,但是如果發請求是ip地址,會造成sessionid驗證不通過。一個簡單取巧的方法:就是前端界面使用js獲取瀏覽器地址中的鏈接,判斷是使用了那種訪問方式,然後對應得去建立websocke連接。
2、瀏覽器不支持websocket請求
因爲soketjs在瀏覽器不支持websoket請求是會自動切換爲http請求輪訓方式。結果就掛了。
3、項目部署到遠程服務器,使用https協議,websocket使用wss://前綴連接,報400
修改Nginx以支持websocket
location / {
index index.html index.htm;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:8382/;
#注意這裏一定要加下面這兩行,否則問題還是沒解決
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_set_header Connection "upgrade";
}
location /socketServer {
proxy_pass http://127.0.0.1:8382/platform/socketServer;
proxy_set_header Host $host; # pass the host header
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_set_header Connection "upgrade";
proxy_set_header X-Real-IP $remote_addr; # Preserve client IP
proxy_set_header X-Forwarded-For $remote_addr;
proxy_http_version 1.1;
}
4、連接會自動斷開連接
解決方法:心跳檢測。
實現心跳要雙向,前端發一個數據給服務器,服務器也要返回一個數據給前端,不然nginx轉發代理的時候,認爲客服端還連着,如果一直沒有接收到服務端的數據,會認爲服務端的連接已失效,然後就會斷開。
一種解決方式是前端發送“ping”給服務器,服務器收到並返回“pong”字符串。
【注意】心跳要每隔一段時間發送,前端的setTimeOut可以實現