SpringBoot | 第十九章:web 應用開發之 WebSocket
前言
web開發
也講解了三章了,這章節開始講解關於與前端通訊相關知識。實現一個線上聊天室類似的功能或者後端推送訊息到前端,在沒有WebSocket
時,讀大學那夥還有接觸過DWR(Direct Web Remoting)
,也使用過輪詢的方式,當Servlet3.0
出來後,也有使用其非同步連線機制進行前後端通訊的。今天我們就來說說WebSocket
。它是HTML5
開始提供的。
關於WebSocket
WebSocket
是HTML5
開始提供的一種在單個TCP
連線上進行全雙工
通訊的協議。
在WebSocket API
中,瀏覽器和伺服器只需要做一個握手的動作,然後,瀏覽器和伺服器之間就形成了一條快速通道。兩者之間就直接可以資料互相傳送。
瀏覽器通過JavaScript
向伺服器發出建立WebSocket
連線的請求,連線建立以後,客戶端和伺服器端就可以通過TCP
連線直接交換資料。
當獲取Web Socket
連線後,你可以通過 send() 方法來向伺服器傳送資料,並通過 onmessage事件來接收伺服器返回的資料。
對於前端,建立一個WebSocket
物件,如下:
var Socket = new WebSocket(url, [protocol] );
說明:第一個引數 url, 指定連線的 URL。第二個引數 protocol 是可選的,指定了可接受的子協議。
WebSocker屬性
以下是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() | 關閉連線 |
WebSocket實踐
前面介紹了在
瀏覽器端
中webSocket
的相關知識點,現在我們就來搭建一個後臺對接應用,以實現一個簡單的線上聊天室。
一點知識
後端關於
WebSocket
的實現是基於JSR356
標準的。該標準的出現,統一了WebSocket
的程式碼寫法。只要支援web容器支援JSR356
標準,那麼實現方式是一致的。而目前實現方式有兩種,一種是註解
方式,另一種就是繼承繼承javax.websocket.Endpoint
類了。
常用註解說明
- @WebSocketEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket伺服器端。註解的值將被用於監聽使用者連線的終端訪問URL地址。
- @onOpen 開啟一個新連線,即有新連線時,會呼叫被此註解的方法。
- @onClose 關閉連線時呼叫。
- @onMessage 當伺服器接收到客戶端傳送的訊息時所呼叫的方法。
- @PathParam
接收
uri
引數的,與@PathVariable功能差不多,可通過url獲取對應值
搭建一個簡易聊天室
0.加入POM
依賴。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
1.編寫控制層,對應WebSocket
的各事件。同時抽取了個公用類,進行通用方法呼叫。
WebSocketController.java
/** * websocket 簡易聊天 * @author oKong * */ //由於是websocket 所以原本是@RestController的http形式 //直接替換成@ServerEndpoint即可,作用是一樣的 就是指定一個地址 //表示定義一個websocket的Server端 @Component @ServerEndpoint(value = "/my-chat/{usernick}") @Slf4j public class WebSocketController { /** * 連線事件 加入註解 * @param session */ @OnOpen public void onOpen(@PathParam(value = "usernick") String userNick,Session session) { String message = "有新遊客[" + userNick + "]加入聊天室!"; log.info(message); WebSocketUtil.addSession(userNick, session); //此時可向所有的線上通知 某某某登入了聊天室 WebSocketUtil.sendMessageForAll(message); } @OnClose public void onClose(@PathParam(value = "usernick") String userNick,Session session) { String message = "遊客[" + userNick + "]退出聊天室!"; log.info(message); WebSocketUtil.remoteSession(userNick); //此時可向所有的線上通知 某某某登入了聊天室 WebSocketUtil.sendMessageForAll(message); } @OnMessage public void OnMessage(@PathParam(value = "usernick") String userNick, String message) { //類似群發 String info = "遊客[" + userNick + "]:" + message; log.info(info); WebSocketUtil.sendMessageForAll(message); } @OnError public void onError(Session session, Throwable throwable) { log.error("異常:", throwable); try { session.close(); } catch (IOException e) { e.printStackTrace(); } throwable.printStackTrace(); } }
WebSocketUtil.java
public class WebSocketUtil { /** * 簡單使用map進行儲存線上的session * */ private static final Map<String, Session> ONLINE_SESSION = new ConcurrentHashMap<>(); public static void addSession(String userNick,Session session) { //putIfAbsent 新增鍵—值對的時候,先判斷該鍵值對是否已經存在 //不存在:新增,並返回null //存在:不覆蓋,直接返回已存在的值 // ONLINE_SESSION.putIfAbsent(userNick, session); //簡單示例 不考慮複雜情況。。怎麼簡單怎麼來了。。 ONLINE_SESSION.put(userNick, session); } public static void remoteSession(String userNick) { ONLINE_SESSION.remove(userNick); } /** * 向某個使用者傳送訊息 * @param session 某一使用者的session物件 * @param message */ public static void sendMessage(Session session, String message) { if(session == null) { return; } // getAsyncRemote()和getBasicRemote()非同步與同步 Async async = session.getAsyncRemote(); //傳送訊息 async.sendText(message); } /** * 向所有線上人傳送訊息 * @param message */ public static void sendMessageForAll(String message) { //jdk8 新方法 ONLINE_SESSION.forEach((sessionId, session) -> sendMessage(session, message)); } }
注意點:
- @ServerEndpoint的value值填寫時,開頭需要加上
/
,不然會提示路徑無效。 - 需要加上型別
@Component
註解,使得能被掃描到。 - 這裡的
session
等,都在包javax.websocket
包下的,注意區分。
2.編寫主啟動類,主要是加入註解@EnableWebSocket
和申明一個Websocket endpoint
類。
@SpringBootApplication @EnableWebSocket @Slf4j public class Chapter19Application { public static void main(String[] args) { SpringApplication.run(Chapter19Application.class, args); log.info("Chapter19啟動!"); } /** * 會自動註冊使用了@ServerEndpoint註解宣告的Websocket endpoint * 要注意,如果使用獨立的servlet容器, * 而不是直接使用springboot的內建容器, * 就不要注入ServerEndpointExporter,因為它將由容器自己提供和管理。 */ @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
3.啟動應用,利用線上的測試工具進行測試。這裡直接使用了http://coolaf.com/tool/chattest進行測試。當然也可以自己寫一個html
了。
我們再開一個標籤頁,然後繼續以另一個身份進入:
這時,可以看見第一個頁面開的,也收到訊息了。現在我們傳送一條訊息:
然後,其中一個斷開連線:
斷開連線
然後可以愉快聊天了,簡單的一個聊天室就完成了。
參考資料
總結
本章節主要是講解了
WebSocket
的使用。因為有統一標準的存在,編寫webSocket
也是很簡單的。對於如何一對一聊天,大家可以自行編寫下,因為知道了對方名稱,就能找出對方的session
然後就能傳送訊息了。
最後
目前網際網路上很多大佬都有
SpringBoot
系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實踐的。若文中有所錯誤之處,還望提出,謝謝。