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");
}
}
-
@EnableWebSocketMessageBroker
注解表明: 这个配置类不仅配置了 WebSocket,还配置了基于代理的 Stomp消息 -
registry.addEndpoint("/endpointWisely").withSockJS();
添加一个服务端点(endpoint),来接收客户端的连接。endpoint的作用是客户端在订阅或发布消息 到目的地址前,要先连接该端点。withSockJS()
作用是添加SockJS支持。 -
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;
}
}
- 当浏览器向服务端发送请求时,通过
@MessageMapping
映射publicChat
这个地址。 - 当服务端由消息时,会对订阅了
@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>
-
var socket=new SockJS("/endpointWisely");
连接SockJS的endpoint名称为/endpointWisely
,建立连接对象。 -
stompClient=Stomp.over(socket);
获取Stomp子协议的客户端对象。 - 当Stomp client创建好后,通过connect()方法进行与STOMP server的连接和认证,connect方法可接受多个参数来提供简单的API,如以下两种:
client.connect(headers, connectCallback);
client.connect(headers, connectCallback, errorCallback);
headers为map类型, connectCallback与errorCallback为回调函数。也可使用{}
来表示不附加任何headers参数。 -
client.subscribe(destination, callback);
浏览器接收一个消息,STOMP客户端首先必须订阅一个目标地址destination。这里订阅了/public/getResponse
这个地址。 - 当客户端与服务端连接成功后,可以调用
send()
方法来发送STOMP消息。这个方法必须有一个参数,用来描述对应的Stomp的目的地。另外可以有两个可选的参数:headers:object类型,包含额外的信息头;body:一个String类型的参数。stompClient.send("/publicChat",{},JSON.stringify({'name':name,'msg':msg}));
表示客户端将发送一个Stomp的帧到/publicChat
地址的目的地,无headers参数,消息体为名字和发送消息。注意:如果你想发送一个有消息体(body)的信息,也必须传递headers参数。如果没有headers需要传递,可以用{}来表示。 - 如果想发送和接收JSON对象的消息,可以通过
JSON.stringify()
和JSON.parse()
来转换。
演示效果
使用360浏览器输入http://localhost:8080/chatRoom
,点击连接,按提示操作。
使用chrome浏览器进行上述相同操作。输入消息,点击发送,消息发送成功。
切换回360浏览器,顺利接受到消息。
本人技术有限,有什么错误或不足,请大家指出。