1. 程式人生 > >SpringBoot | 第十九章:web 應用開發之 WebSocket

SpringBoot | 第十九章:web 應用開發之 WebSocket

前言

web開發也講解了三章了,這章節開始講解關於與前端通訊相關知識。實現一個線上聊天室類似的功能或者後端推送訊息到前端,在沒有WebSocket時,讀大學那夥還有接觸過DWR(Direct Web Remoting),也使用過輪詢的方式,當Servlet3.0出來後,也有使用其非同步連線機制進行前後端通訊的。今天我們就來說說WebSocket。它是HTML5開始提供的。

關於WebSocket

WebSocketHTML5開始提供的一種在單個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系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實踐的。若文中有所錯誤之處,還望提出,謝謝。