使用 Spring Boot websocket 写简单网页聊天室

Spring Boot 为 websocket 提供了一些默认配置,简化了 websocket 使用,这里我们将使用 Spring Boot websocket,并加入 stomp 和 sockjs 的支持,快速编写一个简单的网页聊天室,实现广播消息推送以及点对点的私人消息推送。

篇幅限制下边只给出关键的代码,需要 HTML 页面代码或项目完整源代码的可到这里查看(使用 IDEA 导入后可直接运行)。

基本页面:

将服务器关闭,客户端自动重连:

超过重连次数后,不再重连:

项目的编写分为服务器端和客户端,因此下边也就分为这两个方面进行讲述

服务端

由于 spring boot 的默认配置,大大简化了开发,服务器端需要编写的只有两个地方:配置类和控制器。

当然首先需要引入依赖(这里使用 gradle 作为包管理工具)

compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-websocket')

编写配置类

然后是 websocket 的配置类 WebSocketConfig,配置项的解释见注释

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic", "/user");// “/topic“ 用于给客户端订阅广播信息,”/user“用于给客户端订阅点对点消息

        //registry.setApplicationDestinationPrefixes("/app");// 客户端向服务器发送消息时 URL 为:【/app + controller中 @MessageMapping 的地址】,此处暂时不使用该配置
        //registry.setUserDestinationPrefix("/user/");// 客户端向指定用户发送(一对一)信息时 URL 前缀是“/user/”,即使不配置默认也是“/user/“
    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/endpoint").withSockJS();//注册客户端websocket连接的端口
    }
}

编写 controller

@Controller
public class WebsocketController {
    /**
     * 广播推送
     * @param text
     * @param sessionId
     * @return
     * @throws Exception
     */
    @MessageMapping(value = "/chat")
    @SendTo("/topic/getResponse")
    public String talk(@Payload String text, @Header("simpSessionId") String sessionId) throws Exception {
        return "【" + sessionId + "】说:【" + text + "】";
    }
    /**
     * 点对点推送
     * @param text
     * @param sessionId
     * @return
     * @throws Exception
     */
    @MessageMapping(value = "/speak")
    @SendToUser(value = "/personal")
    public String speak(@Payload String text, @Header("simpSessionId") String sessionId) throws Exception {
        return text;
    }
    /**
     * 异常信息推送
     * @param exception
     * @return
     */
    @MessageExceptionHandler
    @SendToUser(value = "/errors")
    public String handleException(Throwable exception) {
        return exception.getMessage();
    }
}

这里需要说一下其中用到的几个注解:
- 方法注解
- @MessageMapping(value = “/URL”) 匹配客户端 send 消息时的URL;
- @sendTo 和 @sendToUser 分别用于给客户端订阅广播消息和点对点消息;
- 参数注解
- @Payload:使用客户端 STOMP 帧的 body 赋值;
- @Header(“xxx”):使用客户端 STOMP 帧的 headers 中的 xxx 赋值;

在查阅资料的时候,看到也可以使用 SimpMessagingTemplate 进行消息的代理转发,可以这么理解:

@MessageMapping("/chat")    
public void chat(String value){
    this.simpMessagingTemplate.convertAndSend("/topic/getResponse", value);    
}

等同于

@MessageMapping("/chat")
@SendTo("/topic/getResponse")
public String chat(String value) {
    return value;
}

客户端

客户端的代码主要是使用 JavaScript 进行 websocket 的一系列操作,由于使用 STOMP 协议,因此需要使用 STOMP API, 关于 STOMP API 的使用,可以参考这里的 总结。

基本操作如下

var socket = new SockJS('/endpoint');
stompClient = Stomp.over(socket);
stompClient.connect(
    {}, 
    function connectCallback (frame) {...}, 
    function errorCallBack (error) {...}
);

一对一消息推送

// 获取 socket 连接的sessionId,即从 socket._transport.url 中使用正则截取
var sessionId = /\/([^\/]+)\/websocket/.exec(socket._transport.url)[1];
console.log("connected, session id: " + sessionId);
......
// 订阅私人消息
var subscription_personal = stompClient.subscribe('/user/' + sessionId + '/personal', function callBack (response) {
    if (response.body) {
        printToScreen("【私人消息】" + response.body);
    } else {
        printToScreen("收到一个空消息");
    }
}
......
// 发送消息
stompClient.send("/speak", headers, JSON.stringify(body));

广播消息推送

......
// 订阅广播消息
var subscription_broadcast = stompClient.subscribe('/topic/getResponse', function callBack (response) {
    if (response.body) {
        printToScreen("【广播】" + response.body);
    } else {
        printToScreen("收到一个空消息");
    }
});
......
// 发送广播消息
stompClient.send("/chat", headers, JSON.stringify(body));

异常消息推送

// 获取 websocket 连接的 sessionId
var sessionId = /\/([^\/]+)\/websocket/.exec(socket._transport.url)[1];
// 订阅异常消息
var subscription_errors = stompClient.subscribe('/user/' + sessionId + '/errors', function callBack (response) {
    if (response.body) {
        printToScreen("【异常消息】" + response.body);
    } else {
        printToScreen("收到一个空消息");
    }
});

自动重连机制

使用JavaScript设置自动重连机制,与服务器断开连接后自动重来,重连失败超过10后不再重连

function errorCallBack (error) {
     document.getElementById("state-info").innerHTML = "连接断开";
     console.log('连接断开【' + error + '】');

     if (curTryNum <= maxTryNum) {
         document.getElementById("state-info").innerHTML = "连接关闭,10秒后重新连接……";
         console.log("连接关闭,10秒后重新连接……");

         // 10秒后重新连接,实际效果:每10秒重连一次,直到连接成功
         setTimeout(function () {
             connect();
         }, 10000);
     } else {
         document.getElementById("state-info").innerHTML = "连接关闭,且已超过最大重连次数,不再重连";
         console.log("连接关闭,且已超过最大重连次数,不再重连");
     }
}

你可能感兴趣的:(java)