概述
WebSocket的故事系列计划分五大篇六章,旨在由浅入深的介绍WebSocket以及在Springboot中如何快速构建和使用WebSocket提供的能力。本系列计划包含如下几篇文章:
第一篇,什么是WebSocket以及它的用途
第二篇,Spring中如何利用STOMP快速构建WebSocket广播式消息模式
第三篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(1)
第四篇,Springboot中,如何利用WebSocket和STOMP快速构建点对点的消息模式(2)
第五篇,Springboot中,实现网页聊天室之自定义WebSocket消息代理
第六篇,Springboot中,实现更灵活的WebSocket
本篇的主线
本篇将通过一个接近真实的网页聊天室Demo,来详细讲述如何利用WebSocket来实现一些具体的产品功能。本篇将只采用WebSocket本身,不再使用STOMP等这些封装。亲自动手实现消息的接收、处理、发送以及WebSocket的会话管理。这也是本系列的最重要的一篇,不管你们激不激动,反正我是激动了。下面我们就开始。
本篇适合的读者
想了解如何在Springboot上自定义实现更为复杂的WebSocket产品逻辑的同学以及各路有志青年。
小小网页聊天室的需求
为了能够目标明确的表达本文中所要讲述的技术要点,我设计了一个小小聊天室产品,先列出需求,这样大家在看后面的实现时能够知其所以然。
以上就是我们本篇要实现的需求。简单说,就是:
用户可加入,退出某房间,加入后可向房间内所有人发送消息,也可向某个人发送悄悄话消息。
需求分析和设计
设计用户存储
很容易想到我们设计的主体就是用户、会话和房间,那么在用户管理上,我们就可以用下面这个图来表示他们之间的关系:
这样我们就可以用一个简单的Map来存储房间<->用户组
这样的映射关系,在用户组内我们再使用一个Map来存储用户名<->会话Session
这样的映射关系(假设没有重名)。这样,我们就解决了房间和用户组、用户和会话,这些关系的存储和维护。
设计用户行为与用户的关系
有兄弟看到这说了,“你讲这么半天了,跟之前几篇讲的什么STOMP,什么消息代理,有毛线的关系?”大兄弟你先消消气,我们学STOMP,学消息代理,学点对点消息,重要的是学思想,你说对不?下面我们就用上了。
当用户加入到某房间之后,房间里有任何风吹草动,即有人加入、退出或者发公屏消息,都会“通知”给该用户。到此,我们就可以将创建房间理解成“创建消息代理”,将用户加入房间,看成是对房间这个“消息代理”的一个“订阅”,将用户退出房间,看成是对房间这个“消息代理”的一个“解除订阅”。
那么,第一个加入房间的人,我们定义为“创建房间”,即创建了一个消息代理。为了好理解,上图:
其中红色的小人表示第一个加入房间的用户,即创建房间的人。当某用户发送消息时,如果选择将消息发送给聊天室的所有人,即相当于在房间里发送了一个广播,所有订阅这个房间的用户,都会收到这个广播消息;如果选择发送悄悄话,则只将消息发送给特定用户名的用户,即点对点消息。
总结一下我们要实现的要点:
- 用户存储,即用户,房间,会话之间的关系和对象访问方式。
- 动态创建消息代理(房间),并实现用户对房间的绑定(订阅)。
- 单独发送给某个用户消息的能力。
大体设计就到此为止,还有一些细节,我们先来看一下演示效果,再来看通过代码来讲解实现。
聊天室效果展示
用浏览器打开客户端页面后,展示输入框和加入按钮。输入房间号1和用户名小铭,点击进入房间。
进入房间成功后,展示当前房间人数和欢迎语。
当有其他人加入或退出房间时,展示通知信息。可以发送公屏消息和私聊消息。
下面就让我们看一下这些主要功能如何来实现吧。
代码实现
按照我们上述的设计,我会着重介绍重点部分的代码设计和技术要点。
服务端实现
1. 配置WebSocket
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new MyHandler(), "/webSocket/{INFO}").setAllowedOrigins("*")
.addInterceptors(new WebSocketInterceptor());
}
}
复制代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
要点解析:
- 注册
WebSocketHandler
(MyHandler
),这是用来处理WebSocket建立以及消息处理的类,后面会详细介绍。 - 注册
WebSocketInterceptor
拦截器,此拦截器用来在客户端向服务端发起初次连接时,记录客户端拦截信息。 - 注册WebSocket地址,并附带了
{INFO}
参数,用来注册的时候携带用户信息。
以上都会在后续代码中详细介绍。
2. 实现握手拦截器
public class WebSocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
if (serverHttpRequest instanceof ServletServerHttpRequest) {
String INFO = serverHttpRequest.getURI().getPath().split("INFO=")[1];
if (INFO != null && INFO.length() > 0) {
JSONObject jsonObject = new JSONObject(INFO);
String command = jsonObject.getString("command");
if (command != null && MessageKey.ENTER_COMMAND.equals(command)) {
System.out.println("当前session的ID="+ jsonObject.getString("name"));
ServletServerHttpRequest request = (ServletServerHttpRequest) serverHttpRequest;
HttpSession session = request.getServletRequest().getSession();
map.put(MessageKey.KEY_WEBSOCKET_USERNAME, jsonObject.getString("name"));
map.put(MessageKey.KEY_ROOM_ID, jsonObject.getString("roomId"));
}
}
}
return true;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21