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;      }}

结尾:仅供自己学习,记录问题和参考,若有带来误解和不便请见谅,共勉!

原创粉丝点击