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>

项目演示:

spring websocket 和socketjs实现单聊群聊,广播的消息推送详解的更多相关文章

  1. 利用Kafka的Assign模式实现超大群组(10万+)消息推送

    引言 IM即时通信场景下,最重要的一个能力就是推送:在线的直接通过长连接网关服务转发,离线的通过APNS或者极光等系统进行推送.   本文主要是针对在线用户推送场景来进行总结和探讨:如何利用Kafka ...

  2. Flask(4)- flask请求上下文源码解读、http聊天室单聊/群聊(基于gevent-websocket)

    一.flask请求上下文源码解读 通过上篇源码分析,我们知道了有请求发来的时候就执行了app(Flask的实例化对象)的__call__方法,而__call__方法返回了app的wsgi_app(en ...

  3. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  4. websocket(二)--简单实现网页版群聊

    websocket可以实现服务端的消息推送,而不必在客户端轮询,大大的节省的资源,对于实时通讯来说简直是个大喜讯. 在上一篇文章中介绍了协议握手,这篇文章将通过实现简单的群聊来帮助进一步了解webso ...

  5. spring boot下WebSocket消息推送(转)

    原文地址:https://www.cnblogs.com/betterboyz/p/8669879.html WebSocket协议 WebSocket是一种在单个TCP连接上进行全双工通讯的协议.W ...

  6. spring boot下WebSocket消息推送

    WebSocket协议 WebSocket是一种在单个TCP连接上进行全双工通讯的协议.WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范.WebSo ...

  7. 在Spring Boot框架下使用WebSocket实现消息推送

    Spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目(使用Spring Boot开发Web项目)以及怎样为我们的Project添加HTTPS的 ...

  8. 使用spring boot +WebSocket实现(后台主动)消息推送

    言:使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!使用此webscoket务必确保生产环境能兼容/支持!主要是tomcat的兼容与支持. ...

  9. spring+rabbitmq+stomp搭建websocket消息推送(非spring boot方式)

    前言: 两年前做过spring+activemq+stomp的ws推送,那个做起来很简单,但现在公司用的mq中间件是rabbitmq,因此需要通过rabbitmq去做ws通信.仔细搜了搜百度/谷歌,网 ...

随机推荐

  1. HDU 1864 最大报销额 (DP-01背包问题)

    题意:中文题,你懂得. 析:拿过题目一看,本来以为是贪心,仔细一看不是贪心,其实是一个简单的01背包问题(DP),不过这个题的坑是在处理发票上,刚开始WA了一次. 分析一下什么样的发票是不符合要求的: ...

  2. python 编码方式大全 fr = open(filename_r,encoding='cp852')

    7.8.3. Standard Encodings Python comes with a number of codecs built-in, either implemented as C fun ...

  3. Yarn application has already exited with state FINISHED

    如果在运行spark-sql时遇到如下这样的错误,可能是因为yarn-site.xml中的配置项yarn.nodemanager.vmem-pmem-ratio值偏小,它的默认值为2.1,可以尝试改大 ...

  4. 常见的it软件默认端口

    tomcat:8080 nginx:80 mysql:3306 oracle:1521 nexus:8081 浏览器:80 redis:6379 solr:tomcat部署默认8080 jetty部署 ...

  5. 小强 ROS 机器人教程

    首先请您自行依据线标提示将小强接线连接好,完整结构如下两图所示: 小强是属于Turtlebot机器人.它由底盘.主机.Kinect相机(通过USB连接主机)组成,没有显示屏.如果要通过显示器查看主机的 ...

  6. Google Map API申请

    https://code.google.com/apis/console 当然需要先有个Google账户登录. 然后需要建一个项目. 然后根据package+sha1码获取密钥key 然后就可以创建凭 ...

  7. Java内存模型(二)

    volatile型变量的特殊规则 volatile是Java虚拟机提供的最轻量级的同步机制,当一个变量被定义成volatile后,它将具备两种特性,第一是保证此变量对所有线程的可见性,这里的“可见性” ...

  8. 提高Android和iOS调试编译速度

    http://www.cnblogs.com/findumars/p/7841252.html 提高Android和iOS调试编译速度   如果您使用Delphi开发App,就会遇到:Android和 ...

  9. 测试一下你的T-SQL基础知识-subquery

    一直以为自己SQL挺好的,没有想到今天在重构存储过程遇到了一个子查询的问题,修改为自连接之后发现居然结果不对,于是有了下面的测试.假设表中有如下数数据,请问Query1,Query2,Query3的查 ...

  10. 拒绝“高冷”词汇!初学C#中的委托

    有一天,你写了好多好多带“形参”的构造函数(就是“方法”,同义),而且需要向这些构造函数里传递同样的“实参”,然后你就憨憨地一个一个函数的调用并赋予同样的“实参”,这一天就这么过去了... 又过了几天 ...