欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-17 09:28:46
...

参考了《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,点击连接,按提示操作。
Spring Boot 使用WebSocket实现简单页面聊天室
使用chrome浏览器进行上述相同操作。输入消息,点击发送,消息发送成功。
Spring Boot 使用WebSocket实现简单页面聊天室
切换回360浏览器,顺利接受到消息。
Spring Boot 使用WebSocket实现简单页面聊天室

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