Java 项目中的 WebSocket 实现

4,020 阅读4分钟

前篇 :从 0 到 1 的 websocket -- 概念篇

基于 tomcat 的 WebSocket 实现

这种方式需要 tomcat 7.x,JEE7 的支持。

首先创建一个 springboot 项目,在 pom.xml 中导入 websocket 所需要的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.0.12.RELEASE</version>
</dependency>

然后开启 WebSocket 的自动注册(如果使用的是独立的 servlet 容器,而不是 spring boot 的内置容器,就不需要注入这个,它将由容器自己提供和管理)

package com.example.demowebsocket.demowebsocket.socket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author yuanyiwen
 * @create 2019-12-03 18:38
 * @description
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

然后就可以创建 WebSocket 服务端啦!主要在于两个部分 :

  • 通过注解 @ServerEndpoint 来声明实例化 WebSocket 服务端
  • 通过注解 @OnOpen@OnMessage@OnClose@OnError 来声明回调函数
package com.example.demowebsocket.demowebsocket.socket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yuanyiwen
 * @create 2019-12-03 18:48
 * @description
 */
@ServerEndpoint(value = "/demoSocket/{userId}")
@Component
public class SocketServer {

    // 这里使用一个map来维护全部客户端用户
    private static Map<Long, Session> userMap = new ConcurrentHashMap<>();

    // 日志
    private Logger logger = LoggerFactory.getLogger(SocketServer.class);

    // 用户id
    private Long userId;

    /**
     * 打开连接时触发事件
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") Long userId) throws IOException{
        this.userId = userId;
        userMap.put(userId, session);
        logger.info("用户" + userId + "已上线");
    }

    /**
     * 收到消息时触发事件
     */
    @OnMessage
    public void onMessage(String message, Session session) throws IOException, InterruptedException {
        logger.info("用户" + userId + "发来的消息为 :" + message);
        session.getBasicRemote().sendText("你用户" + userId + "的喜悦,我服某器收到了! ");

        // 向全体在线成员推送这条消息
        sendMessageFromOneToAll(userId, message);
    }

    /**
     * 关闭连接时触发事件
     */
    @OnClose
    public void onClose() {
        userMap.remove(userId);
        logger.info("用户" + userId + "已下线");
    }

    /**
     * 传输消息错误时触发事件
     */
    @OnError
    public void onError(Throwable error) {
        logger.error("错误发生");
        error.printStackTrace();
    }

    /**
     * 给某个用户发送消息
     */
    public void sendMessageToOne(Long userId, String message) {

    }

    /**
     * 给所有在线用户发送消息
     */
    public void sendMessageToAll(String message) {

    }

    /**
     * 由某一用户向全体在线用户推送消息
     * @param userId 哪个用户
     * @param message 什么消息
     */
    public void sendMessageFromOneToAll(Long userId, String message) {
        userMap.forEach((id, session) -> {
            try {
                session.getBasicRemote().sendText("用户" + userId + "发消息来啦:" + message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

最后让我们来检验一下成果!首先用在线 WebSocket 测试开三个用户,然后让用户 2 发一条消息

可以看到在用户 2 的消息发送后,每个在线用户都能够收到来自服务器的主动推送 业务完美qwq

基于 Spring 的 WebSocket 实现

这种方式需要 spring4.x 的支持。由于使用了 socketjs,对于不支持 WebSocket 的浏览器可以模拟 WebSocket 的使用。

首先还是创建一个 spring boot 项目,导入依赖

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

然后新建一个配置类,用于添加服务端点,以接收客户端的连接

package com.example.demowebsocket.demowebsocket.springsocket;

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.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author yuanyiwen
 * @create 2019-12-04 21:40
 * @description
 */
@EnableWebSocket
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 这里的路径就是访问时的路径
        registry.addHandler(myHandler(), "/demoWebsocket").setAllowedOrigins("*");
    }

    @Bean
    public MyWebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }
}

最后构建服务端,具体的方法基本与上面对应,数据通过 session 进行传输

package com.example.demowebsocket.demowebsocket.springsocket;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author yuanyiwen
 * @create 2019-12-05 20:52
 * @description
 */
@Component
public class MyWebSocketHandler extends AbstractWebSocketHandler implements HandlerInterceptor {

    // 这里使用一个map来维护全部客户端用户
    private static Map<Long, WebSocketSession> userMap = new ConcurrentHashMap<>();

    // 日志
    private Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);

    // 用户id
    private Long userId;

    /**
     * 打开连接时触发事件
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        this.userId = (Long) session.getAttributes().get("userId");
        userMap.put(userId, session);
        logger.info("用户" + userId + "已上线");
    }

    /**
     * 收到消息时触发事件
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        logger.info("用户" + userId + "发来的消息为 :" + message);
        session.sendMessage(new TextMessage("你用户" + userId + "的喜悦,我服某器收到了! "));

        // 向全体在线成员推送这条消息
        sendMessageFromOneToAll(userId, message);
    }


    /**
     * 关闭连接时触发事件
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        userMap.remove(userId);
        logger.info("用户" + userId + "已下线");
    }

    /**
     * 传输消息错误时触发事件
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        logger.error("错误发生");
        exception.printStackTrace();
    }


    /**
     * 给某个用户发送消息
     */
    public void sendMessageToUser(Long userId, TextMessage message) {

    }

    /**
     * 给所有在线用户发送消息
     */
    public void sendMessageToUsers(TextMessage message) {

    }

    /**
     * 由某一用户向全体在线用户推送消息
     * @param userId 哪个用户
     * @param message 什么消息
     */
    public void sendMessageFromOneToAll(Long userId, TextMessage message) {
        userMap.forEach((id, session) -> {
            try {
                session.sendMessage(new TextMessage("用户" + userId + "发消息来啦:" + message));
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}

最后检查一下消息发送情况(这里测试的时候是把代码中从session获取userId改成了直接令 userId = 1L)

完成!(。・`ω´・)