概念:WebSocket是一種在單個TCP連接上進行全雙工通信的協議。
WebSocket使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,並進行雙向數據傳輸
背景:很多網站為了實現推送技術,所用的技術都是輪詢。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。這種傳統的模式帶來很明顯的缺點,即瀏覽器需要不斷的向服務器發出請求,然而HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。
而比較新的技術去做輪詢的效果是Comet。這種技術雖然可以雙向通信,但依然需要反復發出請求。而且在Comet中,普遍采用的長鏈接,也會消耗服務器資源。
在這種情況下,HTML5定義了WebSocket協議,能更好的節省服務器資源和帶寬,並且能夠更實時地進行通訊。
websocket運用場景:
- 即時通訊:多媒體聊天,你可以使用該技術開個聊天室,聊個火熱。可以單獨2人聊個暢快。
- 互動游戲:現在多人游戲越來越火熱,那么多人游戲必須是時時的,不考慮其他因素,只是時效性方面,也可以用該技術做多人游戲。
- 協同合作:開發人員會有git,svn等代碼管理工具,但還是會有沖突。用此技術開發一個文檔協同編輯工具,使每個人的編輯內容都時時同步,將不會有沖突發生。
- 動態數據表報:類似通知變更,如有需求,可以開發一個時時的數據報表,使用此技術,服務端數據發生變化,可在表報上立刻顯示出來。如,電商平台的交易數據,每時每刻都在變化着,可以時時監控。
- 實時工具:如導航,實時查詢工具等也可使用。
原理:
WebSocket並不是全新的協議,而是利用了HTTP協議來建立連接。我們來看看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連接,需要通過瀏覽器發出請求,之后服務器進行回應,這個過程通常稱為“握手”(handshaking)
版本號和子協議規定了雙方能理解的數據格式,以及是否支持壓縮等等。如果僅使用WebSocket的API,就不需要關心這些。
現在,一個WebSocket連接就建立成功,瀏覽器和服務器就可以隨時主動發送消息給對方。消息有兩種,一種是文本,一種是二進制數據。通常,我們可以發送JSON格式的文本,這樣,在瀏覽器處理起來就十分容易。
為什么WebSocket連接可以實現全雙工通信而HTTP連接不行呢?實際上HTTP協議是建立在TCP協議之上的,TCP協議本身就實現了全雙工通信,但是HTTP協議的請求-應答機制限制了全雙工通信。WebSocket連接建立以后,其實只是簡單規定了一下:接下來,咱們通信就不使用HTTP協議了,直接互相發數據吧。
HTTP協議與websocket協議對比:
WebSocket目前支持兩種統一資源標志符ws和wss,類似於HTTP和HTTPS。
瀏覽器支持:
很顯然,要支持WebSocket通信,瀏覽器得支持這個協議,這樣才能發出ws://xxx的請求。目前,支持WebSocket的主流瀏覽器如下:
- Chrome
- Firefox
- IE >= 10
- Sarafi >= 6
- Android >= 4.4
- iOS >= 8
客戶端簡單實現:
新建websocket.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>websocket 客戶端</title>
<script type="text/javascript">
function WebSocketTest() { if ("WebSocket" in window) { alert("您的瀏覽器支持 WebSocket!"); // 創建一個 websocket
var ws = new WebSocket("ws://localhost:8887"); ws.onopen = function() { // Web Socket 已連接上,使用 send() 方法發送數據
ws.send("發送數據"); alert("數據發送中..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert("數據已接收..."); }; ws.onclose = function() { // 關閉 websocket
alert("連接已關閉..."); }; } else { // 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!"); } } </script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">運行 WebSocket</a>
</div>
</body>
</html>
客戶端API:
WebSocket 屬性
以下是 WebSocket 對象的屬性。假定我們使用了以上代碼創建了 Socket 對象:
屬性 |
描述 |
Socket.readyState |
只讀屬性 readyState 表示連接狀態,可以是以下值: 0 - 表示連接尚未建立。 1 - 表示連接已建立,可以進行通信。 2 - 表示連接正在進行關閉。 3 - 表示連接已經關閉或者連接不能打開。 |
Socket.bufferedAmount |
只讀屬性 bufferedAmount 已被 send() 放入正在隊列中等待傳輸,但是還沒有發出的 UTF-8 文本字節數。 |
WebSocket 事件
以下是 WebSocket 對象的相關事件。假定我們使用了以上代碼創建了 Socket 對象:
事件 |
事件處理程序 |
描述 |
open |
Socket.onopen |
連接建立時觸發 |
message |
Socket.onmessage |
客戶端接收服務端數據時觸發 |
error |
Socket.onerror |
通信發生錯誤時觸發 |
close |
Socket.onclose |
連接關閉時觸發 |
WebSocket 方法
以下是 WebSocket 對象的相關方法。假定我們使用了以上代碼創建了 Socket 對象:
方法 |
描述 |
Socket.send() |
使用連接發送數據 |
Socket.close() |
關閉連接 |
JAVA 服務端實現:
maven依賴
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.4.0</version>
</dependency>
服務端示例:ServerDemo.java
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
/**
* @Author 99514925@qq.com
* @Create 2020/2/3 23:03
**/
public class ServerDemo extends WebSocketServer {
public ServerDemo() throws UnknownHostException {
}
public ServerDemo(int port) throws UnknownHostException {
super(new InetSocketAddress(port));
System.out.println("websocket Server start at port:"+port);
}
/**
* 觸發連接事件
*/
@Override
public void onOpen(WebSocket conn, ClientHandshake clientHandshake) {
System.out.println("new connection ===" + conn.getRemoteSocketAddress().getAddress().getHostAddress());
}
/**
*
* 連接斷開時觸發關閉事件
*/
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
}
/**
* 客戶端發送消息到服務器時觸發事件
*/
@Override
public void onMessage(WebSocket conn, String message) {
System.out.println("you have a new message: "+ message);
//向客戶端發送消息
conn.send(message);
}
/**
* 觸發異常事件
*/
@Override
public void onError(WebSocket conn, Exception e) {
//e.printStackTrace();
if( conn != null ) {
//some errors like port binding failed may not be assignable to a specific websocket
}
}
/**
* 啟動服務端
* @param args
* @throws UnknownHostException
*/
public static void main(String[] args) throws UnknownHostException {
new ServerDemo(8887).start();
}
}
由於WebSocket是一個協議,服務器具體怎么實現,取決於所用編程語言和框架本身。