WebSocket 理論+實踐
阿新 • • 發佈:2018-12-01
1. 理論
1.1 Http和WebSocket
1.1.1 HTTP
http協議在通訊過程中存在一個巨大的缺陷,通訊只能由客戶端發起,伺服器只能根據響應返回響應的結果。也就說,伺服器端無法主動給客戶端傳送訊息。
對於伺服器端連續的狀態變化,http協議就顯得有些力不從心了,當然也可以通過其他的方式實現。比如:
- 輪詢(每隔一段時候,就發出一個詢問,瞭解伺服器有沒有新的資訊)
- long poll(採用阻塞模式,客戶端發起連線後,如果沒訊息,就一直不返回Response給客戶端。直到有訊息才返回,返回完之後,客戶端再次建立連線,周而復始)。
雖然這樣也可以實現我們的要求,但是資源就在輪詢的過程中被大量浪費。
1.1.1 WebSocket
WebSocket協議,2008年誕生,2011年稱為國際標準,其最大的特點就是伺服器端可以主動向客戶端傳送訊息,實現真正的雙向平等對話。主要特點:
- 建立在tcp協議之上,伺服器端的實現比較容易
- 與http協議有很好的相容性,握手階段採用http協議
- 資料格式比較輕量,效能開銷小,通訊高效
- 可以傳送文字,也可以傳送二進位制
- 沒有同源策略(htpp的同源策略主要是出於安全考慮)
- 協議標識是ws,如ws://127.0.0.1:8080/myHandler/{Id}"
理論沒看懂的可以戳這 故事描述型
1.2 WebSocket工作方式
1.2.1 WebSocket 客戶端
建立WebSocket
var Socket = new WebSocket(url, [protocol] );//協議可以為空
屬性
Socket.readyState//連線狀態
Socket.bufferedAmount //佇列中等待傳輸,但是還沒有發出的 UTF-8 文字位元組數。
事件,編寫的時候要加上on 比如onOpen
open //連線建立時觸發
message //客戶端接收服務端資料時觸發
error //通訊發生錯誤時觸發
close //連線關閉時觸發
方法
Socket.send() //使用連線傳送資料 Socket.close() //關閉連線
例項:
// 初始化一個 WebSocket 物件
var ws = new WebSocket("ws://localhost:9998/echo");
// 建立 web socket 連線成功觸發事件
ws.onopen = function () {
// 使用 send() 方法傳送資料
ws.send("傳送資料");
alert("資料傳送中...");
};
// 接收服務端資料時觸發事件
ws.onmessage = function (evt) {
var received_msg = evt.data;
alert("資料已接收...");
};
// 斷開 web socket 連線成功觸發事件
ws.onclose = function () {
alert("連線已關閉...");
};
1.2.2 WebSocket 伺服器端
伺服器端的就主要用程式碼來實現吧
2. 實踐篇
原始碼地址 密碼:f28e
服務端獲取訊息很簡單,主要是向伺服器端傳送訊息。需要向客戶端傳送訊息,那麼我們需要知道客戶端的某個唯一標識,那麼這個標識用什麼來表示呢,那就是session。
2.1 普通javaEE方式
直接貼碼,裡面註釋很清晰 需要的依賴
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
java原始碼
package me.gacl.websocket;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
/**
* @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端,
* 註解的值將被用於監聽使用者連線的終端訪問URL地址,客戶端可以通過這個URL來連線到WebSocket伺服器端
*/
@ServerEndpoint("/websocket")
public class WebSocketTest {
//靜態變數,用來記錄當前線上連線數。應該把它設計成執行緒安全的。
private static int onlineCount = 0;
//concurrent包的執行緒安全Set,用來存放每個客戶端對應的MyWebSocket物件。若要實現服務端與單一客戶端通訊的話,可以使用Map來存放,其中Key可以為使用者標識
private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();
//與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
private Session session;
/**
* 連線建立成功呼叫的方法
* @param session 可選的引數。session為與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
*/
@OnOpen
public void onOpen(Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //線上數加1
System.out.println("有新連線加入!當前線上人數為" + getOnlineCount());
}
/**
* 連線關閉呼叫的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //線上數減1
System.out.println("有一連線關閉!當前線上人數為" + getOnlineCount());
}
/**
* 收到客戶端訊息後呼叫的方法
* @param message 客戶端傳送過來的訊息
* @param session 可選的引數
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的訊息:" + message);
//群發訊息
for(WebSocketTest item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發生錯誤時呼叫
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("發生錯誤");
error.printStackTrace();
}
/**
* 這個方法與上面幾個方法不一樣。沒有用註解,是根據自己需要新增的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketTest.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketTest.onlineCount--;
}
}
2.2 spring boot整合
這裡通過重新寫一個controller,直接給客戶端傳送訊息,先貼這裡的程式碼,應該大部分人都是想實現這個功能。這裡的目的是,在處理一個其他請求之後,需要給原來的客戶端傳送訊息,告訴它我已經處理完了,收到訊息之後再處理後續的邏輯(掃碼場景比較普遍)。
@GetMapping("/")
public WebsocketResponse sendSuccess(){
MyHandler send = new MyHandler();
TextMessage msg = new TextMessage("發給客戶端");
send.sendMessageToUser("888",msg);
return new WebsocketResponse(1);
}
有需要的直到原始碼中拉取程式碼吧,伺服器端的原理都類似
2.2.1 程式碼注意問題
- 這裡的session一定是需要回調的那個客戶端的session,所以第一次請求是需要儲存客戶端的session,公司一般放在redis中快取。