SpringBoot使用WebSocket

服务器通信

Posted by Autuan.Yu on December 6, 2020

导入依赖

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

Server编写

首先编写一个Server 类:

public class WebSocketServer {
}

添加注解方法

为该类添加注解以应用

@Component
@ServerEndpoint("/front/websocket/{userId}")
@Slf4j
public class WebSocketServer {
}
  • @Component : org.springframework.stereotype.Component : 将此类声明为组件并交由Spring 管理
  • @ServerEndpoint : javax.websocket.server.ServerEndpoint : 声明为WebSocket 服务类; value 为客户端注册的URI
  • @Slf4j : lombok.extern.slf4j : 和本文主题``无关,用于日志输出

实现方法

打开第三方的websocket 依赖,我们会发现,在 javax.websocket下有4个注解:@OnOpen@OnMessage@Close@Error

我们需要在 Server 类中新加方法并使用到这些注解

@OnOpen

我们新增一个OnOpen方法

   @OnOpen
    public void onOpen() {

    }

我们定义了一个方法,这样在一个客户端建立链接时会执行此方法。

一般情况下,我们使用 websocket 时会需要向指定的会员发送信息。

所以我们为添加两个参数:

  • javax.websocket.Session
  • String
 	@OnOpen
    public void onOpen(Session session, String userId) {

    }

只定义不使用可不行,session 指的是每一个不同的链接,为了给指定用户发送消息,我们需要保存所有的session

在这里,我们使用类变量 ConcurrentHashMap 来保存每一个不同的session:

为了能够识别,同时增加类变量userId :

public class WebSocketServer {
    /**
     * 所有链接对象
     */
    private static ConcurrentHashMap<String,Session> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 用户ID
     */
    private String userId;


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        // 保存userId 到类变量
        this.userId = userId;
        // 连接建立:存入map
        webSocketMap.put(userId, session);
        log.info("WebSocketServer -> onOpen -> new connect -> userId -> {}", userId);
    }
}

细心的同学已经发现了一个新注解: @PathParam,这是用于获取注册URI中的路径参数{userId}而使用的。

类包: javax.websocket.server.PathParam

@OnMessage

WebSocket 是双向链接: 服务器可以给客户端发消息,客户端当然也可以给服务器发消息。

我们定义一个方法用来处理当客户端主动给服务器发送消息的情况。

	/**
     * 收到客户端消息的处理
     *
     * @param message 消息内容
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("WebSocketServer -> onMessage -> received ->  userId -> {} message -> {}",userId,message);
    }

此处只做了日志打印,需要的小伙伴可以自己实现所需要的业务逻辑

@OnClose

有建立链接,自然就有关闭链接

	/**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 链接关闭,移除map中的保存信息
        webSocketMap.remove(userId);
        log.info("WebSocketServer -> onClose -> userId -> {}", userId);
    }

@OnError

作为一个健壮的程序,当然省不了各种的异常处理了。

我们使用 @OnError 来处理错误:

    /**
     * 错误处理
     */
    @OnError
    public void onError(Session session, Throwable e) {
        log.error("WebSocketServer -> onError", e);
    }

同样,此处只做了日志打印

向客户端发送消息

websocket 主要特性之一 就是发送消息到客户端,发送消息主要是通过调用 Session 中的方法。

	/**
     * 发送自定义消息
     */
    public static void send(String message,String userId) throws IOException {
        log.info("WebSocketServer -> send -> userId -> {} msg -> {}",userId,message);
        if (StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
            webSocketMap.get(userId).getBasicRemote().sendText(message);
        } else {
           log.warn("WebSocketServer -> send -> offline -> userId -> {}",userId);
        }
    }

这个方法是静态方法:我们可以在需要的位置直接调用此方法来向指定的用户发送信息:

WebSocketServer.send("message content","1");

完整的Java 代码

/**
 * @author Autuan.Yu
 */
@ServerEndpoint("/front/websocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {
    /**
     * 所有链接对象
     */
    private static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 用户ID
     */
    private String userId;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        // 保存userId 到类变量
        this.userId = userId;
        // 连接建立:存入map
        webSocketMap.put(userId, session);
        log.info("WebSocketServer -> onOpen -> new connect -> userId -> {}", userId);
    }

    /**
     * 收到客户端消息的处理
     *
     * @param message 消息内容
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("WebSocketServer -> onMessage -> received ->  userId -> {} message -> {}",userId,message);
    }


    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        // 链接关闭,移除map中的保存信息
        webSocketMap.remove(userId);
        log.info("WebSocketServer -> onClose -> userId -> {}", userId);
    }

    /**
     * 错误处理
     */
    @OnError
    public void onError(Session session, Throwable e) {
        log.error("WebSocketServer -> onError", e);
    }

    /**
     * 发送自定义消息
     */
    public static void send(String message,String userId) throws IOException {
        log.info("WebSocketServer -> send -> userId -> {} msg -> {}",userId,message);
        if (StrUtil.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
            webSocketMap.get(userId).getBasicRemote().sendText(message);
        } else {
            log.warn("WebSocketServer -> send -> offline -> userId -> {}",userId);
        }
    }
}

HTML

启动Server

到这一步,服务器端就编写完成了,我们先通过RUN 将我们的WebSocket服务端启动, 这一步便不细说了。

新建HTML文件

在桌面,或者任何其他位置,新建一个HTML 文件。 文件内容则是普普通通的标准HTML 格式:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>websocket</title>
</head>
<body></body>

增加body 内容

userId:

开启socket
</body> ### script 编写 #### 引入 jquery 为了快速编写,先引入jquery,这里直接引入远程的jquery #### 编写websocket支持 事实上,现在主流的浏览器都内置了WebSocket的支持,我们可以直接通过`WebSocket`对象来使用 ```` html ```` ## 完整的 HTML 代码 ```` html <!DOCTYPE html> websocket

userId:

开启socket
</body> ````