.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.mzph.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…

消息存储服务器吗,消息服务器 消息存储

消息服务器 消息存储 内容精选换一换华为云分布式消息服务帮助中心&#xff0c;为用户提供产品介绍、用户指南、API参考、最佳实践、常见问题、视频帮助等技术文档&#xff0c;帮助您快速上手使用分布式消息服务。消息服务器 消息存储 相关内容联邦学习部署服务的FL-Client接口…

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

服务器上次文件命令 内容精选换一换当创建文件系统后&#xff0c;您需要使用云服务器来挂载该文件系统&#xff0c;以实现多个云服务器共享使用文件系统的目的。CIFS类型的文件系统不支持使用Linux操作系统的云服务器进行挂载。同一SFS容量型文件系统不能同时支持NFS协议和CIFS…

spd不能修改服务器内存条的原因,修改内存SPD 解决蓝屏问题

修改内存SPD 解决蓝屏问题互联网 发布时间&#xff1a;2009-04-21 01:18:13 作者&#xff1a;佚名 我要评论问&#xff1a;一台电脑的内存是HY 256MB DDRII 533&#xff0c;最近又购买了一条HY 256MB DDRII 533内存&#xff0c;与原有内存组成双通道。使用时偶尔会出现蓝…

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

云服务器 本地文件 内容精选换一换在云服务器上搭建网站后&#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>…

mysql注入fuzz字典_sql注入fuzz bypass waf

本帖最后由 xmidf 于 2018-7-11 10:16 编辑作者&#xff1a;whynot 转自&#xff1a;先知0x0 前言这里是简单对sql注入绕过waf的一个小总结&#xff0c;非安全研究员&#xff0c;这里不讲原理&#xff0c;关于原理搜集了一些其他大佬的文章(文章在最下面请自取)&#xff0…

python项目选择背景_Python - - 项目实战 - - 游戏背景

目标背景交替滚动的思路确定显示游戏背景01&#xff0c;背景交替滚动的思路确定运行 备课代码&#xff0c;观察 背景图像的显示效果&#xff1a;游戏启动后&#xff0c;背景图像 会 连续不断地 向下方 移动在 视觉上 产生英雄的飞机不断向上方飞行的 错觉 - - 在很多跑酷游戏中…

css阻止换行_CSS中,如何处理短内容和长内容?

本文已经过原作者 shadeed 授权翻译。当我们使用 CSS 构建布局时&#xff0c;考虑长短文本内容很重要&#xff0c;如果能清楚地知道当文本长度变化时需要怎么处理&#xff0c;可以避免很多不必要的问题。在许多情况下&#xff0c;添加或删除一个单词会改变 UI 的外观&#xff0…

mysql排序区分大小写吗_MySQL操作数据时区分大小写

一般情况下使用SQL语句执行update login_ticket set status1 where ticket‘ABC‘会将ticket’abc‘的数据也改掉&#xff0c;那么需要在列名ticket的后面加上collate utf8_binupdate login_ticket set status1 where ticket COLLATE utf8_bin‘ABC‘这里的collate后面的是指该…

java的四种访问权限_Java四种访问权限

一、访问权限简介访问权限控制&#xff1a; 指的是本类及本类内部的成员(成员变量、成员方法、内部类)对其他类的可见性&#xff0c;即这些内容是否允许其他类访问。Java 中一共有四种访问权限控制&#xff0c;其权限控制的大小情况是这样的&#xff1a;public > protected …

java excel条件格式_Java 设置Excel条件格式(高亮条件值、应用单元格值/公式/数据条等类型)...

概述在Excel中&#xff0c;应用条件格式功能可以在很大程度上改进表格的设计和可读性&#xff0c;用户可以指定单个或者多个单元格区域应用一种或者多种条件格式。本篇文章&#xff0c;将通过Java程序示例介绍条件格式的设置方法&#xff0c;设置条件格式时&#xff0c;因不同设…

mysql 家谱树查询_中国家谱族谱数据库可以登录、查询了

原标题&#xff1a;中国家谱族谱数据库可以登录、查询了中青在线武汉6月6日电(党波涛 中国青年报中青在线记者 雷宇)数据量全球第一&#xff0c;最早可追溯到明朝万历年间。华中师范大学中国农村研究院今天对外发布&#xff0c;由该院建设的中国家谱族谱数据库正式上线&#xf…

位运算java_Java中的位运算

Java中的位运算&#xff0c;说实话&#xff0c;工作了两年的时间里&#xff0c;从来没有用过一次&#xff0c;因为平时都是些的是业务代码&#xff0c;很少接触比较底层的东西&#xff0c;我记得第一次在代码中看到还是在HashMap的Hash算法中看到的&#xff0c;这次重拾Java基础…

java蓝桥杯dfs_第七届 蓝桥杯决赛 Java B组 打靶 解题报告(DFS,回溯,全排列)-Go语言中文社区...

题目&#xff1a;打靶小明参加X星球的打靶比赛。比赛使用电子感应计分系统。其中有一局&#xff0c;小明得了96分。这局小明共打了6发子弹&#xff0c;没有脱靶。但望远镜看过去&#xff0c;只有3个弹孔。显然&#xff0c;有些子弹准确地穿过了前边的弹孔。不同环数得分是这样设…

java 高级泛型_java泛型的高级应用

展开全部在上面的例子中&#xff0c;由于没有限制class GenericsFoo类型持有者T的范围&#xff0c;实际上这里32313133353236313431303231363533e59b9ee7ad9431333339666666的限定类型相当于Object&#xff0c;这和“Object泛型”实质是一样的。限制比如我们要限制T为集合接口类…

java正则表达式 问号_正则表达式问号的四种用法详解

正则表达式问号的四种用法详解原文符号因为?在正则表达式中有特殊的含义&#xff0c;所以如果想匹配?本身&#xff0c;则需要转义&#xff0c;\?有无量词问号可以表示重复前面内容的0次或一次&#xff0c;也就是要么不出现&#xff0c;要么出现一次。非贪婪匹配贪婪匹配在满…

mysql+百万+中间表_MYSQL优化

MYSQL优化是一个非常大的课题&#xff0c;这篇文章主要介绍了跟MYSQL相关的4个方面&#xff0c;如果想深入研究可以查下相关资料。一、服务器级别优化二、操作系统级别优化三、MYSQL级别优化四、SQL级别优化一、服务器级别优化1.服务器选型SUN小型机、DELL730xd、HPDL380、IBM3…