pom地址


<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

服务端教程


步骤一:服务端Socket设计

本次将用到以下注解:

  1. @Component:标记为Spring容器
  2. @ServerEndpoint:服务器Socket的地址
  3. @OnOpen:建立连接成功事件标记
  4. @OnClose:建立连接结束事件标记
  5. @OnMessage:接收消息事件

示例程序:

@Slf4j
@Component
@ServerEndpoint("/api/socket/{roomId}")
public class WebSocketServer {

    private Integer roomId;

    /**
     * 建立连接成功
     * @param session
     * @param userId
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("roomId") Integer userId) {
        this.roomId = userId;
        log.info(session.getId());
        log.info("roomId: {} 建立连接", userId);
    }

    @OnClose
    public void onClose() {
        log.info("连接关闭");
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            log.info("message: {}", message);
            session.getBasicRemote().sendText("消息已接收");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

步骤二:Configuration自动装配容器

示例程序:

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

效果演示

可以使用网站测试

image-rdbatpws.png

image-curhxgng.png

后端也可以看到:

image-dszxbyta.png

案例:简易聊天室

@Slf4j
@Component
@ServerEndpoint("/api/socket/{roomId}")
public class WebSocketServer {

    // 房间会话映射: roomId -> (sessionId -> Session)
    private static final ConcurrentHashMap<Integer, ConcurrentHashMap<String, Session>> roomSessions = new ConcurrentHashMap<>();

    // 当前会话的 roomId
    private Integer currentRoomId;

    @OnOpen
    public void onOpen(Session session, @PathParam("roomId") Integer roomId) {
        this.currentRoomId = roomId;

        roomSessions.computeIfAbsent(roomId, k -> new ConcurrentHashMap<>()).put(session.getId(), session);

        log.info("roomId: {} 新连接, 房间连接数: {}", roomId, getRoomSize(roomId));
    }

    @OnClose
    public void onClose(Session session) {
        roomSessions.get(currentRoomId).remove(session.getId());

        // 清理空房间
        if (getRoomSize(currentRoomId) == 0) roomSessions.remove(currentRoomId);

        log.info("roomId: {} 连接关闭, 剩余连接: {}", currentRoomId, getRoomSize(currentRoomId));
    }

    @OnMessage
    public void onMessage(String message) {
        // 向当前房间广播
        broadcastToRoom(currentRoomId, message);
    }

    // 房间广播方法
    public static void broadcastToRoom(Integer roomId, String message) {
        ConcurrentHashMap<String, Session> sessions = roomSessions.get(roomId);
        if (sessions == null) return;

        sessions.forEach((id, session) -> {
            if (session.isOpen()) session.getAsyncRemote().sendText(message);
        });
    }

    // 获取房间大小
    private static int getRoomSize(Integer roomId) {
        return roomSessions.getOrDefault(roomId, new ConcurrentHashMap<>()).size();
    }

}

原理:

  1. 定义了一个线程安全的HashMap存放房间号
  • 房间号对应多个Session(会话)
  1. 定义一个currentRoomId存放当前房间号