javaWEB项目中webSocket的简单应用

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


}

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值