前言
- 关于 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 = idvar _this = thisif (window.WebSocket) { //判断当前浏览器是否支持 websocketvar serverHot = window.location.hostname//携带参数 id userId userNamevar url = 'ws://' + serverHot + ':8082' + '/api/chat/private/' + id + '/' + this.userId + '/' + this.userNamevar ws = new WebSocket(url)_this.ws = wsws.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 = thisvar 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 {@Beanpublic 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<>();@OnOpenpublic 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);}}@OnMessagepublic 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);}@OnClosepublic 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);}@OnErrorpublic void onError(Throwable throwable) {System.out.println("出错: " + throwable.getMessage());}}
- 测试
- 服务器控制台
后浪 加油!
- 服务器控制台