小型直播系統系列-樂聊TV的開發(四)

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/qq_18885315/article/details/77989829

小型直播系統系列-樂聊TV的開發(四)

這一節,我們講解一下基於websocket的彈幕實現:首先了解一下websocket協議


websocket

們知道,傳統的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中的實現。
  

websocket彈幕

    @OnOpen
    public void onOpen(Session session) {
        session.setMaxTextMessageBufferSize((int) MAX_BIG_LONG);
        addOnlineCount(); // 在線數加1--必須先加1---錯誤的時候會減1
        this.session = session;
        if (!parseQueryString(session)) // 如果未能取得用戶id和type,退出
            return;
        // 驗證賬號,防止僞造
        this.chatUser = loginChatServer(chatUserId);
        if (this.chatUser == null) {
            closeSession(session);
            return;
        }
        addChatUserToHashMap(roomId, chatUserId);
        try {
            // 發一個應答標記,表示已經成功登陸,沒有構造
            sendMessage("SUCCESS");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Constant.ONLINECOUNT = onlineCount.toString();
    }

onopen標識客戶端與服務器進行通訊連接,在此處可進行業務邏輯的處理,將所需賬號提出進行保存

private static Log logger = LogFactory.getLog(ChatServer.class);
    /** AtomicInteger:線程安全的整數對象 */
    private static AtomicInteger onlineCount = new AtomicInteger(0);// 線程安全整數對象
    private static long MAX_BIG_LONG = 1024 * 4 * 1024;
    /** roomId與一個集合的哈希。集合中存儲當前房間的所有用戶 */
    private static ConcurrentHashMap<String, CopyOnWriteArraySet<String>> roomToChatUserHashMap = new ConcurrentHashMap<String, CopyOnWriteArraySet<String>>();
    /** 用戶與chatServer實例的哈希。 */
    private static ConcurrentHashMap<String, ChatServer> chatUserToChatServer = new ConcurrentHashMap<String, ChatServer>();
    /** token驗證 **/
    private String token;
    /** 房間號 **/
    private String roomId;
    /** chatUser 主鍵Id **/
    private String chatUserId;
    /** chatUserd對象 **/
    private ChatUser chatUser;

聲明兩個類級別的ConcurrentHashMap存儲房間號和chatUser之間的對應關係,一個房間對應多個chatUser賬號,一個chatUser對應一個chatServer實例,發送消息時只需進行遍歷這倆個map找到相應的實例對象,調用它的發消息即可成功的發送

/**
     * 一個房間對應的一個chatuser列表 發消息時候進行遍歷操作
     * 
     * @param chatUserId
     * @param chatUserId
     * @return
     */
    private boolean addChatUserToHashMap(String roomId, String chatUserId) {
        try {
            CopyOnWriteArraySet<String> chatUserIdSet = null;
            if (roomToChatUserHashMap.containsKey(roomId)) {
                chatUserIdSet = roomToChatUserHashMap.get(roomId);
            } else {
                chatUserIdSet = new CopyOnWriteArraySet<String>();
            }
            chatUserIdSet.add(chatUserId);
            roomToChatUserHashMap.put(roomId, chatUserIdSet);
            chatUserToChatServer.put(chatUserId, this);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

這是進入房間時,將自身賬號加入到房間聊天羣組,用來發廣播消息時遍歷的

/**
     * 從哈希中移除已經斷開的連接
     * 
     * @param chatUserId
     * @param terminalUuid
     * @return
     */
    private boolean removeChatUserFromRoomHashMap(String roomId, String chatUserId) {
        try {
            CopyOnWriteArraySet<String> chatUserIdSet = null;
            if (roomToChatUserHashMap.containsKey(roomId)) {// 如果存在
                chatUserIdSet = roomToChatUserHashMap.get(chatUserId);// 取得chatUserId的集合
            } else {
                return true;
            }
            chatUserIdSet.remove(chatUserId);// 從集合中移除
            if (chatUserIdSet.size() == 0) {// 如果已經沒有連接終端
                roomToChatUserHashMap.remove(roomId);// 則清除
            } else {
                roomToChatUserHashMap.put(roomId, chatUserIdSet);// 更新哈希
            }
            ChatServer chatServer = chatUserToChatServer.get(chatUserId);
            // 釋放資源,清空chatServer
            chatServer = null;
            chatUserToChatServer.remove(chatUserId);// 將chatServer的實例從哈希中移除。
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

當斷開聊天室時,應該講其從聊天組移除掉

 * 關閉websocket連接。
     * 
     * @param session
     *            要關閉的會話
     */
    private void closeSession(Session session) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return;
    }

    /**
     * 連接關閉調用的方法
     */
    @OnClose
    public void onClose(Session session) {
        try {
            removeChatUserFromRoomHashMap(this.roomId, this.chatUserId);
            subOnlineCount(); // 在線數減1
        } catch (Exception e) {

        }
    }

關閉調用的方法

    @OnMessage
    public void onMessage(String message, Session session) {
        if (StringUtils.isBlank(message)) // 收到的是空串
            return;
        if (StringUtils.equals(Constant.SUCCESS_RESPONSE, message)) {
            return;
        }
        Gson gson = null;
        try {// 解析json串
            gson = new Gson();
            ChatMessage chatMessage = gson.fromJson(message, ChatMessage.class);
            // 解析json出錯
            if (chatMessage == null)
                return;
            /**
             * 目的爲了以後對每個模塊進行拓展,所以分開寫
             * 
             */
            // 如果是圖片類型的消息體
            if (StringUtils.equals(chatMessage.getMessageType(), EnumMessageType.IMAGE.name())) {
                try {
                    boolean dealImageResult = dealBinary(chatMessage);// 處理圖片結果
                    if (!dealImageResult) {
                        return;// 如果沒生成,則返回
                    }
                } catch (Exception e) {
                    logger.error("處理圖片異常" + e.getMessage());
                }
            }
            // 處理小視頻
            else if ((StringUtils.equals(chatMessage.getMessageType(), EnumMessageType.VIDEO.name()))) {
                try {
                    boolean dealVideoResult = dealBinary(chatMessage);
                    if (!dealVideoResult) {
                        return;
                    }
                } catch (Exception e) {
                    logger.error("處理小視頻異常" + e.getMessage());
                }

            }...................代碼不全,需要的找我聯繫

進行通訊的模塊

不足之處

爲考慮多個終端登錄的情況,比如一個用戶雙開瀏覽器,這時候這套方案顯然是不可行的,具體的解決方案找我諮詢

彈幕聊天室

如果成功了之後就會是下面的場景

這裏寫圖片描述

這裏寫圖片描述

開始動手製作你自己的直播間和聊天室吧。

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