WebSocket + Spring boot 实现简单的多人聊天
直接上代码:
HTML + JS 前端部分
这里的WebSocket
对象是关键,其他没啥说的。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="number" name="" id="id_input" value="" placeholder="用户ID(数字)" />
<button id="key">连接</button>
<br />
<input type="text" name="" id="msg_input" value="" />
<button id="send">发送</button>
<div id="msgbox">
<!-- 用来接受显示服务端发来的消息 -->
</div>
<script type="text/javascript">
var btn = document.getElementById("key");
var send = document.getElementById("send");
var msgbox = document.getElementById("msgbox");
btn.onclick = function() {
if ("WebSocket" in window) {
console.log("您的浏览器支持 WebSocket!");
// 打开一个 web socket并携带这用户ID的参数
var ws = new WebSocket("ws://127.0.0.1:8999/chat/" + document.getElementById("id_input").value);
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
console.log("连接成功...");
};
send.onclick = function() {
ws.send(document.getElementById("msg_input").value);
}
ws.onmessage = function(evt) {
//将数据回显到界面上
msgbox.innerHTML = msgbox.innerHTML + "<br/>" + evt.data;
};
ws.onclose = function() {
// 关闭 websocket
console.log("连接已关闭...");
};
} else {
// 浏览器不支持 WebSocket
console.log("您的浏览器不支持 WebSocket!");
}
}
</script>
</body>
</html>
Springboot后端部分
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>godkai.xyz</groupId>
<artifactId>websocketTest</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
</parent>
<dependencies>
<!--spring-boot starters-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
</dependencies>
</project>
2. yml配置文件
server:
port: 8999
3. 配置ServerEndpointExporter的Bean,让Springboot支持WebSocket
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4. 消息处理类(重点)
这里我个人理解是每有一个websocket的连接请求服务端会创建一个这个我写的对象ChatSocketEntity
前端那里:
"ws://127.0.0.1:8999/chat/{userId}"
这个类上:@ServerEndpoint("/chat/{userId}")
来控制每个websocket的事件监听,分别是
@OnOpen
建立连接时调用的方法@OnClose
收到客户端消息后调用的方法@OnMessage
连接关闭时调用的方法@OnError
发生错误时调用的方法
使用一个静态的Map来储存当前已经连接的用户,当然我觉得这个Map可以用任意容器来代替(Redis?)。
private static Map<Integer, ChatSocketEntity> onlineUserMap = new ConcurrentHashMap<Integer, ChatSocketEntity>();
这几个注解对应的方法。
其中private Session session
属性对应的是服务端与客户端的一个会话对象。
我理解成 在服务端的角度看 session 就是与客户端的一个TCP长连接,而我自己的ChatSocketEntity类就是用来控制这个 session 的生命周期的类。
代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Component
@ServerEndpoint("/chat/{userId}")
@Slf4j
public class ChatSocketEntity{
//用来保存所有已经连接上的ChatSocketEntity
private static Map<Integer, ChatSocketEntity> onlineUserMap = new ConcurrentHashMap<Integer, ChatSocketEntity>();
private Integer userId;
private Session session;
@OnOpen
public void onOpen(@PathParam("userId") Integer userId, Session session) {
//判断用户ID已经连接过
ChatSocketEntity chatSocketEntity = ChatSocketEntity.onlineUserMap.get(userId);
if (chatSocketEntity != null) {
try {
sendMessage("用户已在其他地方连接,您被迫下线", chatSocketEntity.session);
chatSocketEntity.session.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//保存用户连接的信息
this.userId = userId;
this.session = session;
ChatSocketEntity.onlineUserMap.put(userId, this);
log.info("user:" + userId + " is online!!!" + "|online count: " + onlineUserMap.size());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
ChatSocketEntity.onlineUserMap.remove(this.userId);
log.info("user:" + this.userId + " is leave!!!" + "|online count: " + onlineUserMap.size());
}
/**
* 收到客户端消息后调用的方法
*/
@OnMessage
public void onMessage(String message) {
Collection<ChatSocketEntity> values = onlineUserMap.values();
values.forEach(x -> {
try {
sendMessage(this.userId + ": " + message, x.session);
} catch (IOException e) {
e.printStackTrace();
}
});
log.info("user:" + this.userId + " say: " + message);
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误!!!");
error.printStackTrace();
}
/**
* 根据session向客户端发送消息
*/
public static void sendMessage(String message, Session session) throws IOException {
session.getBasicRemote().sendText(message);
}
}
总结
前端用 WebSocket 对象进行信息交互
后端用 ChatSocketEntity(需要自定义) 类的对象包裹着 javax.websocket.Session类的对象 来进行交互
本文章内容限于个人理解,本人小菜鸡一个,如有理解错误还望大佬能帮忙指出。