springwebsocket和socketjs实现单聊群聊,广播的消息推送详解(代码片段)

charlypage charlypage     2022-12-22     194

关键词:

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解

WebSocket简单介绍

  随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。

技术分享图片

  我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)Comet技术。其实后者本质上也是一种轮询,只不过有所改进。

  轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。

  Comet技术又可以分为长轮询流技术长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。

  这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。

  伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。

  JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。

项目结构图:

技术分享图片

相关代码:

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com</groupId>
  <artifactId>websocket-singlechat</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <name>websocket-singlechat Maven Webapp</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>javax</groupId>
      <artifactId>javaee-api</artifactId>
      <version>7.0</version>
    </dependency>
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.7</version>
    </dependency>
  </dependencies>

  <build>
    <finalName>websocket-singlechat</finalName>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
        <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.7.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.20.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-war-plugin</artifactId>
          <version>3.2.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

ChatSocket:

package com.home.chat;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import com.google.gson.Gson;
import com.home.vo.ContentVo;
import com.home.vo.Message;

/**
 * 总通信管道
 *
 */
@ServerEndpoint("/chatSocket")
public class ChatSocket 

    //定义一个全局变量集合sockets,用户存放每个登录用户的通信管道
    private  static  Set<ChatSocket>  sockets=new HashSet<ChatSocket>();
    //定义一个全局变量Session,用于存放登录用户的用户名
    private  Session  session;
    //定义一个全局变量map,key为用户名,该用户对应的session为value
    private  static  Map<String, Session>  map=new HashMap<String, Session>();
    //定义一个数组,用于存放所有的登录用户,显示在聊天页面的用户列表栏中
    private  static  List<String>names=new ArrayList<String>();
    private String username;
    private Gson  gson=new Gson();

    /*
     * 监听用户登录
     */
    @OnOpen
    public void open(Session session)
        System.out.println("建立了一个socket通道" + session.getId());
        this.session = session;
        //将当前连接上的用户session信息全部存到scokets中
        sockets.add(this);
        //拿到URL路径后面所有的参数信息
        String queryString = session.getQueryString();
        System.out.println();
        //截取=后面的参数信息(用户名),将参数信息赋值给全局的用户名
        this.username = queryString.substring(queryString.indexOf("=")+1);
        //每登录一个用户,就将该用户名存入到names数组中,用于刷新好友列表
        names.add(this.username);
        //将当前登录用户以及对应的session存入到map中
        this.map.put(this.username, this.session);
        System.out.println("用户"+this.username+"进入聊天室");
        Message message = new Message();
        message.setAlert("用户"+this.username+"进入聊天室");
        //将当前所有登录用户存入到message中,用于广播发送到聊天页面
        message.setNames(names);
        //将聊天信息广播给所有通信管道(sockets)
        broadcast(sockets, gson.toJson(message) );
    

    /*
     * 退出登录
     */
    @OnClose
    public void close(Session session)
        //移除退出登录用户的通信管道
        sockets.remove(this);
        //将用户名从names中剔除,用于刷新好友列表
        names.remove(this.username);
        Message message = new Message();
        System.out.println("用户"+this.username+"退出聊天室");
        message.setAlert(this.username+"退出当前聊天室!!!");
        //刷新好友列表
        message.setNames(names);
        broadcast(sockets, gson.toJson(message));
    

    /*
     * 接收客户端发送过来的消息,然后判断是广播还是单聊
     */
    @OnMessage
    public void receive(Session  session,String msg) throws IOException
        //将客户端消息转成json对象
        ContentVo vo = gson.fromJson(msg, ContentVo.class);
        //如果是群聊,就像消息广播给所有人
        if(vo.getType()==1)
            Message message = new Message();
            message.setDate(new Date().toLocaleString());
            message.setFrom(this.username);
            message.setSendMsg(vo.getMsg());
            broadcast(sockets, gson.toJson(message));
        else
            Message message = new Message();
            message.setDate(new Date().toLocaleString());
            message.setFrom(this.username);
            message.setAlert(vo.getMsg());
            message.setSendMsg("<font color=red>正在私聊你:</font>"+vo.getMsg());
            String to  = vo.getTo();
            //根据单聊对象的名称拿到要单聊对象的Session
            Session to_session = this.map.get(to);
            //如果是单聊,就将消息发送给对方
            to_session.getBasicRemote().sendText(gson.toJson(message));
        
    

    /*
     * 广播消息
     */
    public void broadcast(Set<ChatSocket>sockets ,String msg)
        //遍历当前所有的连接管道,将通知信息发送给每一个管道
        for(ChatSocket socket : sockets)
            try 
                //通过session发送信息
                socket.session.getBasicRemote().sendText(msg);
             catch (IOException e) 
                e.printStackTrace();
            
        
    

ServerConfig:

package com.home.config;

import java.util.Set;

import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
/**
 * 项目启动时会自动启动,类似与ContextListener.
 * 是webSocket的核心配置类。
 *
 */
public class ServerConfig implements ServerApplicationConfig 

    //扫描src下所有类@ServerEndPoint注解的类。
    @Override
    public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scan) 
        System.out.println("扫描到"+scan.size()+"个服务端程序");
        return scan;
    

    //获取所有以接口方式配置的webSocket类。
    @Override
    public Set<ServerEndpointConfig> getEndpointConfigs(
            Set<Class<? extends Endpoint>> point) 
        System.out.println("实现EndPoint接口的类数量:"+point.size());
        return null;
    

LoginServlet:

package com.home.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoginServlet extends HttpServlet 

    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)throws IOException,ServletException 

    


    public void doPost(HttpServletRequest request,
                       HttpServletResponse response)throws IOException,ServletException

        String username = request.getParameter("username");
        System.out.println("doPost当前登录用户为"+username);
        request.getSession().setAttribute("username",username);
        //这里只是简单地模拟登录,登陆之后直接跳转到聊天页面
        response.sendRedirect("chat.jsp");
    

ContentVo:

package com.home.vo;

/**
 * 客户端发送给服务端消息实体
 *
 */
public class ContentVo 

    private String to;
    private String msg;
    private Integer type;
    public String getTo() 
        return to;
    
    public void setTo(String to) 
        this.to = to;
    
    public String getMsg() 
        return msg;
    
    public void setMsg(String msg) 
        this.msg = msg;
    
    public Integer getType() 
        return type;
    
    public void setType(Integer type) 
        this.type = type;
    

Message:

package com.home.vo;

import java.util.Date;
import java.util.List;

/**
 * 服务端发送给客户端消息实体
 *
 */
public class Message 

    private  String  alert;   //

    private  List<String>  names;

    private  String  sendMsg;

    private String  from;

    private String  date;


    public String getDate() 
        return date;
    

    public void setDate(String date) 
        this.date = date;
    

    public String getSendMsg() 
        return sendMsg;
    

    public void setSendMsg(String sendMsg) 
        this.sendMsg = sendMsg;
    

    public String getFrom() 
        return from;
    

    public void setFrom(String from) 
        this.from = from;
    

    public String getAlert() 
        return alert;
    

    public void setAlert(String alert) 
        this.alert = alert;
    

    public List<String> getNames() 
        return names;
    

    public void setNames(List<String> names) 
        this.names = names;
    

    public Message() 
        super();
    

web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name>Archetype Created Web Application</display-name>

  <servlet>
    <description></description>
    <display-name>LoginServlet</display-name>
    <servlet-name>LoginServlet</servlet-name>
    <servlet-class>com.home.servlet.LoginServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>LoginServlet</servlet-name>
    <url-pattern>/LoginServlet</url-pattern>
  </servlet-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

chat.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Insert title here</title>
    <script type="text/javascript" src="./jquery-3.3.1/jquery-3.3.1.js"></script>
    <script type="text/javascript">
        var ws;
        var userName='$sessionScope.username';
        //通过URL请求服务端(chat为项目名称)
        var url = "ws://localhost:8080/chatSocket?username="+userName;
      
        //进入聊天页面就是一个通信管道
        window.onload = function() 
            console.log(url);

            if ('WebSocket' in window) 
                ws = new WebSocket(url);
             else if ('MozWebSocket' in window) 
                ws = new MozWebSocket(url);
             else 
                alert('WebSocket is not supported by this browser.');
                return;
            
            ws.onopen=function()
                // showMsg("webSocket通道建立成功!!!");
                console.log("webSocket通道建立成功!!!");
            ;

            //监听服务器发送过来的所有信息
            ws.onmessage = function(event) 
                eval("var result=" + event.data);

                //如果后台发过来的alert不为空就显示出来
                if (result.alert != undefined) 
                    $("#content").append(result.alert + "<br/>");
                

                //如果用户列表不为空就显示
                if (result.names != undefined) 
                    //刷新用户列表之前清空一下列表,免得会重复,因为后台只是单纯的添加
                    $("#userList").html("");
                    $(result.names).each(
                        function() 
                            $("#userList").append(
                                "<input  type=checkbox  value='"+this+"'/>"
                                + this + "<br/>");
                        );
                

                //将用户名字和当前时间以及发送的信息显示在页面上
                if (result.from != undefined) 
                    $("#content").append(
                        result.from + " " + result.date + " 说:<br/>"
                        + result.sendMsg + "<br/>");
                

            ;
        ;

        //将消息发送给后台服务器
        function send() 
            //拿到需要单聊的用户名
            //alert("当前登录用户为"+userName);
            var ss = $("#userList :checked");
            console.log("ss==>"+ss);
            console.log(" ss.length()=="+ss.length);
            //alert("群聊还是私聊"+ss.size());
            var to = $('#userList :checked').val();
            if (to == userName) 
                alert("你不能给自己发送消息啊");
                return;
            
            //根据勾选的人数确定是群聊还是单聊
            var value = $("#msg").val();
            //alert("消息内容为"+value);
            var object = null;

            if (ss.length == 0) 
                object = 
                    msg : value,
                    type : 1, //1 广播 2单聊
                ;
             else 
                object = 
                    to : to,
                    msg : value,
                    type : 2, //1 广播 2单聊
                ;
            
            //将object转成json字符串发送给服务端
            var json = JSON.stringify(object);
            //alert("str="+json);
            ws.send(json);
            //消息发送后将消息栏清空
            $("#msg").val("");
        
    </script>
</head>
<body>

<h3>欢迎 $sessionScope.username 使用本聊天系统!!</h3>

<div id="content"
     style="border: 1px solid black; width: 400px; height: 300px; float: left; color: #7f3f00;"></div>
<div id="userList"
     style="border: 1px solid black; width: 120px; height: 300px; float: left; color: #00ff00;"></div>

<div style="clear: both;" style="color:#00ff00">
    <input id="msg" />
    <button onclick="send();">发送消息</button>
</div>
</body>
</html>

login.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Insert title here</title>
    <script type="text/javascript"  src="jquery-1.4.4.min.js"></script>
</head>
<body>
<form  name="ff"  action="LoginServlet" method="post" >
    用户名:<input name="username"  /><br/>
    <input type="submit"  value="登录"/>
</form>
</body>
</html>

项目演示:

技术分享图片

高级 Restclient 套接字实现无法连接到 Spring Websocket

】高级Restclient套接字实现无法连接到SpringWebsocket【英文标题】:AdvancedRestclientsocketimplementationcannotconnecttoSpringWebsocket【发布时间】:2015-08-2711:01:14【问题描述】:我使用SpringStomp和socketjs开发了一个聊天应用程序。我已通过客户... 查看详情

WebSocket 连接失败。 WebSocket握手期间出错-socketjs

】WebSocket连接失败。WebSocket握手期间出错-socketjs【英文标题】:WebSocketconnectionfailed.ErrorduringWebSockethandshake-socketjs【发布时间】:2017-05-0917:05:08【问题描述】:详情:我一直在尝试将我的react项目配置为与hotloader一起使用,以便我... 查看详情

springwebsocket:握手因升级头无效而失败:null

我正在使用带有后端弹簧的wss(安全网络套接字)和用于javascript客户端的STOMP。有谁知道为什么得到:HandshakefailedduetoinvalidUpgradeheader:null答案我遇到了与tomcat的nginxhttps代理相同的问题。这是因为我不支持wss请求。为了支持wss请... 查看详情

springwebsocket(代码片段)

...所有的浏览器都支持,目前可支持的版本如下所示。2SpringWebSocket在Spring中开发WebSocket框架主要基于spring-websocket和spring-messaging库。在WebSocket中,主要包含如下信息。Message消息,包含消息头和负载。消息头中包含,... 查看详情

springwebsocket使用@sendtouser

springwebsocket使用@SendToUser原文链接:https://blog.csdn.net/yingxiake/article/details/51224569之前我们利用@SendTo在方法上进行注解,方法的返回值会被messageconverter转化并推送到消息代理器中,由消息代理器广播到订阅路径去@MessageMapping("bar")//... 查看详情

Spring Websockets:如何验证消息和接收客户端?

】SpringWebsockets:如何验证消息和接收客户端?【英文标题】:SpringWebsockets:Howtovalidatemessageandreceiveclients?【发布时间】:2019-07-3015:37:07【问题描述】:我已配置并运行SpringWebSockets。客户订阅特定主题(例如/topic/contract/contract_id)... 查看详情

springwebsocket在线聊天室

Springwebsocket在线聊天室 由David发表在天码营 每天大家都在使用QQ等即时聊天工具,今天我们就使用Spring框架以及websocket技术在网页端简单的实现一个在线聊天的功能。添加maven依赖在线聊天室使需要使用到的技术或者框架包... 查看详情

Spring websockets:消息未发布

】Springwebsockets:消息未发布【英文标题】:Springwebsockets:messagenotpublished【发布时间】:2016-05-2011:36:20【问题描述】:我将SpringWebSockets与STOMP和SockJS一起用于前端。它工作正常,但我还有另一个困难。这是后端代码:@MessageMapping("... 查看详情

未找到404的springwebsocket

我正在尝试用spring创建一个websocket端点。但每当我尝试从客户端连接时,我得到404.我也有一个Java实现@ServerEndpoint(value="/websocket")这很好用。下面是我的spring实现的代码,它不起作用。我在这做错了什么?packagecom.tlab.endpoint;importo... 查看详情

javawebsocket的实现以及springwebsocket

开始学习WebSocket,准备用它来实现一个在页面实时输出log4j的日志以及控制台的日志。首先知道一些基础信息:java7开始支持WebSocket,并且只是做了定义,并未实现tomcat7及以上,jetty9.1及以上实现了WebSocket,其他容器没有研究sprin... 查看详情

Spring websocket 和消息传递支持有多成熟?

】Springwebsocket和消息传递支持有多成熟?【英文标题】:HowmatureistheSpringwebsocketandmessagingsupport?【发布时间】:2014-07-1513:24:44【问题描述】:SpringWebsocket/Messagingsupport是否成熟了,或者事情还在不断发展,等待一些明确的真实案例... 查看详情

Spring websocket 和 Stomp.js - 我应该在订阅和发送之间等待多长时间?

】Springwebsocket和Stomp.js-我应该在订阅和发送之间等待多长时间?【英文标题】:SpringwebsocketandStomp.js-howlongshouldiwaitbetweensubscribeandsend?【发布时间】:2014-08-3010:38:15【问题描述】:我有以下代码(来自springwebsocket演示应用程序):... 查看详情

os实模式和保护模式区别及寻址方式

实模式和保护模式区别及寻址方式转载请注明出处:http://blog.csdn.NET/rosetta64KB-4GB-64TB?   我记得大学的汇编课程、组成原理课里老师讲过实模式和保护模式的区别,在很多书本上也有谈及,无奈本人理解和感悟能力... 查看详情

实模式和保护模式区别及寻址方式

...sp;我记得大学的汇编课程、组成原理课里老师讲过实模式和保护模式的区别,在很多书本上也有谈及,无奈本人理解和感悟能力实在太差,在很长一段时间里都没真正的明白它们的内含,更别说为什么实模式下最大寻址空间为1MB... 查看详情

实模式和保护模式区别及寻址方式

...sp;我记得大学的汇编课程、组成原理课里老师讲过实模式和保护模式的区别,在很多书本上也有谈及,无奈本人理解和感悟能力实在太差,在很长一段时间里都没真正的明白它们的内含,更别说为什么实模式下最大寻址空间为1MB... 查看详情

Spring websockets不接收数据

】Springwebsockets不接收数据【英文标题】:Springwebsocketsnotreceivingdata【发布时间】:2018-01-2513:25:19【问题描述】:我正在尝试使用springwebsockets,但由于某种我不明白的原因,我可以与服务器建立连接,但是当我发送数据时没有任... 查看详情

实参和形参(代码片段)

实参'''位置实参:不用明确形参名的传参方式,一定按照位置给形参传值关键字实参:必须明确形参名字与值为形参传值,可以不用按照位置*****注:混用1.关键字实参必须出现在位置实参后2.多个位置实参还是按照位置... 查看详情

使用websocket构建实时聊天

...决方案。Spring自4.0版本后增加了WebSocket支持,本例就使用SpringWebSocket构建一个简单实时聊天的应用。SpringWebSocket提供了一个WebSocketHandler接口,这个接口提供了WebSocket连接建立后生命周期的处理方法。WebSocketSession不同于HttpSession... 查看详情