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

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

WebSocket简单介绍

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

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解_第1张图片
image

我们知道,传统的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以上的版本才能运行。

项目结构图:

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解_第2张图片
image

相关代码:

pom.xml:




  4.0.0

  com
  websocket-singlechat
  1.0-SNAPSHOT
  war

  websocket-singlechat Maven Webapp
  
  http://www.example.com

  
    UTF-8
    1.7
    1.7
  

  
    
      junit
      junit
      4.11
      test
    
    
      javax
      javaee-api
      7.0
    
    
      com.google.code.gson
      gson
      2.7
    
  

  
    websocket-singlechat
    
      
        
          maven-clean-plugin
          3.0.0
        
        
        
          maven-resources-plugin
          3.0.2
        
        
          maven-compiler-plugin
          3.7.0
        
        
          maven-surefire-plugin
          2.20.1
        
        
          maven-war-plugin
          3.2.0
        
        
          maven-install-plugin
          2.5.2
        
        
          maven-deploy-plugin
          2.8.2
        
      
    
  


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  sockets=new HashSet();
    //定义一个全局变量Session,用于存放登录用户的用户名
    private  Session  session;
    //定义一个全局变量map,key为用户名,该用户对应的session为value
    private  static  Map  map=new HashMap();
    //定义一个数组,用于存放所有的登录用户,显示在聊天页面的用户列表栏中
    private  static  Listnames=new ArrayList();
    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("正在私聊你:"+vo.getMsg());
            String to  = vo.getTo();
            //根据单聊对象的名称拿到要单聊对象的Session
            Session to_session = this.map.get(to);
            //如果是单聊,就将消息发送给对方
            to_session.getBasicRemote().sendText(gson.toJson(message));
        }
    }

    /*
     * 广播消息
     */
    public void broadcast(Setsockets ,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> getAnnotatedEndpointClasses(Set> scan) {
        System.out.println("扫描到"+scan.size()+"个服务端程序");
        return scan;
    }

    //获取所有以接口方式配置的webSocket类。
    @Override
    public Set getEndpointConfigs(
            Set> 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  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 getNames() {
        return names;
    }

    public void setNames(List names) {
        this.names = names;
    }

    public Message() {
        super();
    }
}

web.xml:



  Archetype Created Web Application

  
    
    LoginServlet
    LoginServlet
    com.home.servlet.LoginServlet
  

  
    LoginServlet
    /LoginServlet
  

  
    index.jsp
  


chat.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>



    
    Insert title here
    
    



欢迎 ${sessionScope.username }使用本聊天系统!!

login.jsp:

<%@ page language="java" contentType="text/html; charset=utf-8"
         pageEncoding="utf-8"%>



    
    Insert title here
    


用户名:

项目演示:

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解_第3张图片
image

你可能感兴趣的:(spring websocket 和socketjs实现单聊群聊,广播的消息推送详解)