.net 實時通信_【WebSocket】實時多人答題對戰游戲

18928e82d244cdf578925b30c111c446.png

本文公眾號來源:后端技術漫談?

作者:蠻三刀把刀

前言

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

系列教程回顧:

手把手搭建WebSocket多人在線聊天室

【多人聊天室】WebSocket集群/分布式改造

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

這是我在最近作業競賽中設計的小項目,和小伙伴們一起設計了整個游戲流程和后端代碼,前端頁面暫時就不放開給大家了,大家可以參考前兩章教程自己動手寫一下前端頁面。

本文內容摘要:

  • 在線游戲常用的通訊方案

  • 如何使用WebSocket實現游戲對戰實時通信

  • 游戲步驟的畫面演示和對應的WebSocket接口設計

本文源碼:(媽媽再也不用擔心我無法復現文章代碼啦)

https://github.com/qqxx6661/websocket-game-demo

正文

WebSocket實現在線多人游戲——對戰答題

在線游戲常用的通訊方案

參考:

https://blog.csdn.net/honey199396/article/details/54603860

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接口

用戶創建昵稱,進入玩家大廳

打開界面,進入游戲:

10281d8c88a7e6e786beb51159b6c556.png

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

private?Map<String,?StatusEnum>?userToStatus?=?new?HashMap<>();

用戶狀態分為空閑和游戲中:

public?enum?StatusEnum?{
????IDLE,
????IN_GAME
}

WebSocket接口設計如下:

bce9649d3159582a47f7db26ce05e06f.png

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

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

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

5c6acbd11166a6f1e08e457461ca7c54.png

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

private?Map<String,?String>?userToPlay?=?new?HashMap<>();

WebSocket接口設計如下:

7eba3a30f41f9b4fef03459d3fd241f2.png

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?list=new?ArrayList<>();
????????????questionService.getQuestions(limit).forEach(item->{
????????????????QuestionRelayDTO?relayDTO=new?QuestionRelayDTO();
????????????????relayDTO.setTopic_id(item.getId());
????????????????relayDTO.setTopic_name(item.getQuestion());
????????????????List?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;
????}

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

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

9ad6875ee0ebd71eecbd92342763cd2a.png

WebSocket接口設計如下:

00f5e4f37b892288d47dd8844c34ef5c.png

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,這樣子就把用戶和訂閱路徑唯一的匹配起來了.

參考文獻

點對點通訊:

https://blog.csdn.net/yingxiake/article/details/51224569

總結

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

本文工程源代碼:

https://github.com/qqxx6661/websocket-game-demo

公眾號文章導航:兩年嘔心瀝血的文章!

d03d095b8b6141c8e4435dd2d67555a6.png200多篇原創技術文章海量視頻資源精美腦圖面試題

長按掃碼可關注獲取?

在看和分享對我非常重要!54a4291f8f2862176e17f3a56910aa9f.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/529734.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/529734.shtml
英文地址,請注明出處:http://en.pswp.cn/news/529734.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

磊科路由虛擬服務器設置,磊科路由器虛擬轉發服務設置的方法

磊科路由器虛擬轉發服務設置的方法磊科路由的虛擬 MAC 地址的分配功能實現了不同主機將流量發送給備份組中不同的路由器&#xff0c;但為了使備份組中的路由器能夠轉發主機發送的流量&#xff0c;還需要在路由器上創建虛擬轉發器&#xff0c;每個虛擬轉發器都對應備份組的一個虛…

css動畫執行保持forwards,css3動畫如何在動作結束時保持該狀態不變

animation-fill-mode : none | forwards | backwards | both;none&#xff1a;不改變默認行為。forwards &#xff1a;當動畫完成后&#xff0c;保持最后一個屬性值(在最后一個關鍵幀中定義)。backwards&#xff1a;在 animation-delay 所指定的一段時間內&#xff0c;在動畫顯…

anylogic中如何構建復雜網絡_如何對復雜網絡建模所需要的數據進行預處理

上一篇文章介紹了如何構建Space L實體網絡的模型&#xff0c;這一篇是對上一篇文章的一個補充優化。以下部分摘自上一篇文章&#xff1a;邢八寶&#xff1a;如何建立復雜網絡實體網絡的Space L模型&#xff1f;?zhuanlan.zhihu.com地鐵網絡&#xff0c;一般都有三四百個節點&a…

ajax需要引用什么js文件嗎,如何在ajax調用中包含js文件?

嘿&#xff0c;我找到了添加它的方法.... :)注意 - 這是一個同步過程&#xff0c;所以你不必擔心腳本是否加載....腳本將始終加載實例你調用該函數&#xff0c;你可以立即開始使用加載的腳本..讓我們使用這兩個功能1)第一個是用于檢索值的ajax函數async應為true以同步發送請求/…

門戶網站服務器遷移,云服務器怎么遷移網站

1、尋找新的服務器產品在原云服務器暫時不關閉的前提下&#xff0c; 尋找新的服務器。尋找到新的服務器空間之后&#xff0c;將原主機空間的網站進行備份&#xff0c;并下載備份數據。2、配置新服務器環境一般找到新服務器空間之后&#xff0c;需要根據原網站程序對新服務器進行…

c從oracle到mysql移植_數據庫從oracle移植到mysql時需要進行的修改

分頁方式不同&#xff0c;oracle使用rownum&#xff0c;mysql使用limit使用hibernate的QBC不用修改&#xff0c;但hql和sql都應該用統一方法修改mysql子查詢必須帶別名select * from (select * from city where city_id 1) t 別名(此處是t)必須加存在差異的函數a)日期轉字符串…

nodejs mysql access denied_Node使用Sequlize連接Mysql報錯:Access denied for user ‘xxx’@‘localhost’...

前言最近在工作中遇到問題&#xff0c;問題如下&#xff1a;Unhandled rejection SequelizeAccessDeniedError: Access denied for user lupenglocalhost (using password: YES)這是Node在使用Sequlize連接Mysql數據時報的錯&#xff0c;關鍵看冒號后面的錯誤&#xff1a;訪問拒…

消息存儲服務器嗎,消息服務器 消息存儲

消息服務器 消息存儲 內容精選換一換華為云分布式消息服務幫助中心&#xff0c;為用戶提供產品介紹、用戶指南、API參考、最佳實踐、常見問題、視頻幫助等技術文檔&#xff0c;幫助您快速上手使用分布式消息服務。消息服務器 消息存儲 相關內容聯邦學習部署服務的FL-Client接口…

mysql 跨實例復制數據_社區投稿 | MySQL 跨實例 copy 大表解決方案

作者簡介任坤&#xff0c;現居珠海&#xff0c;先后擔任專職 Oracle 和 MySQL DBA&#xff0c;現在主要負責 MySQL、mongoDB 和 Redis 維護工作。一、背景某天晚上 20:00 左右開發人員找到我&#xff0c;要求把 pre-prod 環境上的某張表導入到 prod &#xff0c;第二天早上 07:…

ajax跨域只能是get,jsonp跨域請求只能get變相解決方案

1.java設置返回表頭&#xff1a;response.setHeader("Access-Control-Allow-Origin","*");response.setHeader("Access-Control-Allow-Methods","POST");response.setHeader("Access-Control-Max-Age","1000");2.…

云服務器虛擬主機區別,云服務器和虛擬主機的區別

云空間服務是云計算服務的重要組成部分,是面向各類互聯網用戶提供綜合業務能力的服務平臺。平臺整合了傳統意義上的互聯網應用三大核心要素:計算、存儲、網絡,面向用戶提供公用化的互聯網基礎設施服務。采用操作系統虛擬化技術,虛擬化效率高,虛擬化License費用低,能共享操作系統…

php連接mysql并操作系統_PHP 連接并操作MySQL的一個實例

/*** MyClass 抽象類,用于執行查詢語句**/class MyClass{const HOST 192.168.73.110:3306;const USER root;const PASSWORD root;const DB kmdbcenter;static $Instance false;private $QueryResult False;private final function __construct(){if(!mysql_connect(MyCla…

服務器上次文件命令,服務器上次文件命令

服務器上次文件命令 內容精選換一換當創建文件系統后&#xff0c;您需要使用云服務器來掛載該文件系統&#xff0c;以實現多個云服務器共享使用文件系統的目的。CIFS類型的文件系統不支持使用Linux操作系統的云服務器進行掛載。同一SFS容量型文件系統不能同時支持NFS協議和CIFS…

win7裝mysql一直未響應6_win7重裝mysql最后一步無響應解決方法

重新安裝MySQL出示未響應&#xff0c;一般顯示在安裝MySQL程序最后一步的2&#xff0c;3項就不動了。這種情況一般是你以前安裝過MySQL數據庫服務項被占用了。解決方法&#xff1a;一種方法&#xff1a;你可以安裝MySQL的時候在這一步時它默認的服務名是“MySQL” 只需要把這個…

spd不能修改服務器內存條的原因,修改內存SPD 解決藍屏問題

修改內存SPD 解決藍屏問題互聯網 發布時間&#xff1a;2009-04-21 01:18:13 作者&#xff1a;佚名 我要評論問&#xff1a;一臺電腦的內存是HY 256MB DDRII 533&#xff0c;最近又購買了一條HY 256MB DDRII 533內存&#xff0c;與原有內存組成雙通道。使用時偶爾會出現藍…

服務器批量修改代碼,利用Redis實現多服務器批量操作

工作中遇到一個項目需要在多個平臺編譯打包&#xff0c;每次都需要登錄到不同的服務器同步代碼&#xff0c;編譯&#xff0c;打包&#xff0c;上傳&#xff0c;非常麻煩&#xff0c;于是想為何不能一次操作&#xff0c;多臺服務器自動執行呢。網上找了下&#xff0c;有很多解決…

django與mysql實現增刪_django與mysql實現簡單的增刪查改

模型定義from django.db import modelsclass Grades(models.Model):g_name models.CharField(max_length20)create_date models.DateTimeField()girl_num models.IntegerField()boy_num models.IntegerField()isDelete models.BooleanField(defaultFalse)def __str__(self…

服務器本地文件,云服務器 本地文件

云服務器 本地文件 內容精選換一換在云服務器上搭建網站后&#xff0c;部分客戶通過本地網絡訪問網站時出現偶發性無法訪問的情況。確認客戶使用的本地網絡。若客戶的本地網絡是NAT網絡(本地主機通過NAT功能使用公網IP地址訪問彈性云服務器)&#xff0c;可能會導致該問題。若客…

mysql oracle 備份數據庫備份_完整備份Oracle數據庫

修改備份文件的有效時間(必須用spfile啟動數據庫)SQLgt; alter system set control_file_record_keep_time30 scopeboth;修改備份文件的有效時間(必須用spfile啟動數據庫)SQL> alter system set control_file_record_keep_time30 scopeboth;System altered.先啟動歸檔SQL>…

修改域服務器IP,域控制器遷移以及修改服務器ip

windows2003域控制器如果服務器太舊就需要遷移至新的服務器上,經本人實驗,無誤。windows server 2003 域控制器轉移遷移準備工作:1. 在Windows Server 2003上運行dcpromo命令將其升級為域控制器&#xff0c;并在升級時選擇使其成為現有Windows 2003域的額外的域控制器。2. 在Wi…