[WebSocket]使用WebSocket實現實時多人答題對戰遊戲

前言

前兩章教程,咱們使用WebSocket的基礎特性打造了一個小小聊天室,並在第二章對其進行了集羣化改造。前端

系列教程回顧:nginx

[WebSocket]第一章:手把手搭建WebSocket多人在線聊天室(SpringBoot+WebSocket)git

[WebSocket]第二章:WebSocket集羣分佈式改造——實現多人在線聊天室github

在本文中,我將介紹如何使用WebSocket向實時多人答題對戰遊戲提供服務端,並詳細介紹通接口的設計。web

這是我在最近做業競賽中設計的小項目,和小夥伴們一塊兒設計了整個遊戲流程和後端代碼,前端頁面暫時就不放開給你們了,你們能夠參考前兩章教程本身動手寫一下前端頁面。算法

本文內容摘要:spring

  • 在線遊戲經常使用的通信方案
  • 如何使用WebSocket實現遊戲對戰實時通訊
  • 遊戲步驟的畫面演示和對應的WebSocket接口設計

本文源碼:(媽媽不再用擔憂我沒法復現文章代碼啦)apache

github.com/qqxx6661/we…後端

正文

WebSocket實如今線多人遊戲——對戰答題

在線遊戲經常使用的通信方案

參考:瀏覽器

blog.csdn.net/honey199396…

HTTP

優勢:協議較成熟,應用普遍、基於TCP/IP,擁有TCP優勢、研發成本很低,開發快速、開源軟件較多,nginx,apache,tomact等

缺點:無狀態無鏈接、只有PULL模式,不支持PUSH、數據報文較大

特性:基於TCP/IP應用層協議、無狀態,無鏈接、支持C/S模式、適用於文本傳輸

TCP

優勢:可靠性 、全雙工協議、開源支持多、應用較普遍、面向鏈接、研發成本低、報文內容不限制(IP層自動分包,重傳,不大於1452bytes)

缺點:操做系統:較耗內存,支持鏈接數有限、設計:協議較複雜,自定義應用層協議、網絡:網絡差狀況下延遲較高、傳輸:效率低於UDP協議

特性:面向鏈接、可靠性、全雙工協議、基於IP層、OSI參考模型位於傳輸層、適用於二進制傳輸

WebScoket

優勢:協議較成熟、基於TCP/IP,擁有TCP優勢、數據報文較小,包頭很是小、面向鏈接,有狀態協議、開源較多,開發較快

缺點:

特性:有狀態,面向鏈接、數據報頭較小、適用於WEB3.0,以及其餘即時聯網通信

UDP

優勢:操做系統:併發高,內存消耗較低、傳輸:效率高,網絡延遲低、傳輸模型簡單,研發成本低

缺點:協議不可靠、單向協議、開源支持少、報文內容有限,不能大於1464bytes、設計:協議設計較複雜、網絡:網絡差,並且丟數據報文

特性:無鏈接,不可靠,基於IP協議層,OSI參考模型位於傳輸層,最大努力交付,適用於二進制傳輸

總結

  • 對於弱聯網類遊戲,必須消除類的,卡牌類的,能夠直接HTTP協議,考慮安全的話直接HTTPS,或者對內容體作對稱加密;
  • 對於實時性,交互性要求較高,能夠優先選擇Websocket,其次TCP協議;
  • 對於實時性要求極高,且可達性要求通常能夠選擇UDP協議;
  • 局域網對戰類,賽車類,直接來UDP協議吧;

WebSocket實現雙人在線遊戲實時通訊

咱們採用websocket做爲咱們的通訊方案,主要是由於咱們但願對戰雙方可以實時顯示對方的得分。

本小節詳細介紹了咱們在線問答對戰遊戲中,具體的websocket通信方式定義。

本問答遊戲規則以下:

  • 用戶打開h5頁面後,輸入本身的暱稱,發送給服務端,服務端將用戶暱稱保存到hashmap,並記錄用戶狀態(空閒,遊戲中),接着用戶進入大廳。
  • 大廳中用戶能夠互相選擇,一旦某用戶選擇了另外一位用戶,將觸發開始遊戲,雙方進入答題模式。
  • 答題的兩位用戶各回答10題,每題答對爲10分,共100分,左上角頁面顯示本身的分數,右上角顯示對方分數,實時經過websocket接收對方分數。
  • 10題結束,雙方等待對方總分,最後判斷輸贏,顯示結果界面。

因此咱們須要設計三個WebSocket協議:

  • 用戶建立暱稱,進入玩家大廳
  • 用戶選擇對手,雙方進入遊戲
  • 對戰過程實時顯示雙方分數

接下來詳細介紹這三種WebSocket接口

用戶建立暱稱,進入玩家大廳

打開界面,進入遊戲:

咱們使用了HashMap存儲用戶狀態,

private Map<String, StatusEnum> userToStatus = new HashMap<>();
複製代碼

用戶狀態分爲空閒和遊戲中:

public enum StatusEnum {
    IDLE,
    IN_GAME
}
複製代碼

WebSocket接口設計以下:

WebSocket接口代碼以下:

@MessageMapping("/game.add_user")
    @SendTo("/topic/game")
    public MessageReply addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String sender = chatMessage.getSender();
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.ADD_USER);
        result.setReceiver(Collections.singletonList(sender));
        if (userToStatus.containsKey(sender)) {
            message.setCode(201);
            message.setStatus("該用戶名已存在");
            message.setChatMessage(result);
            log.warn("addUser[" + sender + "]: " + message.toString());
        } else {
            result.setContent(mapper.writeValueAsString(userToStatus.keySet().stream().filter(k -> userToStatus.get(k).equals(StatusEnum.IDLE)).toArray()));
            message.setCode(200);
            message.setStatus("成功");
            message.setChatMessage(result);
            userToStatus.put(sender, StatusEnum.IDLE);
            headerAccessor.getSessionAttributes().put("username",sender);
            log.warn("addUser[" + sender + "]: " + message.toString());
        }
        return message;
    }
複製代碼

用戶選擇對手,雙方進入遊戲

在大廳中選擇玩家,隨後會進入對戰:

咱們使用了HashMap存儲了正在對戰的用戶,給雙方配對。

private Map<String, String> userToPlay = new HashMap<>();
複製代碼

WebSocket接口設計以下:

WebSocket接口代碼以下:

@MessageMapping("/game.choose_user")
    @SendTo("/topic/game")
    public MessageReply chooseUser(@Payload ChatMessage chatMessage) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String receiver = chatMessage.getContent();
        String sender = chatMessage.getSender();
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.CHOOSE_USER);
        if (userToStatus.containsKey(receiver) && userToStatus.get(receiver).equals(StatusEnum.IDLE)) {
            List<QuestionRelayDTO> list=new ArrayList<>();
            questionService.getQuestions(limit).forEach(item->{
                QuestionRelayDTO relayDTO=new QuestionRelayDTO();
                relayDTO.setTopic_id(item.getId());
                relayDTO.setTopic_name(item.getQuestion());
                List<Answer> answers=new ArrayList<>();
                answers.add(new Answer(1,item.getId(),item.getOptionA(),item.getResult()==1?1:0));
                answers.add(new Answer(2,item.getId(),item.getOptionB(),item.getResult()==2?1:0));
                answers.add(new Answer(3,item.getId(),item.getOptionC(),item.getResult()==3?1:0));
                answers.add(new Answer(4,item.getId(),item.getOptionD(),item.getResult()==4?1:0));
                relayDTO.setTopic_answer(answers);
                list.add(relayDTO);
            });
            result.setContent(mapper.writeValueAsString(list));
            result.setReceiver(Arrays.asList(sender, receiver));
            message.setCode(200);
            message.setStatus("匹配成功");
            message.setChatMessage(result);
            userToStatus.put(receiver, StatusEnum.IN_GAME);
            userToStatus.put(sender, StatusEnum.IN_GAME);
            userToPlay.put(receiver,sender);
            userToPlay.put(sender,receiver);
            log.warn("chooseUser[" + sender + "," + receiver + "]: " + message.toString());
        } else {
            result.setContent(mapper.writeValueAsString(userToStatus.keySet().stream().filter(k -> userToStatus.get(k).equals(StatusEnum.IDLE)).toArray()));
            result.setReceiver(Collections.singletonList(sender));
            message.setCode(202);
            message.setStatus("該用戶不存在或已在遊戲中");
            message.setChatMessage(result);
            log.warn("chooseUser[" + sender + "]: " + message.toString());
        }
        return message;
    }
複製代碼

對戰過程實時顯示雙方分數

對戰過程當中的演示圖:左邊顯示我方分數,右邊顯示對方分數

WebSocket接口設計以下:

WebSocket接口代碼以下:

@MessageMapping("/game.do_exam")
    @SendTo("/topic/game")
    public MessageReply doExam(@Payload ChatMessage chatMessage) throws JsonProcessingException {
        MessageReply message = new MessageReply();
        String sender = chatMessage.getSender();
        String receiver = userToPlay.get(sender);
        ChatMessage result = new ChatMessage();
        result.setType(MessageTypeEnum.DO_EXAM);
        log.warn("userToStatus:" + mapper.writeValueAsString(userToStatus));
        if (userToStatus.containsKey(receiver) && userToStatus.get(receiver).equals(StatusEnum.IN_GAME)) {
            result.setContent(chatMessage.getContent());
            result.setSender(sender);
            result.setReceiver(Collections.singletonList(receiver));
            message.setCode(200);
            message.setStatus("成功");
            message.setChatMessage(result);
            log.warn("doExam[" + receiver + "]: " + message.toString());
        }else{
            result.setReceiver(Collections.singletonList(sender));
            message.setCode(203);
            message.setStatus("該用戶不存在或已退出遊戲");
            message.setChatMessage(result);
            log.warn("doExam[" + sender + "]: " + message.toString());
        }
        return message;
    }
複製代碼

進一步

這個只是個兩天趕出來的Demo,固然裏成品還有很是大的差距。這裏有幾個須要繼續解決的事情:

  • 實現自動匹配/排行榜
  • WebSocket通信優化:在某些地方使用點對點通信,而非所有使用廣播通信。

咱們可使用convertAndSendToUser()方法,按照名字就能夠判斷出來,convertAndSendToUser()方法可以讓咱們給特定用戶發送消息。

spring webscoket能識別帶」/user」的訂閱路徑並作出處理,例如,若是瀏覽器客戶端,訂閱了’/user/topic/greetings’這條路徑,

stompClient.subscribe('/user/topic/greetings', function(data) {
    //...
});
複製代碼

就會被spring websocket利用UserDestinationMessageHandler進行轉化成」/topic/greetings-usererbgz2rq」,」usererbgz2rq」中,user是關鍵字,erbgz2rq是sessionid,這樣子就把用戶和訂閱路徑惟一的匹配起來了

參考文獻

點對點通信:

blog.csdn.net/yingxiake/a…

總結

咱們在本文中實現了在線多人對戰遊戲的服務端WebSocket接口設計,進一步鞏固了對WebSocket的基礎和應用範圍的理解。

本文工程源代碼:

github.com/qqxx6661/we…

關注我

我目前是一名後端開發工程師。主要關注後端開發,數據安全,爬蟲,邊緣計算等方向。

微信:yangzd1102(請註明來意)

Github:@qqxx6661

我的博客:

原創博客主要內容

  • Java知識點複習全手冊
  • Leetcode算法題解析
  • 劍指offer算法題解析
  • SpringCloud菜鳥入門實戰系列
  • SpringBoot菜鳥入門實戰系列
  • 爬蟲相關技術文章
  • 後端開發相關技術文章

我的公衆號:後端技術漫談

我的公衆號:後端技術漫談

若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章
相關標籤/搜索