基於spring @EnableWebSocket 實現socket通信業務處理優化

本篇文章針對基於spring @EnableWebSocket 實現socket通信業務處理的處理

在socket通信默認情況下是線程不安全的,當多個線程訪問同一個socket實體是將會發生錯誤,具體看源碼當socket發送信息是改變自身狀態,當另一個線程發送時會檢查狀態,當狀態不爲初始值是將拋出異常,

本人解決思路是將每個socket客戶端的信息根據放到單獨隊列去處理,以實現單線程操作

不涉及socket存儲處理以及發送的邏輯代碼

 

首先定義消息實體類封裝收到的消息

public class MessageCheckBean {
    /*
     1 open 2 message 3 close 4 error
     */
    private Integer msgType;
    private WebSocketSession session;
    private String msg;
    private CloseReason closeReason;
    private Throwable throwable;
    public Integer getMsgType() {
        return msgType;
    }

    public void setMsgType(Integer msgType) {
        this.msgType = msgType;
    }

    public WebSocketSession getSession() {
        return session;
    }

    public void setSession(WebSocketSession session) {
        this.session = session;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public CloseReason getCloseReason() {
        return closeReason;
    }

    public void setCloseReason(CloseReason closeReason) {
        this.closeReason = closeReason;
    }

    public Throwable getThrowable() {
        return throwable;
    }

    public void setThrowable(Throwable throwable) {
        this.throwable = throwable;
    }

然後首先消息隊列以及線程池管理邏輯,講接收消息和發送消息分開

/**
 * 消息隊列以及線程池管理
 */
public class MsgCheckSessionQueueManager {

    private static boolean openAll = false;
    private static boolean closeAllThread = false;
    /*
    原始數據隊列
     */
    private static LinkedBlockingQueue<MessageCheckBean> rowBlockingQueue = new LinkedBlockingQueue();
    private static ArrayList<LinkedBlockingQueue<UserMsg>> needSendMsgArray = new ArrayList<>();
    static {
        needSendMsgArray.add(new LinkedBlockingQueue<>());
        needSendMsgArray.add(new LinkedBlockingQueue<>());
        needSendMsgArray.add(new LinkedBlockingQueue<>());
    }
    //實際生產請使用其他線程池
    private static ExecutorService sendMsgCacheThreadPool = new ThreadPoolExecutor(needSendMsgArray.size(), needSendMsgArray.size(),
            20L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());
    private static ExecutorService rawMsgDealCacheThreadPool = new ThreadPoolExecutor(needSendMsgArray.size(), needSendMsgArray.size(),
            20L, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>());

    /**
     * 處理client發送來的消息
     * @param msg
     */
    public static void addRawMsg(MessageCheckBean msg){
        try {
            rowBlockingQueue.put(msg);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 處理髮送給client的消息
     * @param userMsg
     */
    public static void addUserMsg(UserMsg userMsg){
        try {
            needSendMsgArray.get(根據業務處理消息到不同隊列).put(userMsg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
     開啓線程調度
     */
    public static void beginDealMsg(){
        if(!openAll){
            dealMessage();
            openAll = true;
        }
    }

    public static void main(String[] args) {
        beginDealMsg();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        shutDownTheadPool();
    }

    private static void dealMessage(){
        Thread threadRowDeal = new Thread(new Runnable() {
            @Override
            public void run() {
                rawMsgDealCacheThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (!closeAllThread){
                                MessageCheckBean msgBewan = rowBlockingQueue.take();
                                //處理來自socket客戶端端的原始數據
                                dealRawMsgToSendQueue(msgBewan);
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        });
       threadRowDeal.setDaemon(true);
       threadRowDeal.start();

       //開啓線程池處理消息隊列
       Thread openRowDealThread = new Thread(new Runnable() {
           @Override
           public void run() {
               for(int i = 0; i< needSendMsgArray.size(); i++){
                   final int j = i;
                   sendMsgCacheThreadPool.execute(new Runnable() {
                       @Override
                       public void run() {
                           LinkedBlockingQueue<UserMsg> linkedBlockingQueue = needSendMsgArray.get(j);
                           while (!closeAllThread){
                               try {
                                   UserMsg userMsg = linkedBlockingQueue.take();
                                   //發送消息
                                   MsgClientDealCheckSeesionManager.sendMsgToClient(userMsg);
                               } catch (InterruptedException e) {
                                   e.printStackTrace();
                               }
                               //處理來自socket客戶端端的原始數據
                           }
                       }
                   });
               }
           }
       });
       openRowDealThread.setDaemon(true);
       openRowDealThread.start();
    }

    /**
     * 更具業務處理來自socket客戶端的原始數據 並將消息插入到needSendMsgArray
     * 每個用戶插入到單獨隊列
     */
    private static void dealRawMsgToSendQueue(MessageCheckBean msgBean) throws InterruptedException {
        UserMsg userMsg = MsgClientDealCheckSeesionManager.dealRawMsgToSendQueue(msgBean);
        if(userMsg != null){
            addUserMsg(userMsg);
        }
    }
    /*
        關閉線程池  添加消息防止堵塞隊列拋出異常
     */
    public static void shutDownTheadPool(){
        closeAllThread = true;
        openAll = false;
        MessageCheckBean messageBean = new MessageCheckBean();
        messageBean.setMsgType(5);
        MsgCheckSessionQueueManager.addRawMsg(messageBean);
        for (int i = 0; i < needSendMsgArray.size(); i++) {
            try {
                UserMsg userMsg = new UserMsg();
                userMsg.setDeviceId(String.valueOf(i));
                needSendMsgArray.get(i).put(userMsg);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        rawMsgDealCacheThreadPool.shutdown();
        sendMsgCacheThreadPool.shutdown();
    }
}

封裝消息接收消息處理類實現收到信息的業務處理

/**
 * 消息處理類
 *    1 open 2 message 3 close 4 error
 */
public class MsgCheckSeesionHandler {

    public static void openSeesion(WebSocketSession session){
        MessageCheckBean messageBean = new MessageCheckBean();
        messageBean.setMsgType(1);
        messageBean.setSession(session);
        MsgCheckSessionQueueManager.addRawMsg(messageBean);
    }

    public static void onMessage(WebSocketSession session, String msg){
        MessageCheckBean messageBean = new MessageCheckBean();
        messageBean.setMsgType(2);
        messageBean.setSession(session);
        messageBean.setMsg(msg);
        MsgCheckSessionQueueManager.addRawMsg(messageBean);
    }

    public static void onClose(WebSocketSession session) {
        MessageCheckBean messageBean = new MessageCheckBean();
        messageBean.setMsgType(3);
        messageBean.setSession(session);
        MsgCheckSessionQueueManager.addRawMsg(messageBean);
    }

    public static void onError(WebSocketSession session, Throwable throwable) {
        MessageCheckBean messageBean = new MessageCheckBean();
        messageBean.setMsgType(4);
        messageBean.setSession(session);
        messageBean.setThrowable(throwable);
        MsgCheckSessionQueueManager.addRawMsg(messageBean);
    }
}

處理封裝的消息的具體實現類更具業務處理消息

/**
 * 原始數據處理類
 */
public class MsgClientDealCheckSeesionManager {

    /*
    根據userMsg直接發送消息
     */
    public static void sendMsgToClient(UserMsg userMsg) {

        System.out.println("send to client" + userMsg.getDeviceId());
    }

    /*
    處理客戶端發送來的消息
     */
    public static UserMsg dealRawMsgToSendQueue(MessageCheckBean msgBean) {
        UserMsg uerMsg = new UserMsg();
        if(msgBean.getMsgType() == 1){
            //openSession
            uerMsg = SeesionOpenDealManager.openSeesion(msgBean.getSession());
        } else if(msgBean.getMsgType() == 2){
            //onMessage
            System.out.println("message");
            uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
            uerMsg = SessionMessageDealManager.onMessag(msgBean.getSession(),msgBean.getMsg());
        } else if(msgBean.getMsgType() == 3){
            //onClose
            uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
            System.out.println("close");
        } else if(msgBean.getMsgType() == 4){
            //onError
            uerMsg.setDeviceId(msgBean.getSession().getAttributes().get("deviceId").toString());
            System.out.println("error");
        } else {

        }
        return uerMsg;
    }
}

重新上篇文章長得消息接口實現類替換消息接收處理,將消息直接存儲到消息隊列中

@RestController
public class WebSocketControllerBack extends TextWebSocketHandler {

    private final Logger logger = LoggerFactory.getLogger(WebSocketController.class);

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        logger.info("WebSocket服務端連接: "+session.getAttributes().get("token")+"===>"+session.getAttributes().get("deviceId"));
        MsgCheckSeesionHandler.openSeesion(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        logger.info("WebSocket服務端關閉: 關閉連接狀態: "+status);
        MsgCheckSeesionHandler.onClose(session);
    }

    @Override
    public void handleMessage(WebSocketSession wsSession, WebSocketMessage<?> message) throws Exception {
        logger.info("WebSocket服務端接受:接受來自客戶端發送的信息: "+message.getPayload().toString());
       MsgCheckSeesionHandler.onMessage(wsSession, message.getPayload().toString());
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        logger.info("WebSocket服務端異常:連接異常信息: " + exception.getMessage());
        MsgCheckSeesionHandler.onError(session, exception);
    }

    /*
     * 是否支持消息拆分發送:如果接收的數據量比較大,最好打開(true), 否則可能會導致接收失敗。
     * 如果出現WebSocket連接接收一次數據後就自動斷開,應檢查是否是這裏的問題。
     */
    @Override
    public boolean supportsPartialMessages() {
        return true;
    }

}

到此整體思路實現完成,此版本針對對數據安全不高的通信,若不能丟數據或者其他業務需求,可嘗試將消息存儲到其他消息框架中

 

更具業務優化發送消息

   public static void sendMsgToClient(UserMsg userMsg) {

        System.out.println("send to client" +);
    }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章