javaWEB项目中webSocket的简单应用
来源:互联网 发布:淘宝店买什么好 编辑:程序博客网 时间:2024/06/01 10:43
webSocket握手协议
客户端到服务端:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Upgrade: WebSocket
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5
Origin: http://example.com
[8-byte security key]
服务端到客户端:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
1.添加maven依赖
tomcat版本要7.0.65及以上,不然不支持webSocket<!-- spring版本号 --><spring.version>4.3.9.RELEASE</spring.version> <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> <classifier>jdk15</classifier> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.5</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.5</version> </dependency><!-- 添加对webSocket的支持 --><dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope></dependency><!-- 如果需要在MVC模式中被其他类调用,需要配置Configurator属性:用于调用一些自定义的配置算法,如拦截连接握手或可用于被每个端点实例调用的任意的方法和算法;对于服务加载程序,该接口必须提供默认配置器加载平台。 --><dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version></dependency>
2.Spring-mvc.xml文件中添加
<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> </list> </property> </bean>
3.前端代码 包含心跳重连
添加webSocket的js引用<script src="${pageContext.request.contextPath}/js/sockjs-0.3.min.js"></script> /*Websocket*/ var websocket; var userId = $('#userId').val(); var storeId = $('#storeId').val(); var host = window.location.host; if(storeId!=0){ if('WebSocket' in window) { console.log("此浏览器支持websocket"); reconnect(userId,storeId); } else if('MozWebSocket' in window) { alert("此浏览器只支持MozWebSocket"); } else { alert("此浏览器只支持SockJS"); } //websocket连接打开 websocket.onopen = function(evnt) { heartCheck.start(); $("#socketMsg").html("链接服务器成功!") }; //接收到服务器消息时调用方法 websocket.onmessage = function(evnt) { heartCheck.reset(); var resultMsg = JSON.parse(evnt.data) //调用播放语音的方法 var ele= document.getElementById("eleme"); var meituan = document.getElementById("meituan"); if(resultMsg.message.indexOf("饿了么")!=-1){ ele.play(); }else if(evnt.data.indexOf("美团")!=-1){ meituan.play(); } //调用打印方法 var orderId = resultMsg.orderId; if(orderId!='' && orderId!=null && orderId != undefined){ print(orderId); } }; websocket.onerror = function(evnt) { reconnect(userId,storeId); $('#socketMsg').html("与服务器连接异常!"); }; websocket.onclose = function(evnt) { reconnect(userId,storeId); $("#socketMsg").html("与服务器断开了链接!") } } //心跳重连 var heartCheck = { timeout: 30000,//60ms timeoutObj: null, serverTimeoutObj: null, reset: function(){ clearTimeout(this.timeoutObj); clearTimeout(this.serverTimeoutObj); this.start(); }, start: function(){ var self = this; this.timeoutObj = setTimeout(function(){ websocket.send("HeartBeat"); self.serverTimeoutObj = setTimeout(function(){ //如果onclose会执行reconnect,我们执行ws.close()就行了.如果直接执行reconnect 会触发onclose导致重连两次 websocket.close(); }, self.timeout) }, this.timeout) }, } //重连方法 function reconnect(userId,storeId){ websocket = new WebSocket("ws://"+host+"/koms/websocket/remindOrder/"+userId+"/"+storeId); }
4.服务端代码
其中包含一些业务代码,需要做到订单自动打印,打印的控制是放在HttpSession里面,当时做的时候碰到一个问题,当一个用户在同一个浏览器里面打开多个窗口的时候,消息会重复发多次. 就会重复打印。
登录一个页面之后,当前页面不关闭,直接复制本页面,这样我的服务端的Set集合里面对同一个用户保存了多个webSocket的session,这样我在服务端给客户端发信息的时候,同一个用户在同一个浏览器就发了多次。
后来的解决方案是,将webSocket的session保存到HttpSession中,每一个HttpSession发送一条信息,然后跳出,这样就不会存在同一个HTTPSession发多个消息了。同一个用户登录登录不同的浏览器,这样httpSession就不一样,我们业务里面允许就没有做处理
在Websocket中获取httpsession
import javax.servlet.http.HttpSession;import javax.websocket.HandshakeResponse;import javax.websocket.server.HandshakeRequest;import javax.websocket.server.ServerEndpointConfig;import javax.websocket.server.ServerEndpointConfig.Configurator;/* * 获取HttpSession * */public class GetHttpSessionConfigurator extends Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { // TODO Auto-generated method stub HttpSession httpSession=(HttpSession) request.getHttpSession(); sec.getUserProperties().put(HttpSession.class.getName(),httpSession); }}
import java.io.IOException;import java.util.HashMap;import java.util.HashSet;import java.util.Map;import java.util.Set;import javax.servlet.http.HttpSession;import javax.websocket.EndpointConfig;import javax.websocket.OnClose;import javax.websocket.OnError;import javax.websocket.OnMessage;import javax.websocket.OnOpen;import javax.websocket.Session;import javax.websocket.server.PathParam;import javax.websocket.server.ServerEndpoint;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import com.cn.framework.constant.SessionListener;@Component@ServerEndpoint(value="/websocket/remindOrder/{userId}/{storeId}",configurator=GetHttpSessionConfigurator.class)public class OrderRemind { //日志记录 private Logger logger = LoggerFactory.getLogger(OrderRemind.class); //记录每个用户下多个终端的连接 private static Map<Long, Set<OrderRemind>> userSocket = new HashMap<>(); //需要session来对用户发送数据, 获取连接特征userId private Session session; private Long userId; private int storeId; //httpsession public static HttpSession httpSession = null; /** * @Title: onOpen * @Description: websocekt连接建立时的操作 * @param @param userId 用户id * @param @param session websocket连接的session属性 * @param @throws IOException */ @OnOpen public void onOpen(@PathParam("userId") Long userId,@PathParam("storeId") int storeId,Session session,EndpointConfig config) throws IOException{ this.session = session; this.userId = userId; this.storeId = storeId; //将webSocket的对象放到HttpSession集合中 httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); Set<OrderRemind> webSocketSessions = (Set) httpSession.getAttribute("webSocketObject"); if(webSocketSessions!=null){ webSocketSessions.add(this); }else{ webSocketSessions = new HashSet<>(); webSocketSessions.add(this); } httpSession.setAttribute("webSocketObject", webSocketSessions); //根据该用户当前是否已经在别的终端登录进行添加操作 if (userSocket.containsKey(this.userId)) { Set<OrderRemind> set = userSocket.get(this.userId); System.out.println(set.size()); logger.error("当前用户id:{},门店:{},weSocketSession:{},已有其他终端登录",this.userId,this.storeId,this.session.hashCode()); userSocket.get(this.userId).add(this); //增加该用户set中的连接实例 }else { logger.error("当前用户id:{},门店:{},weSocketSession:{},第一个终端登录",this.userId,this.storeId,this.session.hashCode()); Set<OrderRemind> addUserSet = new HashSet<>(); addUserSet.add(this); userSocket.put(this.userId, addUserSet); } logger.error("用户{}登录的终端个数是为{}",userId,userSocket.get(this.userId).size()); logger.error("当前在线用户数为:{},所有终端个数为:{}",userSocket.size(),SessionListener.sessions.size()); } /** * @Title: onClose * @Description: 连接关闭的操作 */ @OnClose public void onClose(){ //连接关闭的时候 httpsession中移除webSocket对象 Set<OrderRemind> webSocketSessions = (Set) httpSession.getAttribute("webSocketObject"); webSocketSessions.remove(this); httpSession.setAttribute("webSocketObject",webSocketSessions); //移除当前用户终端登录的websocket信息,如果该用户的所有终端都下线了,则删除该用户的记录 if (userSocket.get(this.userId).size() == 0) { userSocket.remove(this.userId); }else{ userSocket.get(this.userId).remove(this); } logger.error("用户{}登录的终端个数是为{}",this.userId,userSocket.get(this.userId).size()); logger.error("当前在线用户数为:{},所有终端个数为:{}",userSocket.size(),SessionListener.sessions.size()); } /** * @Title: onMessage * @Description: 收到消息后的操作 * @param @param message 收到的消息 * @param @param session 该连接的session属性 */ @OnMessage public void onMessage(String message, Session session) { logger.error("收到来自用户id为:{}的消息:{}",this.userId,message); if(session ==null) logger.debug("session null"); //心跳重连机制 try { session.getBasicRemote().sendText("{\"message\":\"连接正常!\"}"); } catch (IOException e) { e.printStackTrace(); } } /** * @Title: onError * @Description: 连接发生错误时候的操作 * @param @param session 该连接的session * @param @param error 发生的错误 */ @OnError public void onError(Session session, Throwable error){ logger.error("用户id为:{}的连接发送错误",this.userId); error.printStackTrace(); } /** * @Title: sendMessageToUser * @Description: 发送消息给用户下的所有终端 * @param @param userId 用户id * @param @param message 发送的消息 * @param @return 发送成功返回true,反则返回false */ public Boolean sendMessageToUser(Map<String,Object> remindMessage){ //订单门店 int storeId = (Integer)remindMessage.get("store_id"); //订单渠道 int channelId = (Integer)remindMessage.get("channel_id"); //品牌 int brandId = (Integer)remindMessage.get("brand_id"); //订单号 Integer orderId = (Integer)remindMessage.get("order_id"); logger.error("sendMessageToUser:{ storeId:"+storeId+" ---- channelId"+channelId+" ---- brandId"+brandId+"}"); String message = ""; if(channelId==1){ message = "{\"message\":\"您有新的饿了么订单,请及时处理\",\"orderId\":\""+orderId+"\"}"; }else if(channelId ==2){ message = "{\"message\":\"您有新的美团外卖订单,请及时处理\",\"orderId\":\""+orderId+"\"}"; } final String sendMessage = message; //获取到所有的session 遍历所有的session ,取得所有session里面的webSocket对象 匹配userid和storeId Map<String, HttpSession> sessions = SessionListener.sessions; //遍历所有的session Set<String> keySets = sessions.keySet(); for (String key : keySets) { HttpSession session =(HttpSession)sessions.get(key); Set<OrderRemind> orderReminds = (Set)session.getAttribute("webSocketObject"); try { if(orderReminds==null || orderReminds.size()==0){ continue; } for (OrderRemind orderRemind : orderReminds) { if(storeId==orderRemind.storeId){ logger.error("发送消息到客户端:{ userId:"+userId+" ---- orderId"+orderId+"---orderRemind.storeId:"+orderRemind.storeId+"}"); orderRemind.session.getBasicRemote().sendText(sendMessage); } break; } } catch (Exception e) { e.printStackTrace(); } }/* Set<Long> keys = userSocket.keySet(); for (Long userId : keys) { Set<OrderRemind> orderRemindes = userSocket.get(userId); for (OrderRemind orderRemind : orderRemindes) { //属于该门店的用户均发送推送消息 if(orderRemind.storeId==storeId){ try { logger.error("发送消息到客户端:{ userId:"+userId+" ---- orderId"+orderId+"---orderRemind.storeId:"+orderRemind.storeId+"}"); orderRemind.session.getBasicRemote().sendText(message); } catch (IOException e) { logger.error(" 给用户id为:{}发送消息失败",orderRemind.userId); e.printStackTrace(); } } } }*/ return false; }}
结尾:仅供自己学习,记录问题和参考,若有带来误解和不便请见谅,共勉!
- javaWEB项目中webSocket的简单应用
- springboot中websocket的简单应用
- websocket的简单应用
- JavaWeb项目中枚举的应用
- JavaWeb项目中定时器的简单运用
- javaweb webSocket 实现简单的点对点聊天功能
- javaweb webSocket 实现简单的点对点聊天功能
- HTML5 WebSocket(Client) + JavaWeb(Server) 实现简单的聊天室功能
- HTML5 WebSocket(Client) + JavaWeb(Server)实现简单的聊天室功能
- HTML5 WebSocket(Client) + JavaWeb(Server) 实现简单的聊天室功能
- 一个简单的javaweb项目
- 使用WebSocket进行通信的简单应用
- WebSocket简单应用
- SpringBoot中WebSocket的应用:聊天室
- SpringBoot中WebSocket的应用:即时通讯
- 最简单的JavaWeb应用结构
- Spring4.0 + websocket简单应用
- javaweb中Filter(过滤器)的常见应用
- 我的自我评价·担忧·困惑·反思(不断更新)
- Android 性能优化学习笔记.
- 17.9.1日报
- 利用CoreAnimation实现一个时间的进度条
- srs-bench 针对特定业务性能测试并发推流 解决方案
- javaWEB项目中webSocket的简单应用
- 5. Longest Palindromic Substring
- JAVA入门第一式
- 版本控制之SVN(一)
- 前端面试系列之---异步 单线程
- 腾讯社交广告“高校算法大赛” Amelie 30th
- 位运算应用技巧(给各位对于位运算不知所云的新手们)
- Centos 配置本地yum源
- 前端面试系列之---DOM操作 BOM