多人聊天系统

功能说明:多人聊天系统,主要功能点:

1、当你登陆成功后,可以看到所有在线用户(实际开发可以通过redis实现,我这边仅仅用map集合)

2、实现群聊功能,我发送消息,大家都可以看到。

先看案例效果:

这里面有关在线人数有个bug,就是在线用户会被覆盖,lisi登陆的话,zhangsan在线信息就丢来,xiaoxiao登陆,lisi就丢来,这主要原因是因为我放的是普通集合,所以在线用户数据是无法共享

所以只能显示最后显示的用户,如果放到redis就不会有这个问题。

一、案例说明

1、UserChatController

  1. @Controller
  2. public class UserChatController {
  3.  
  4. @Autowired
  5. private WebSocketService ws;
  6.  
  7. /**
  8. * 1、登陆时,模拟数据库的用户信息
  9. */
  10. //模拟数据库用户的数据
  11. public static Map<String, String> userMap = new HashMap<String, String>();
  12. static{
  13. userMap.put("zhangsan", "123");
  14. userMap.put("lisi", "456");
  15. userMap.put("wangwu", "789");
  16. userMap.put("zhaoliu", "000");
  17. userMap.put("xiaoxiao", "666");
  18. }
  19.  
  20. /**
  21. *2、 模拟用户在线进行页面跳转的时候,判断是否在线
  22. * (这个实际开发中肯定存在redis或者session中,这样数据才能共享)
  23. * 这里只是简单的做个模拟,所以暂且用普通map吧
  24. */
  25. public static Map<String, User> onlineUser = new HashMap<>();
  26. static{
  27. //key值一般是每个用户的sessionID(这里表示admin用户一开始就在线)
  28. onlineUser.put("123",new User("admin","888"));
  29. }
  30.  
  31. /**
  32. *3、 功能描述:用户登录接口
  33. */
  34. @RequestMapping(value="login", method=RequestMethod.POST)
  35. public String userLogin( @RequestParam(value="username", required=true)String username,
  36. @RequestParam(value="pwd",required=true) String pwd, HttpSession session) {
  37.  
  38. //判断是否正确
  39. String password = userMap.get(username);
  40. if (pwd.equals(password)) {
  41. User user = new User(username, pwd);
  42. String sessionId = session.getId();
  43.  
  44. //用户登陆成功就把该用户放到在线用户中...
  45. onlineUser.put(sessionId, user);
  46. //跳到群聊页面
  47. return "redirect:/group/chat.html";
  48. } else {
  49. return "redirect:/group/error.html";
  50. }
  51.  
  52. }
  53.  
  54. /**
  55. *4、 功能描述:用于定时给客户端推送在线用户
  56. */
  57. @Scheduled(fixedRate = 2000)
  58. public void onlineUser() {
  59. ws.sendOnlineUser(onlineUser);
  60. }
  61.  
  62. /**
  63. *5、 功能描述 群聊天接口
  64. * message 消息体
  65. * headerAccessor 消息头访问器,通过这个获取sessionId
  66. */
  67. @MessageMapping("/group/chat")
  68. public void topicChat(InMessage message, SimpMessageHeaderAccessor headerAccessor){
  69. //这个sessionId是在HttpHandShakeIntecepter拦截器中放入的
  70. String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
  71. //通过sessionID获得在线用户信息
  72. User user = onlineUser.get(sessionId);
  73. message.setFrom(user.getUsername());
  74. ws.sendTopicChat(message);
  75.  
  76. }
  77. }

2、握手请求的拦截器

  1. /**
  2. * WebSocket握手请求的拦截器. 检查握手请求和响应, 对WebSocketHandler传递属性
  3. * 可以通过这个类的方法获取resuest,和response
  4. */
  5. public class HttpHandShakeIntecepter implements HandshakeInterceptor{
  6.  
  7. //在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
  8. //这个项目只在WebSocketSession这里存入sessionID
  9. @Override
  10. public boolean beforeHandshake(ServerHttpRequest request,
  11. ServerHttpResponse response, WebSocketHandler wsHandler,
  12. Map<String, Object> attributes) throws Exception {
  13.  
  14. System.out.println("【握手拦截器】beforeHandshake");
  15.  
  16. if(request instanceof ServletServerHttpRequest) {
  17. ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
  18. HttpSession session = servletRequest.getServletRequest().getSession();
  19. String sessionId = session.getId();
  20. System.out.println("【握手拦截器】beforeHandshake sessionId="+sessionId);
  21. //这里将sessionId放入SessionAttributes中,
  22. attributes.put("sessionId", sessionId);
  23. }
  24.  
  25. return true;
  26. }
  27.  
  28. //在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头(这个项目没有用到该方法)
  29. @Override
  30. public void afterHandshake(ServerHttpRequest request,
  31. ServerHttpResponse response, WebSocketHandler wsHandler,
  32. Exception exception) {
  33. System.out.println("【握手拦截器】afterHandshake");
  34.  
  35. if(request instanceof ServletServerHttpRequest) {
  36. ServletServerHttpRequest servletRequest = (ServletServerHttpRequest)request;
  37. HttpSession session = servletRequest.getServletRequest().getSession();
  38. String sessionId = session.getId();
  39. System.out.println("【握手拦截器】afterHandshake sessionId="+sessionId);
  40. }
  41. }
  42. }

3、频道拦截器

  1. /**
  2. * 功能描述:频道拦截器 ,类似管道,可以获取消息的一些meta数据
  3. */
  4. public class SocketChannelIntecepter extends ChannelInterceptorAdapter{
  5.  
  6. /**
  7. * 在完成发送之后进行调用,不管是否有异常发生,一般用于资源清理
  8. */
  9. @Override
  10. public void afterSendCompletion(Message<?> message, MessageChannel channel,
  11. boolean sent, Exception ex) {
  12. System.out.println("SocketChannelIntecepter->afterSendCompletion");
  13. super.afterSendCompletion(message, channel, sent, ex);
  14. }
  15.  
  16. /**
  17. * 在消息被实际发送到频道之前调用
  18. */
  19. @Override
  20. public Message<?> preSend(Message<?> message, MessageChannel channel) {
  21. System.out.println("SocketChannelIntecepter->preSend");
  22.  
  23. return super.preSend(message, channel);
  24. }
  25.  
  26. /**
  27. * 发送消息调用后立即调用
  28. */
  29. @Override
  30. public void postSend(Message<?> message, MessageChannel channel,
  31. boolean sent) {
  32. System.out.println("SocketChannelIntecepter->postSend");
  33.  
  34. StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);//消息头访问器
  35.  
  36. if (headerAccessor.getCommand() == null ) return ;// 避免非stomp消息类型,例如心跳检测
  37.  
  38. String sessionId = headerAccessor.getSessionAttributes().get("sessionId").toString();
  39. System.out.println("SocketChannelIntecepter -> sessionId = "+sessionId);
  40.  
  41. switch (headerAccessor.getCommand()) {
  42. case CONNECT:
  43. connect(sessionId);
  44. break;
  45. case DISCONNECT:
  46. disconnect(sessionId);
  47. break;
  48. case SUBSCRIBE:
  49. break;
  50.  
  51. case UNSUBSCRIBE:
  52. break;
  53. default:
  54. break;
  55. }
  56. }
  57.  
  58. /**
  59. * 连接成功
  60. */
  61. private void connect(String sessionId){
  62. System.out.println("connect sessionId="+sessionId);
  63. }
  64.  
  65. /**
  66. * 断开连接
  67. */
  68. private void disconnect(String sessionId){
  69. System.out.println("disconnect sessionId="+sessionId);
  70. //用户下线操作
  71. UserChatController.onlineUser.remove(sessionId);
  72. }
  73.  
  74. }

4、修改webSocket配置类

既然写了两个拦截器,那么肯定需要在配置信息里去配置它们。

  1. @EnableWebSocketMessageBroker
  2. public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  3.  
  4. /**
  5. *配置基站
  6. */
  7. public void registerStompEndpoints(StompEndpointRegistry registry) {
  8.  
  9. registry.addEndpoint("/endpoint-websocket").addInterceptors(new HttpHandShakeIntecepter()).setAllowedOrigins("*").withSockJS();
  10. }
  11.  
  12. /**
  13. * 配置消息代理(中介)
  14. * enableSimpleBroker 服务端推送给客户端的路径前缀
  15. * setApplicationDestinationPrefixes 客户端发送数据给服务器端的一个前缀
  16. */
  17. @Override
  18. public void configureMessageBroker(MessageBrokerRegistry registry) {
  19.  
  20. registry.enableSimpleBroker("/topic","/chat");
  21. registry.setApplicationDestinationPrefixes("/app");
  22.  
  23. }
  24.  
  25. @Override
  26. public void configureClientInboundChannel(ChannelRegistration registration) {
  27. registration.interceptors( new SocketChannelIntecepter());
  28. }
  29.  
  30. @Override
  31. public void configureClientOutboundChannel(ChannelRegistration registration) {
  32. registration.interceptors( new SocketChannelIntecepter());
  33. }
  34.  
  35. }

5、app.js

登陆页面和群聊页面就不细聊,贴上代码就好。

index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  4. <head>
  5. <title>Hello WebSocket</title>
  6. <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
  7. <link href="/group/main.css" rel="stylesheet">
  8. <script src="/webjars/jquery/jquery.min.js"></script>
  9. </head>
  10. <body>
  11.  
  12. <div id="main-content" class="container">
  13. <div class="row">
  14. <div class="col-md-6">
  15. <form class="form-inline" method='post' action="/login">
  16. <div class="form-group">
  17. <input type="text" name="username" class="form-control" placeholder="用户名">
  18. <input type="password" name="pwd" class="form-control" placeholder="密码">
  19. <input type="submit" class="default" value="登录" />
  20. </div>
  21. </form>
  22. </div>
  23. </div>
  24. </div>
  25. </body>
  26. </html>

index.html

 chat.html

  1. <!DOCTYPE html>
  2. <html>
  3. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  4. <head>
  5. <title>devsq聊天室</title>
  6. <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
  7. <link href="/group/main.css" rel="stylesheet">
  8. <script src="/webjars/jquery/jquery.min.js"></script>
  9. <script src="/webjars/sockjs-client/sockjs.min.js"></script>
  10. <script src="/webjars/stomp-websocket/stomp.min.js"></script>
  11. <script src="/group/app.js"></script>
  12. </head>
  13. <body>
  14.  
  15. <div id="main-content" class="container">
  16. <div class="row">
  17. <div class="col-md-6">
  18. <form class="form-inline">
  19. <div class="form-group">
  20. <label for="connect">建立连接通道:</label>
  21. <button id="connect" class="btn btn-default" type="submit">Connect</button>
  22. <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
  23. </button>
  24. </div>
  25. </form>
  26. </div>
  27. <div class="col-md-6">
  28. <form class="form-inline">
  29. <div class="form-group">
  30. <input type="text" id="content" class="form-control" placeholder="请输入...">
  31. </div>
  32. <button id="send" class="btn btn-default" type="submit">发送</button>
  33. </form>
  34. </div>
  35. </div>
  36. <div class="row">
  37. <div class="col-md-6">
  38. <table id="conversation" class="table table-striped">
  39. <thead>
  40. <tr>
  41. <th>实时在线用户列表</th>
  42. </tr>
  43. </thead>
  44. <tbody id='online'>
  45. </tbody>
  46. </table>
  47. </div>
  48.  
  49. <div class="col-md-6">
  50. <table id="conversation" class="table table-striped">
  51. <thead>
  52. <tr>
  53. <th>聊天记录</th>
  54. </tr>
  55. </thead>
  56. <tbody id='record'>
  57. </tbody>
  58. </table>
  59. </div>
  60.  
  61. </div>
  62. </div>
  63. </body>
  64. </html>

chat.html

app.js

  1. var stompClient = null;
  2.  
  3. //一加载就会调用该方法
  4. function connect() {
  5. var socket = new SockJS('/endpoint-websocket');
  6. stompClient = Stomp.over(socket);
  7. stompClient.connect({}, function (frame) {
  8. setConnected(true);
  9. console.log('Connected: ' + frame);
  10.  
  11. //订阅群聊消息
  12. stompClient.subscribe('/topic/chat', function (result) {
  13. showContent(JSON.parse(result.body));
  14. });
  15.  
  16. //订阅在线用户消息
  17. stompClient.subscribe('/topic/onlineuser', function (result) {
  18. showOnlieUser(JSON.parse(result.body));
  19. });
  20.  
  21. });
  22. }
  23.  
  24. //断开连接
  25. function disconnect() {
  26. if (stompClient !== null) {
  27. stompClient.disconnect();
  28. }
  29. setConnected(false);
  30. console.log("Disconnected");
  31. }
  32.  
  33. //发送聊天记录
  34. function sendContent() {
  35. stompClient.send("/app/group/chat", {}, JSON.stringify({'content': $("#content").val()}));
  36.  
  37. }
  38.  
  39. //显示聊天记录
  40. function showContent(body) {
  41. $("#record").append("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
  42. }
  43.  
  44. //显示实时在线用户
  45. function showOnlieUser(body) {
  46. $("#online").html("<tr><td>" + body.content + "</td> <td>"+new Date(body.time).toLocaleTimeString()+"</td></tr>");
  47. }
  48.  
  49. $(function () {
  50.  
  51. connect();//自动上线
  52.  
  53. $("form").on('submit', function (e) {
  54. e.preventDefault();
  55. });
  56.  
  57. $( "#disconnect" ).click(function() { disconnect(); });
  58. $( "#send" ).click(function() {
  59. sendContent();
  60. });
  61. });

 gitHub源码https://github.com/yudiandemingzi/spring-boot-websocket-study

我只是偶尔安静下来,对过去的种种思忖一番。那些曾经的旧时光里即便有过天真愚钝,也不值得谴责。毕竟,往后的日子,还很长。不断鼓励自己,

天一亮,又是崭新的起点,又是未知的征程(上校1)

WebSocket(5)---多人聊天系统的更多相关文章

  1. 基于SpringBoot+WebSocket搭建一个简单的多人聊天系统

    前言   今天闲来无事,就来了解一下WebSocket协议.来简单了解一下吧. WebSocket是什么   首先了解一下WebSocket是什么?WebSocket是一种在单个TCP连接上进行全双工 ...

  2. webSocket实现多人聊天功能

    webSocket实现多人在线聊天 主要思路如下: 1.使用vue构建简单的聊天室界面 2.基于nodeJs 的webSocket开启一个socket后台服务,前端使用H5的webSocket来创建一 ...

  3. nodejs+websocket实时聊天系统

    介绍下websocket: webSocket协议本质上是一个基于tcp的协议; 建立一个websocket连接,大体的过程: 1.客户端浏览器首先向服务器发起一个http请求,这个请求和平常的请求有 ...

  4. WebSocket实现实时聊天系统

    WebSocket实现实时聊天系统 等闲变却故人心,却道故人心易变. 简介:前几天看了WebSocket,今天体验下它的实时聊天. 一.项目介绍 WebSocket 实时聊天系统自己一个一码的搞出来还 ...

  5. html websocket

    from:http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/ websocket 规范升级过,在该链接的文章内未提及,后面 ...

  6. HTML5学习之WebSocket通讯(六)

    WebSocket是下一代客户端-服务器的异步通信方法. WebSocket最伟大之处在于服务器和客户端可以在任意时刻相互推送信息 WebSocket允许跨域通信 Ajax技术需要客户端发起请求,We ...

  7. WebSocket C# Demo

    WebSocket 规范 WebSocket 协议本质上是一个基于 TCP 的协议.为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTT ...

  8. WebSocket简单介绍(WebSocket 实战)(3)

    这一节里我们用一个案例来演示怎么使用 WebSocket 构建一个实时的 Web 应用.这是一个简单的实时多人聊天系统,包括客户端和服务端的实现.客户端通过浏览器向聊天服务器发起请求,服务器端解析客户 ...

  9. Websocket教程SpringBoot+Maven整合(详情)

    1.大话websocket及课程介绍 简介: websocket介绍.使用场景分享.学习课程需要什么基础 笔记: websocket介绍: WebSocket协议是基于TCP的一种新的网络协议.它实现 ...

随机推荐

  1. sql server 查询所有表结构

    SELECT CASE WHEN col.colorder = 1 THEN obj.name ELSE '' END AS 表名, Coalesce(epTwo.value, '') AS docu ...

  2. Hive基础测试操作

    一.Hive测试 1.查看数据库 show databases; 2.使用某个数据库,如默认数据库 user default; 3.创建表 create table if not exist itst ...

  3. Scrapy:Python实现scrapy框架爬虫两个网址下载网页内容信息——Jason niu

    import scrapy class DmozSpider(scrapy.Spider): name ="dmoz" allowed_domains = ["dmoz. ...

  4. 1.3 正则表达式和python语言-1.3.6匹配多个字符串

    1.3.6 匹配多个字符串(2018-05-08) 我们在正则表达式 bat|bet|bit 中使用了择一匹配(|)符号.如下为在 Python中使用正则表达式的方法. import re #bat| ...

  5. 使用Jsoup实现java爬虫(非原创)

    1,查看页面源代码,使用css或者JQuery选择器方式或元素节点选择 例如: 或者写成:Elements elements1 = Jsoup.connect("http://jb.999a ...

  6. vscode设置中文语言

    https://jingyan.baidu.com/article/7e44095377c9d12fc1e2ef5b.html

  7. 使用IDEA时跳转到.class的解决办法

    项目背景:jdk1.8 软件环境:IDEA 问题: 1. 两个不同的项目,在A项目中写了一个实体类.B项目中引用.在B项目中CTRL+鼠标左键点击进入,正常情况下是进入了源码文件,也就是.JAVA文件 ...

  8. java.lang.ClassCastException: net.sf.json.JSONNull cannot be cast to net.sf.json.JSONObject的解决方法

    报错情况已经说明了,在百度查了好几个解决方法,这里总结一下: 首先:加一个判断是否为空,再做操作 // 得到json串 String jsonString = UtilPOSTGET.UPost(FO ...

  9. 关于在centos7 64为引用android so引发的问题修复

    背景: 公司有解码的app,解码库位c++编写so动态库. 之前做过一版在调用html5摄像头,然后提取图像进行解码,后面因为图像质量不佳放弃. 最近 因为小程序api有更新 可以获取到相对清晰的图像 ...

  10. prometheus — 基于文件的服务发现

    基于文件的服务发现方式不需要依赖其他平台与第三方服务,用户只需将要新的target信息以yaml或json文件格式添加到target文件中 ,prometheus会定期从指定文件中读取target信息 ...