SpringBoot+WebSocket實現(xiàn)即時通訊功能(Spring方式)
什么是websocket?
WebSocket是一種在單個TCP連接上進行全雙工通信的協(xié)議。WebSocket通信協(xié)議于2011年被IETF定為標準RFC 6455,并由RFC7936補充規(guī)范。WebSocket API也被W3C定為標準。
WebSocket使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動向客戶端推送數(shù)據(jù)。在WebSocket API中,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進行雙向數(shù)據(jù)傳輸。
為什么有了HTTP協(xié)議還要WebSocket
HTTP協(xié)議采用的是客戶端(瀏覽器)輪詢的方式,即客戶端發(fā)送請求,服務(wù)端做出響應(yīng),為了獲取最新的數(shù)據(jù),需要不斷的輪詢發(fā)出HTTP請求,占用大量帶寬。
WebSocket采用了一些特殊的報頭,使得瀏覽器和服務(wù)器只需要通過“握手”建立一條連接通道后,此鏈接保持活躍狀態(tài),之后的客戶端和服務(wù)器的通信都使用這個連接,解決了Web實時性的問題,相比于HTTP有一下好處:
一個Web客戶端只建立一個TCP連接
WebSocket服務(wù)端可以主動推送(push)數(shù)據(jù)到Web客戶端
有更加輕量級的頭,減少了數(shù)據(jù)傳輸量
特點
- 建立在TCP協(xié)議只上,服務(wù)端比較容易實現(xiàn)
- 于HTTP協(xié)議有良好的兼容性,默認端口也是80和443,握手階段使用HTTP協(xié)議,因此握手時不容易屏蔽,能通過各種HTTP代理服務(wù)器
- 數(shù)據(jù)格式輕量,通信高效且節(jié)省帶寬
- 支持傳輸文本數(shù)據(jù)和二進制數(shù)據(jù)
- 沒有同源限制,客戶端可以與任意服務(wù)器通信
- 也支持加密傳輸,WS+SSL,URL形如wss://
技術(shù)
- jdk8
- maven
- SpringBoot2.7.4
- thmeleaf
- websocket
實現(xiàn)(簡易websocket聊天室)
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>websocket</name>
<description>websocket</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
websocketHandler.java
package com.example.websocket.handler;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class WebsocketHandler implements WebSocketHandler {
private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("連接成功"+session.getId());
SESSIONS.put(session.getId(), session);
System.out.println("當前在線人數(shù):"+SESSIONS.size());
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
System.out.println("接收消息"+session.getId());
String msg = message.getPayload().toString();
System.out.println(msg);
// session.sendMessage(message);
sendMessageToAllUsers(session.getId()+":"+msg);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("連接出錯"+session.getId());
if (!session.isOpen()) {
SESSIONS.remove(session.getId());
session.close();
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
System.out.println("關(guān)閉連接"+session.getId());
if(!session.isOpen()){
SESSIONS.remove(session.getId());
System.out.println("當前在線人數(shù):"+SESSIONS.size());
}
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* sendMessageToUser:發(fā)給指定用戶
*
*/
public void sendMessageToUser(String userId, String contents) {
WebSocketSession session = SESSIONS.get(userId);
if (session != null && session.isOpen()) {
try {
TextMessage message = new TextMessage(contents);
session.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* sendMessageToAllUsers:發(fā)給所有的用戶
*
*/
public void sendMessageToAllUsers(String contents) {
Set<String> userIds = SESSIONS.keySet();
for (String userId : userIds) {
this.sendMessageToUser(userId, contents);
}
}
}
WebsocketInterceptor.java
package com.example.websocket.interceptor;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;
@Component
public class WebsocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
System.out.println("前置攔截");
return true;
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
System.out.println("后置攔截");
}
}
WebsocketConfig.java
package com.example.websocket.config;
import com.example.websocket.handler.WebsocketHandler;
import com.example.websocket.interceptor.WebsocketInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Component
@EnableWebSocket
public class WebsocketConfig implements WebSocketConfigurer {
@Autowired
private WebsocketHandler websocketHandler;
@Autowired
private WebsocketInterceptor websocketInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(websocketHandler, "/ws").setAllowedOrigins("*").addInterceptors(websocketInterceptor);
}
}
WebsocketController.java
package com.example.websocket.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class WebsocketController {
@GetMapping("/")
public String init() {
return "websocket";
}
@GetMapping("/chat")
public String chat() {
return "chatRoom";
}
}
WebsocketApplication.java
package com.example.websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WebsocketApplication {
public static void main(String[] args) {
SpringApplication.run(WebsocketApplication.class, args);
}
}
前端
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My WebSocket Test</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" rel="external nofollow" integrity="sha384-HSMxcRTRxnN+Bdg0JdbxYKrThecOKuH5zCYotlSAcp1+c8xmyTe9GYg1l9a69psu" crossorigin="anonymous">
<!-- 可選的 Bootstrap 主題文件(一般不用引入) -->
<link rel="stylesheet" rel="external nofollow" integrity="sha384-6pzBo3FDv/PJ8r2KRkGHifhEocL+1X2rVCTTkUfGk7/0pbek5mMa1upzvWbrUbOZ" crossorigin="anonymous">
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js" integrity="sha384-aJ21OjlMXNL5UyIl/XNwTMqvzeRMZH2w8c5cRVpzpU8Y5bApTppSuUkhZXN0VxHd" crossorigin="anonymous"></script>
</head>
<body>
Welcome<br/>
<div id="message"></div>
<input id="text" type="text" />
<button class="btn btn-primary" onclick="send()">Send</button>
<button class="btn btn-danger"onclick="closeWebSocket()">Close</button>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/ws");
}
else{
alert('Not support websocket')
}
//連接發(fā)生錯誤的回調(diào)方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回調(diào)方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//監(jiān)聽窗口關(guān)閉事件,當窗口關(guān)閉時,主動去關(guān)閉websocket連接,防止連接還沒斷開就關(guān)閉窗口,server端會拋異常。
window.onbeforeunload = function(){
websocket.close();
}
//將消息顯示在網(wǎng)頁上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關(guān)閉連接
function closeWebSocket(){
websocket.close();
}
//發(fā)送消息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
message.value("");
}
</script>
</html>
測試
先啟動服務(wù)

然后我們就可以愉快聊天了


結(jié)尾
到這我們的簡單版的ws長連接聊天室就做好了,以上就是SpringBoot+WebSocket實現(xiàn)即時通訊功能(Spring方式)的詳細內(nèi)容,更多關(guān)于SpringBoot+WebSocket即時通訊的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis實戰(zhàn)之TypeHandler高級進階
本文主要介紹了自定義的枚舉TypeHandler的相關(guān)知識,具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02
解決出現(xiàn) java.lang.ExceptionInInitializerError錯誤問題
這篇文章主要介紹了解決出現(xiàn) java.lang.ExceptionInInitializerError錯誤問題的相關(guān)資料,需要的朋友可以參考下2017-01-01
Java使用easyExcel批量導入數(shù)據(jù)詳解
這篇文章主要介紹了Java使用easyExcel批量導入數(shù)據(jù)詳解,通常我們會提供一個模板,此模塊我們可以使用easyExcel導出數(shù)據(jù)生成的一個Excel文件當作模板,提供下載鏈接,用戶在該文件內(nèi)填入規(guī)定的數(shù)據(jù)格式以后可以批量導入數(shù)據(jù)到數(shù)據(jù)庫中,需要的朋友可以參考下2023-08-08
SpringBoot多環(huán)境配置數(shù)據(jù)讀取方式
SpringBoot通過環(huán)境隔離機制,支持properties/yaml/yml多格式配置,結(jié)合@Value、Environment和@ConfigurationProperties實現(xiàn)靈活配置讀取,下面給大家介紹SpringBoot之多環(huán)境配置全解析,感興趣的朋友一起看看吧2025-07-07
Springboot項目參數(shù)校驗方式(Validator)
本文介紹了如何在Spring Boot項目中使用`spring-boot-starter-validation`包和注解來實現(xiàn)請求參數(shù)校驗,主要介紹了校驗注解的使用方法、校驗失敗的異常捕獲以及`@Validated`的分組功能2025-02-02
SpringCloudStream原理和深入使用小結(jié)
Spring?Cloud?Stream是一個用于構(gòu)建與共享消息傳遞系統(tǒng)連接的高度可擴展的事件驅(qū)動型微服務(wù)的框架,本文給大家介紹SpringCloudStream原理和深入使用,感興趣的朋友跟隨小編一起看看吧2024-06-06

