WebSocket là một giao thức truyền thông hai chiều, được sử dụng để thực hiện giao tiếp thời gian thực giữa client và server. Môi trường phát triển: JDK 1.8, Windows 7 64-bit, IntelliJ IDEA. 1. Tạo một dự án Spring Boot mới 2. Thêm thư viện WebSocket```
Trong file pom.xml, phần dependencies sẽ bao gồm:``` <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> 2.0.0
<!-- Công cụ Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Tomcat tích hợp -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<!-- Kiểm thử -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
3. Viết lớp xử lý WebSocket phía server 3.1, WebSocketEndPoint là thành phần chính của WebSocket server @PathParam là một annotation thuộc gói javax.websocket.server, dùng để trích xuất giá trị từ đường dẫn URL. Trong URL, có thể sử dụng các tham số key và name để kiểm soát quyền truy cập vào WebSocket. Key có thể là token người dùng sau khi đăng nhập, và có thể được sử dụng để xác thực hoặc tạo khóa duy nhất để quản lý kết nối trực tuyến. Mặc dù không thực hiện việc lưu trữ cặp (key + name), nhưng có thể làm như vậy để theo dõi kết nối.```
package com.geniuses.sewage_zero_straight.net.websocket;
import com.geniuses.sewage_zero_straight.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.*;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketHandler.createKey;
@Slf4j
@Component
@ServerEndpoint("/net/websocket/{key}/{name}")//Định nghĩa điểm cuối WebSocket
public class WebSocketEndPoint {
private static UserService userService;
@Autowired
public void setUserService(UserService userService){
WebSocketEndPoint.userService = userService;
}
@OnOpen
public void onOpen(@PathParam("key") String key, @PathParam("name") String name, Session session){
log.info("Kết nối mới: {}", session);
add(createKey(key, name), session);
WebSocketHandler.sendMessage(session, key + name);
log.info("Số người online: {}",count());
sessionMap().keySet().forEach(item -> log.info("Người dùng online: ", item));
for (Map.Entry<String, Session> item : sessionMap().entrySet()){
log.info("12: {}", item.getKey());
}
}
@OnMessage
public void onMessage(String message){
log.info("Có tin nhắn mới: {}", message);
}
@OnClose
public void onClose(@PathParam("key") String key, @PathParam("name") String name,Session session){
log.info("Kết nối đóng: {}", session);
remove(createKey(key, name));
log.info("Số người online: {}",count());
sessionMap().keySet().forEach(item -> log.info("Người dùng online: ", (item.split("@"))[1]));
for (Map.Entry<String, Session> item : sessionMap().entrySet()){
log.info("12: {}", item.getKey());
}
}
@OnError
public void onError(Session session, Throwable throwable){
try {
session.close();
} catch (IOException e) {
log.error("onError Exception: {}", e);
}
log.info("Kết nối gặp lỗi: {}", throwable);
}
}
3.2, WebSocketPool là lớp quản lý danh sách kết nối trực tuyến``` package com.geniuses.sewage_zero_straight.net.websocket;
import lombok.extern.slf4j.Slf4j;
import javax.websocket.Session; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
@Slf4j public class WebSocketPool {
// Lưu trữ các phiên kết nối trực tuyến
private static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
/**
* Thêm một phiên kết nối
* @param key
* @param session
*/
public static void add(String key, Session session){
if (!key.isEmpty() && session != null){
ONLINE_USER_SESSIONS.put(key, session);
}
}
/**
* Xóa một phiên theo key
* @param key
*/
public static void remove(String key){
if (!key.isEmpty()){
ONLINE_USER_SESSIONS.remove(key);
}
}
/**
* Đếm số người online
* @return
*/
public static int count(){
return ONLINE_USER_SESSIONS.size();
}
/**
* Trả về danh sách phiên kết nối
* @return
*/
public static Map<String, Session> sessionMap(){
return ONLINE_USER_SESSIONS;
}
}
3.3, WebSocketHandler là lớp xử lý hành động liên quan đến WebSocket```
package com.geniuses.sewage_zero_straight.net.websocket;
import lombok.extern.slf4j.Slf4j;
import javax.websocket.RemoteEndpoint;
import javax.websocket.Session;
import java.io.IOException;
import static com.geniuses.sewage_zero_straight.net.websocket.WebSocketPool.sessionMap;
@Slf4j
public class WebSocketHandler {
/**
* Tạo khóa từ key và tên người dùng
* @param key
* @param name
* @return
*/
public static String createKey(String key, String name){
return key + "@" + name;
}
/**
* Gửi tin nhắn đến một phiên cụ thể
* @param session
* @param msg
*/
public static void sendMessage(Session session, String msg){
if (session == null)
return;
final RemoteEndpoint.Basic basic = session.getBasicRemote();
if (basic == null)
return;
try {
basic.sendText(msg);
} catch (IOException e) {
log.error("sendText Exception: {}", e);
}
}
/**
* Gửi tin nhắn đến tất cả người dùng đang online
* @param message
*/
public static void sendMessageAll(String message){
log.info("Phát thông báo: gửi tin nhắn tới mọi người");
sessionMap().forEach((key, session) -> sendMessage(session, message));
}
}
- Triển khai giao diện người dùng4.1, index.html, trang này sử dụng jQuery và Bootstrap, bạn cần tự thêm các tài nguyên tương ứng```
4.2, Controller điều hướng đến trang index.html``` package com.geniuses.sewage_zero_straight.web;
import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/view") @Controller public class ViewController {
/**
* Trả về trang chủ
* @return
*/
@GetMapping("/index")
public String index(){
return "index";
}
}
5. Cấu hình WebSocket```
package com.geniuses.sewage_zero_straight.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
- Lưu ý: Lớp được đánh dấu bởi @ServerEndpoint không thể sử dụng @Autowired trực tiếp vì Spring sẽ khởi tạo nó một lần khi ứng dụng chạy, và mỗi lần có kết nối mới sẽ tạo một instance mới. Do đó, việc inject sẽ không hiệu quả trong những lần sau. Để giải quyết vấn đề này, có thể đặt biến cần inject là biến lớp, và cung cấp một phương thức set, sau đó sử dụng @Autowired trên phương thức đó. 7. Đây chỉ là một ví dụ đơn giản về WebSocket, có thể mở rộng hơn tùy theo yêu cầu.