您现在的位置是:首页 > 文章详情

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

日期:2018-08-16点击:357

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

WebSocket简单介绍

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

img_6caf0e57bce2d57dd4a47e84047ad0cd.png
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以上的版本才能运行。

项目结构图:

img_29149338186c7496a3c2dcd630a2a8c0.gif
image

相关代码:

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> 

项目演示:

img_74e4414e5e215166971c437270bd4501.gif
image
原文链接:https://yq.aliyun.com/articles/652835
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章