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的一种新的网络协议.它实现 ...
随机推荐
- sql server 查询所有表结构
SELECT CASE WHEN col.colorder = 1 THEN obj.name ELSE '' END AS 表名, Coalesce(epTwo.value, '') AS docu ...
- Hive基础测试操作
一.Hive测试 1.查看数据库 show databases; 2.使用某个数据库,如默认数据库 user default; 3.创建表 create table if not exist itst ...
- Scrapy:Python实现scrapy框架爬虫两个网址下载网页内容信息——Jason niu
import scrapy class DmozSpider(scrapy.Spider): name ="dmoz" allowed_domains = ["dmoz. ...
- 1.3 正则表达式和python语言-1.3.6匹配多个字符串
1.3.6 匹配多个字符串(2018-05-08) 我们在正则表达式 bat|bet|bit 中使用了择一匹配(|)符号.如下为在 Python中使用正则表达式的方法. import re #bat| ...
- 使用Jsoup实现java爬虫(非原创)
1,查看页面源代码,使用css或者JQuery选择器方式或元素节点选择 例如: 或者写成:Elements elements1 = Jsoup.connect("http://jb.999a ...
- vscode设置中文语言
https://jingyan.baidu.com/article/7e44095377c9d12fc1e2ef5b.html
- 使用IDEA时跳转到.class的解决办法
项目背景:jdk1.8 软件环境:IDEA 问题: 1. 两个不同的项目,在A项目中写了一个实体类.B项目中引用.在B项目中CTRL+鼠标左键点击进入,正常情况下是进入了源码文件,也就是.JAVA文件 ...
- java.lang.ClassCastException: net.sf.json.JSONNull cannot be cast to net.sf.json.JSONObject的解决方法
报错情况已经说明了,在百度查了好几个解决方法,这里总结一下: 首先:加一个判断是否为空,再做操作 // 得到json串 String jsonString = UtilPOSTGET.UPost(FO ...
- 关于在centos7 64为引用android so引发的问题修复
背景: 公司有解码的app,解码库位c++编写so动态库. 之前做过一版在调用html5摄像头,然后提取图像进行解码,后面因为图像质量不佳放弃. 最近 因为小程序api有更新 可以获取到相对清晰的图像 ...
- prometheus — 基于文件的服务发现
基于文件的服务发现方式不需要依赖其他平台与第三方服务,用户只需将要新的target信息以yaml或json文件格式添加到target文件中 ,prometheus会定期从指定文件中读取target信息 ...