WebSocket(5)---多人聊天系统
多人聊天系统
功能说明:多人聊天系统,主要功能点:
1、当你登陆成功后,可以看到所有在线用户(实际开发可以通过redis实现,我这边仅仅用map集合)
2、实现群聊功能,我发送消息,大家都可以看到。
先看案例效果:
这里面有关在线人数有个bug,就是在线用户会被覆盖,lisi登陆的话,zhangsan在线信息就丢来,xiaoxiao登陆,lisi就丢来,这主要原因是因为我放的是普通集合,所以在线用户数据是无法共享
所以只能显示最后显示的用户,如果放到redis就不会有这个问题。
一、案例说明
1、UserChatController
@Controller
public class UserChatController { @Autowired
private WebSocketService ws; /**
* 1、登陆时,模拟数据库的用户信息
*/
//模拟数据库用户的数据
public static Map<String, String> userMap = new HashMap<String, String>();
static{
userMap.put("zhangsan", "123");
userMap.put("lisi", "456");
userMap.put("wangwu", "789");
userMap.put("zhaoliu", "000");
userMap.put("xiaoxiao", "666");
} /**
*2、 模拟用户在线进行页面跳转的时候,判断是否在线
* (这个实际开发中肯定存在redis或者session中,这样数据才能共享)
* 这里只是简单的做个模拟,所以暂且用普通map吧
*/
public static Map<String, User> onlineUser = new HashMap<>();
static{
//key值一般是每个用户的sessionID(这里表示admin用户一开始就在线)
onlineUser.put("123",new User("admin","888"));
} /**
*3、 功能描述:用户登录接口
*/
@RequestMapping(value="login", method=RequestMethod.POST)
public String userLogin( @RequestParam(value="username", required=true)String username,
@RequestParam(value="pwd",required=true) String pwd, HttpSession session) { //判断是否正确
String password = userMap.get(username);
if (pwd.equals(password)) {
User user = new User(username, pwd);
String sessionId = session.getId(); //用户登陆成功就把该用户放到在线用户中...
onlineUser.put(sessionId, user);
//跳到群聊页面
return "redirect:/group/chat.html";
} else {
return "redirect:/group/error.html";
} } /**
*4、 功能描述:用于定时给客户端推送在线用户
*/
@Scheduled(fixedRate = 2000)
public void onlineUser() {
ws.sendOnlineUser(onlineUser);
} /**
*5、 功能描述 群聊天接口
* message 消息体
* headerAccessor 消息头访问器,通过这个获取sessionId
*/
@MessageMapping("/group/chat")
public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor){
//这个sessionId是在HttpHandShakeIntecepter拦截器中放入的
String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
//通过sessionID获得在线用户信息
User user = onlineUser.get(sessionId);
message.setFrom(user.getUsername());
ws.sendTopicChat(message); }
}
2、握手请求的拦截器
/**
* WebSocket握手请求的拦截器. 检查握手请求和响应, 对WebSocketHandler传递属性
* 可以通过这个类的方法获取resuest,和response
*/
public class HttpHandShakeIntecepter implements HandshakeInterceptor{ //在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
//这个项目只在WebSocketSession这里存入sessionID
@Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception { System.out.println("【握手拦截器】beforeHandshake"); if(request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
HttpSession session = servletRequest.getServletRequest().getSession();
String sessionId = session.getId();
System.out.println("【握手拦截器】beforeHandshake sessionId="+sessionId);
//这里将sessionId放入SessionAttributes中,
attributes.put("sessionId", sessionId);
} return true;
} //在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头(这个项目没有用到该方法)
@Override
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
System.out.println("【握手拦截器】afterHandshake"); if(request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
HttpSession session = servletRequest.getServletRequest().getSession();
String sessionId = session.getId();
System.out.println("【握手拦截器】afterHandshake sessionId="+sessionId);
}
}
}
3、频道拦截器
/**
* 功能描述:频道拦截器 ,类似管道,可以获取消息的一些meta数据
*/
public class SocketChannelIntecepter extends ChannelInterceptorAdapter{ /**
* 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理
*/
@Override
public void afterSendCompletion(Message<?> message, MessageChannel channel,
boolean sent, Exception ex) {
System.out.println("SocketChannelIntecepter->afterSendCompletion");
super.afterSendCompletion(message, channel, sent, ex);
} /**
* 在消息被实际发送到频道之前调用
*/
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
System.out.println("SocketChannelIntecepter->preSend"); return super.preSend(message, channel);
} /**
* 发送消息调用后立即调用
*/
@Override
public void postSend(Message<?> message, MessageChannel channel,
boolean sent) {
System.out.println("SocketChannelIntecepter->postSend"); StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息头访问器 if (headerAccessor.getCommand() == null ) return ;// 避免非stomp消息类型,例如心跳检测 String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId); switch (headerAccessor.getCommand()) {
case CONNECT:
connect(sessionId);
break;
case DISCONNECT:
disconnect(sessionId);
break;
case SUBSCRIBE:
break; case UNSUBSCRIBE:
break;
default:
break;
}
} /**
* 连接成功
*/
private void connect(String sessionId){
System.out.println("connect sessionId="+sessionId);
} /**
* 断开连接
*/
private void disconnect(String sessionId){
System.out.println("disconnect sessionId="+sessionId);
//用户下线操作
UserChatController.onlineUser.remove(sessionId);
} }
4、修改webSocket配置类
既然写了两个拦截器,那么肯定需要在配置信息里去配置它们。
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { /**
*配置基站
*/
public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()).setAllowedOrigins("*").withSockJS();
} /**
* 配置消息代理(中介)
* enableSimpleBroker 服务端推送给客户端的路径前缀
* setApplicationDestinationPrefixes 客户端发送数据给服务器端的一个前缀
*/
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic","/chat");
registry.setApplicationDestinationPrefixes("/app"); } @Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors( new SocketChannelIntecepter());
} @Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.interceptors( new SocketChannelIntecepter());
} }
5、app.js
登陆页面和群聊页面就不细聊,贴上代码就好。
index.html
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
<title>Hello WebSocket</title>
<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/group/main.css" rel="stylesheet">
<script src="/webjars/jquery/jquery.min.js"></script>
</head>
<body> <div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline" method='post' action="/login">
<div class="form-group">
<input type="text" name="username" class="form-control" placeholder="用户名">
<input type="password" name="pwd" class="form-control" placeholder="密码">
<input type="submit" class="default" value="登录" />
</div>
</form>
</div>
</div>
</div>
</body>
</html>
index.html
chat.html
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<head>
<title>devsq聊天室</title>
<link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/group/main.css" rel="stylesheet">
<script src="/webjars/jquery/jquery.min.js"></script>
<script src="/webjars/sockjs-client/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/stomp.min.js"></script>
<script src="/group/app.js"></script>
</head>
<body> <div id="main-content" class="container">
<div class="row">
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<label for="connect">建立连接通道:</label>
<button id="connect" class="btn btn-default" type="submit">Connect</button>
<button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
</button>
</div>
</form>
</div>
<div class="col-md-6">
<form class="form-inline">
<div class="form-group">
<input type="text" id="content" class="form-control" placeholder="请输入...">
</div>
<button id="send" class="btn btn-default" type="submit">发送</button>
</form>
</div>
</div>
<div class="row">
<div class="col-md-6">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>实时在线用户列表</th>
</tr>
</thead>
<tbody id='online'>
</tbody>
</table>
</div> <div class="col-md-6">
<table id="conversation" class="table table-striped">
<thead>
<tr>
<th>聊天记录</th>
</tr>
</thead>
<tbody id='record'>
</tbody>
</table>
</div> </div>
</div>
</body>
</html>
chat.html
app.js
var stompClient = null; //一加载就会调用该方法
function connect() {
var socket = new SockJS('/endpoint-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame); //订阅群聊消息
stompClient.subscribe('/topic/chat', function (result) {
showContent(JSON.parse(result.body));
}); //订阅在线用户消息
stompClient.subscribe('/topic/onlineuser', function (result) {
showOnlieUser(JSON.parse(result.body));
}); });
} //断开连接
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
} //发送聊天记录
function sendContent() {
stompClient.send("/app/group/chat", {}, JSON.stringify({'content': $("#content").val()})); } //显示聊天记录
function showContent(body) {
$("#record").append("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
} //显示实时在线用户
function showOnlieUser(body) {
$("#online").html("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
} $(function () { connect();//自动上线 $("form").on('submit', function (e) {
e.preventDefault();
}); $( "#disconnect" ).click(function() { disconnect(); });
$( "#send" ).click(function() {
sendContent();
});
});
gitHub源码:https://github.com/yudiandemingzi/spring-boot-websocket-study
我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,
天一亮,又是崭新的起点,又是未知的征程(上校1)
WebSocket(5)---多人聊天系统的更多相关文章
- 基于SpringBoot+WebSocket搭建一个简单的多人聊天系统
前言 今天闲来无事,就来了解一下WebSocket协议.来简单了解一下吧. WebSocket是什么 首先了解一下WebSocket是什么?WebSocket是一种在单个TCP连接上进行全双工 ...
- webSocket实现多人聊天功能
webSocket实现多人在线聊天 主要思路如下: 1.使用vue构建简单的聊天室界面 2.基于nodeJs 的webSocket开启一个socket后台服务,前端使用H5的webSocket来创建一 ...
- nodejs+websocket实时聊天系统
介绍下websocket: webSocket协议本质上是一个基于tcp的协议; 建立一个websocket连接,大体的过程: 1.客户端浏览器首先向服务器发起一个http请求,这个请求和平常的请求有 ...
- WebSocket实现实时聊天系统
WebSocket实现实时聊天系统 等闲变却故人心,却道故人心易变. 简介:前几天看了WebSocket,今天体验下它的实时聊天. 一.项目介绍 WebSocket 实时聊天系统自己一个一码的搞出来还 ...
- html websocket
from:http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/ websocket 规范升级过,在该链接的文章内未提及,后面 ...
- HTML5学习之WebSocket通讯(六)
WebSocket是下一代客户端-服务器的异步通信方法. WebSocket最伟大之处在于服务器和客户端可以在任意时刻相互推送信息 WebSocket允许跨域通信 Ajax技术需要客户端发起请求,We ...
- WebSocket C# Demo
WebSocket 规范 WebSocket 协议本质上是一个基于 TCP 的协议.为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTT ...
- WebSocket简单介绍(WebSocket 实战)(3)
这一节里我们用一个案例来演示怎么使用 WebSocket 构建一个实时的 Web 应用.这是一个简单的实时多人聊天系统,包括客户端和服务端的实现.客户端通过浏览器向聊天服务器发起请求,服务器端解析客户 ...
- Websocket教程SpringBoot+Maven整合(详情)
1.大话websocket及课程介绍 简介: websocket介绍.使用场景分享.学习课程需要什么基础 笔记: websocket介绍: WebSocket协议是基于TCP的一种新的网络协议.它实现 ...
随机推荐
- UOJ#129. 【NOI2015】寿司晚宴 动态规划
原文链接www.cnblogs.com/zhouzhendong/p/UOJ129.html 题解 考虑把大于等于 $\sqrt n$ 的质数和小于 $\sqrt n$ 的分开考虑: 1. 小于等于 ...
- Vue-Router动态路由匹配
//重点在于路由出口 <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- & ...
- 【java】-- 多线程之间实现通讯
1.多线程之间如何实现通讯 1.1.什么是多线程之间通讯? 多线程之间通讯,其实就是多个线程在操作同一个资源,但是操作的动作不同. 画图演示 1.2.多线程之间通讯需求 需求:第一个线程写入(inpu ...
- Love Live!-01字典树启发式合并
链接:https://ac.nowcoder.com/acm/contest/201/D?&headNav=www 思路:题目要求的是每个等级下的最大 简单路径中的最大异或值,那么我们为了保证 ...
- ISP PIPLINE (九_2) Denoise 之 time domain denoise
时域噪声是空域噪声在时间上波动的一种描述. 1.多帧平均去噪法 1.1 理论: 1.2 帧数增加,噪声减小: 1.3 IIR滤波器的效果 2.1中的两种方法在拍摄视频的时候,如果有运动物体,则会出现拖 ...
- 动态规划——Freedom Trail
题目:https://leetcode.com/problems/freedom-trail/ 额...不解释大意了,题目我也不想写过程了有点繁琐,直接给出代码: public int findRot ...
- USACO 邮票 Stamps
f[x]表示组成 x 最少需要的邮票数量 一一举例 最多贴5张邮票,有三种邮票可用,分别是1分,3分,8分 组成0分需要0张邮票 ——f[0]=0 组成1分需要在0分的基础上加上一张1分邮票 ——f[ ...
- C# 中传参中的OUT 和 ref 区别 笔记
//out传参前需要对参数进行赋值处理,ref则不需要.//out.ref 传参都可以对值进行改变 1 static void Main(string[] args) { ; //int J = 10 ...
- history.back(-1) 和history.go(-1) 有什么区别?
history.back(-1) 返回上一页,当前页面的数据都没有保存下来.就像当前也没有出现过一样. history.go(-1) 返回上一页,当前页的内容都保存下来了,包括session,等 ...
- 使用POST下载文件
一直以来,JS都没有比较好的可以直接处理二进制的方法.而Blob的存在,允许我们可以通过JS直接操作二进制数据.一.下载util.fetchDownload= function (opt,data) ...