Spring Boot 使用WebSocket实现简单页面聊天室

参考了《JavaEE开发的颠覆者 Spring Boot实战》中的实现

WebSocket

WebSocket 是 Html5 新增加特性之一,目的是浏览器与服务端建立全双工的通信方式,解决 http 请求-响应带来过多的资源消耗。也能够实现 web 浏览器 和 server 间的异步通信,全双工意味着 server 与 浏览器间 可以发送和接收消息。
可以直接使用WebSocket,也可以使用SockJS(SockJS是WebSocket协议的模拟,当浏览器不支持WebSocket的时候转换为其他通信方式)。

Stomp

Stomp是WebSocket的子协议,提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。它们的不同在于WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义。STOMP 在 WebSocket 之上提供了一个基于帧(frame)的线路格式层,用来定义消息语义。

简单页面聊天室的实现

使用Spring Boot+SockJS+Stomp实现。

pom.xml主要依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
</dependency>

配置WebSocket

package com.example.mysite.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // TODO Auto-generated method stub
        registry.addEndpoint("/endpointWisely").withSockJS();
    }
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // TODO Auto-generated method stub
        registry.enableSimpleBroker("/public");
    }
}
  1. @EnableWebSocketMessageBroker注解表明: 这个配置类不仅配置了 WebSocket,还配置了基于代理的 Stomp消息
  2. registry.addEndpoint("/endpointWisely").withSockJS();添加一个服务端点(endpoint),来接收客户端的连接。endpoint的作用是客户端在订阅或发布消息 到目的地址前,要先连接该端点。withSockJS()作用是添加SockJS支持。
  3. registry.enableSimpleBroker("/public");配置了一个 简单的消息代理,通俗一点讲就是设置消息连接请求的各种规范信息。消息代理将会处理前缀为“/public”的消息。

model

浏览器向服务端发送的消息由此类接收。

package com.example.mysite.model;
public class WiselyMessage {
    private String name;
    private String msg;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }

}

服务器向浏览器发送此类的消息

package com.example.mysite.model;
public class WiselyResponse {
    private String responseMsg;

    public String getResponseMsg() {
        return responseMsg;
    }
    public void setResponseMsg(String responseMsg) {
        this.responseMsg = responseMsg;
    }
}

Controller

package com.example.mysite.controller;
import java.util.Date;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import com.example.mysite.model.WiselyMessage;
import com.example.mysite.model.WiselyResponse;
@Controller
public class WebSocketController {
    @MessageMapping("/publicChat")
    @SendTo("/public/getResponse")
    public WiselyResponse say(WiselyMessage message){
        WiselyResponse response=new WiselyResponse();
        StringBuilder msg=new StringBuilder();
        msg.append(message.getName()).append(" -- ")
            .append(new Date()).append("\n>>> ")
            .append(message.getMsg()).append('\n');
        response.setResponseMsg(msg.toString());
        return response;
    }
}
  1. 当浏览器向服务端发送请求时,通过@MessageMapping映射publicChat这个地址。
  2. 当服务端由消息时,会对订阅了@SendTo中的路径的浏览器发送消息。

页面chatRoom.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>聊天室</title>
</head>
<body>
<label id="msg" >未连接</label><br/>
<button id="connect" onclick="connect()">连接</button>
<button id="disConnect" disabled="disabled" onclick="disConnect()">断开</button>
<label id="username" ></label>
<br/>
<textarea id="getMsg" rows="25" cols="60" disabled="disabled"></textarea><br/>
<textarea id="sendMsg" rows="3" cols="60"></textarea><br/>
<button id="send" onclick="sendMsg()" disabled="disabled">发送</button>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script> 
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script type="text/javascript">
function connect(){
    var username=prompt("输入用户名");
    if(username==null||username==""){
        alert("需输入用户名才能连接");
    }
    else{
        var socket=new SockJS("/endpointWisely");
        stompClient=Stomp.over(socket);
        stompClient.connect({},function(frame){
            document.getElementById("username").innerHTML=username;
            document.getElementById("connect").disabled=true;
            document.getElementById("disConnect").disabled=false;
            document.getElementById("send").disabled=false;
            document.getElementById("msg").innerHTML="连接成功";
            stompClient.subscribe('/public/getResponse', function (response){
                var msg=JSON.parse(response.body).responseMsg;
                document.getElementById("getMsg").append(msg);
                })
            },function(error){
            document.getElementById("msg").innerHTML="连接失败";
            }); 

    }
}
function disConnect(){
    document.getElementById("username").innerHTML=null;
    document.getElementById("connect").disabled=false;
    document.getElementById("disConnect").disabled=true;
    document.getElementById("send").disabled=true;
    ocument.getElementById("msg").innerHTML="未连接";
    if(stompClient!=null){
        stompClient.disconnect();
    }
}
function sendMsg(){
    var name=document.getElementById("username").innerHTML;
    var msg=document.getElementById("sendMsg").value;
    stompClient.send("/publicChat",{},JSON.stringify({'name':name,'msg':msg}));
}
</script>
</body>
</html>
  1. var socket=new SockJS("/endpointWisely");连接SockJS的endpoint名称为/endpointWisely,建立连接对象。
  2. stompClient=Stomp.over(socket);获取Stomp子协议的客户端对象。
  3. 当Stomp client创建好后,通过connect()方法进行与STOMP server的连接和认证,connect方法可接受多个参数来提供简单的API,如以下两种:
    client.connect(headers, connectCallback);
    client.connect(headers, connectCallback, errorCallback);

    headers为map类型, connectCallback与errorCallback为回调函数。也可使用{}来表示不附加任何headers参数。
  4. client.subscribe(destination, callback);浏览器接收一个消息,STOMP客户端首先必须订阅一个目标地址destination。这里订阅了/public/getResponse这个地址。
  5. 当客户端与服务端连接成功后,可以调用send()方法来发送STOMP消息。这个方法必须有一个参数,用来描述对应的Stomp的目的地。另外可以有两个可选的参数:headers:object类型,包含额外的信息头;body:一个String类型的参数。stompClient.send("/publicChat",{},JSON.stringify({'name':name,'msg':msg}));表示客户端将发送一个Stomp的帧到/publicChat地址的目的地,无headers参数,消息体为名字和发送消息。注意:如果你想发送一个有消息体(body)的信息,也必须传递headers参数。如果没有headers需要传递,可以用{}来表示
  6. 如果想发送和接收JSON对象的消息,可以通过JSON.stringify()JSON.parse()来转换。

演示效果

使用360浏览器输入http://localhost:8080/chatRoom,点击连接,按提示操作。
演示1
使用chrome浏览器进行上述相同操作。输入消息,点击发送,消息发送成功。
演示2
切换回360浏览器,顺利接受到消息。
演示3

本人技术有限,有什么错误或不足,请大家指出。

猜你喜欢

转载自blog.csdn.net/YINLINNEVERG/article/details/80269382