前言
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
了。
首先,輸入我們的服務地址:ws://127.0.0.1:8080/my-chat/okong,連接后就可以看見服務器返回的消息了。
我們再開一個標簽頁,然后繼續以另一個身份進入:
這時,可以看見第一個頁面開的,也收到消息了。現在我們發送一條消息:
然后,其中一個斷開連接:
然后可以愉快聊天了,簡單的一個聊天室就完成了。
參考資料
- https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#websocket
- https://docs.spring.io/spring-boot/docs/1.5.15.RELEASE/reference/htmlsingle/#boot-features-websockets
- http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html
- http://www.runoob.com/html/html5-websocket.html
總結
本章節主要是講解了
WebSocket
的使用。因為有統一標准的存在,編寫webSocket
也是很簡單的。對於如何一對一聊天,大家可以自行編寫下,因為知道了對方名稱,就能找出對方的session
然后就能發送消息了。
最后
目前互聯網上很多大佬都有
SpringBoot
系列教程,如有雷同,請多多包涵了。本文是作者在電腦前一字一句敲的,每一步都是自己實踐的。若文中有所錯誤之處,還望提出,謝謝。
老生常談
- 個人QQ:
499452441
- 微信公眾號:
lqdevOps
個人博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-19
原文地址:http://blog.lqdev.cn/2018/08/14/springboot/chapter-nineteen/