前言
- 关于 WebSocket:
1、WebSocket 是 html5 提供的通讯协议
2、特点是建立在单个 tcp 连接上的全双工协议
3、浏览器向服务器发起 WebSocket 连接请求,当成功获取到连接后,就可以实现浏览器和服务器之间的数据传输 - 版本
后端: springboot 2.0
前端: vue
测试: Chrome 8:81.0 - 源码
后端: 后端源码 github
前端: 前端源码 github - 在线体验地址 (组件管理/聊天室 test 123456)
正文
-
客户端
首先,既然要在前端发起一个 websocket 连接请求,那么肯定得有一个实际存在的地址,也就是我们在服务器端配置的链接地址。然后我们通过 var ws = new WebSocket(url) 来获取 websocket 客户端对象,成功获取到连接之后,我们就可以获取 websocket 回调函数,然后进行数据传输。- 回调函数
事件 描述 ws.onopen 连接成功之后调用 ws.onmessage 消息发送之后调用 ws.close 连接关闭之后调用 ws.error 发生异常时调用 - 代码
//初始化 websocket
initWebSocket(id) {
this.id = id
var _this = this
if (window.WebSocket) { //判断当前浏览器是否支持 websocket
var serverHot = window.location.hostname
//携带参数 id userId userName
var url = 'ws://' + serverHot + ':8082' + '/api/chat/private/' + id + '/' + this.userId + '/' + this.userName
var ws = new WebSocket(url)
_this.ws = ws
ws.onopen = function(e) { //回调函数
console.log('连接成功' + url)
}
ws.onmessage = function(e) {
_this.msgList.push(JSON.parse(e.data)) //msgList 为消息列表
_this.onlineNum = JSON.parse(e.data).onlineNum //当前在线人数
console.log('发送成功' + url)
}
ws.onclose = function(e) {
console.log('关闭连接' + url)
}
}
},
//发送消息
sendMsg() {
var _this = this
var params = {
content: _this.content
}
_this.ws.send(JSON.stringify(params))
this.content = ''
},
- 服务端
- 服务端自动注册
首先早 pom 文件中引入依赖 spring-boot-starter-websocket 。
ServerEndpointExporter 是由Spring官方提供的标准实现,用于扫描ServerEndpointConfig配置类和@ServerEndpoint注解实例。使用规则也很简单:1.如果使用默认的嵌入式容器 比如Tomcat 则必须手工在上下文提供ServerEndpointExporter。2. 如果使用外部容器部署war包,则不要提供提供ServerEndpointExporter,因为此时SpringBoot默认将扫描服务端的行为交给外部容器处理。
//开启 websocket 服务端自动注册
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 创建 websocket 服务端
创建 WebSocketController
1、通过注解 @ServerEndpoint 来声明实例化 websocket ,也就是前端 websocket 对象连接时的地址
2、通过注解 @OnOpen、@OnMessage、@OnClose、@OnError 来声明 websocket 回调函数 - 代码
@Component
@ServerEndpoint("/chat/private/{id}/{userId}/{userName}")
public class WebSocketController {
//群组中的每一个成员的 websocket 连接
private static ConcurrentHashMap<String, List<Session>> groupMap = new ConcurrentHashMap<>();
//群组中的消息
private static ConcurrentHashMap<String, List<MessageDto>> groupMsgMap = new ConcurrentHashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam("id") String id, @PathParam("userId") String userId,
@PathParam("userName") String userName) {
List<Session> list = groupMap.get(id);
if (list != null && list.size() > 0 && list.get(0).getPathParameters().get("userId").equals(userId)) {
System.out.println("用户 " + userName + " 再次进入聊天室 " + id);
} else {
list = groupMap.computeIfAbsent(id, k -> new ArrayList<>());
list.add(session);
onMessage(id, userId, userName, "{'content':'用户 " + userName + " 上线了';" + "'onlineNum':" + list.size() + "}");
System.out.println("用户 " + userName + " 进入聊天室 " + id);
}
}
@OnMessage
public void onMessage(@PathParam("id") String id, @PathParam("userId") String userId,
@PathParam("userName") String userName, String content) {
List<Session> list = groupMap.get(id);
list.forEach(item -> {
MessageDto messageDto = JSON.parseObject(content, MessageDto.class);
messageDto.setFromUser(userName);
messageDto.setOnlineNum(list.size());
String json = JSON.toJSONString(messageDto);
try {
item.getBasicRemote().sendText(json);
} catch (IOException e) {
e.printStackTrace();
}
});
System.out.println("用户 " + userName + " 在聊天室 " + id + " 中发送了消息: " + content);
}
@OnClose
public void onClose(Session session, @PathParam("id") String id, @PathParam("userId") String userId,
@PathParam("userName") String userName) {
List<Session> list = groupMap.get(id);
list.remove(session);
onMessage(id, userId, userName, "{'content':'用户 " + userName + " 下线了'}");
System.out.println("用户 " + userName + " 退出聊天室 " + id);
}
@OnError
public void onError(Throwable throwable) {
System.out.println("出错: " + throwable.getMessage());
}
}
- 测试
- 服务器控制台
后浪 加油!
- 服务器控制台