前言

前两章教程,我们使用WebSocket的基础特性打造了一个小小聊天室,并在第二章对其进行了集群化改造。

系列教程回顾:

[WebSocket]第一章:手把手搭建WebSocket多人在线聊天室(SpringBoot+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接口

用户创建昵称,进入玩家大厅

打开界面,进入游戏:

我们使用了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,这样子就把用户和订阅路径唯一的匹配起来了

参考文献

点对点通讯:

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

总结

我们在本文中实现了在线多人对战游戏的服务端WebSocket接口设计,进一步巩固了对WebSocket的基础和应用范围的理解。

本文工程源代码:

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

关注我

我目前是一名后端开发工程师。主要关注后端开发,数据安全,爬虫,边缘计算等方向。

微信:yangzd1102(请注明来意)

Github:@qqxx6661

个人博客:

原创博客主要内容

  • Java知识点复习全手册
  • Leetcode算法题解析
  • 剑指offer算法题解析
  • SpringCloud菜鸟入门实战系列
  • SpringBoot菜鸟入门实战系列
  • 爬虫相关技术文章
  • 后端开发相关技术文章

个人公众号:后端技术漫谈

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

使用WebSocket实现实时多人答题对战游戏的更多相关文章

  1. UE4 多人网络对战游戏笔记

    1.给物体施加一个径向力 定义一个径向力: URadialForceComponent* RadialForceComp; 在构造函数里赋默认值: RadialForceComp = CreateDe ...

  2. Asp.net+WebSocket+Emgucv实时人脸识别

    上个月在网上看到一个用web实现简单AR效果的文章,然后自己一路折腾,最后折腾出来一个 Asp.net+WebSocket+Emgucv实时人脸识别的东西,网上也有不少相关资料,有用winform的也 ...

  3. [转]使用 HTML5 WebSocket 构建实时 Web 应用

    HTML5 WebSocket 简介和实战演练 本文主要介绍了 HTML5 WebSocket 的原理以及它给实时 Web 开发带来的革命性的创新,并通过一个 WebSocket 服务器和客户端的案例 ...

  4. 使用 HTML5 WebSocket 构建实时 Web 应用

    原文地址:http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/ HTML5 WebSocket 简介和实战演练 本文主要介绍 ...

  5. (转)使用 HTML5 WebSocket 构建实时 Web 应用

    HTML5 WebSocket 简介和实战演练 本文主要介绍了 HTML5 WebSocket 的原理以及它给实时 Web 开发带来的革命性的创新,并通过一个 WebSocket 服务器和客户端的案例 ...

  6. springboot搭建一个简单的websocket的实时推送应用

    说一下实用springboot搭建一个简单的websocket 的实时推送应用 websocket是什么 WebSocket是一种在单个TCP连接上进行全双工通信的协议 我们以前用的http协议只能单 ...

  7. WebSocket实现实时聊天系统

    WebSocket实现实时聊天系统 等闲变却故人心,却道故人心易变. 简介:前几天看了WebSocket,今天体验下它的实时聊天. 一.项目介绍 WebSocket 实时聊天系统自己一个一码的搞出来还 ...

  8. day22 01 初识面向对象----简单的人狗大战小游戏

    day22 01 初识面向对象----简单的人狗大战小游戏 假设有一个简单的小游戏:人狗大战   怎样用代码去实现呢? 首先得有任何狗这两个角色,并且每个角色都有他们自己的一些属性,比如任务名字nam ...

  9. springboot1.5.9整合websocket实现实时显示的小demo

    最近由于项目需要实时显示数据库更新的数据变化情况,一开始想过在前端使用ajax异步轮询方法实现,但后面考虑到性能和流量等要求,就放弃该方法而选择使用websocket(毕竟现在springboot整合 ...

  10. SpringBoot + WebSocket 实现答题对战匹配机制

    概要设计 类似竞技问答游戏:用户随机匹配一名对手,双方同时开始答题,直到双方都完成答题,对局结束.基本的逻辑就是这样,如果有其他需求,可以在其基础上进行扩展 明确了这一点,下面介绍开发思路.为每个用户 ...

随机推荐

  1. 基于R语言的GD库实现地理探测器并自动将连续变量转为类别变量

      本文介绍基于R语言中的GD包,依据栅格影像数据,实现自变量最优离散化方法选取与执行,并进行地理探测器(Geodetector)操作的方法.   首先,在R语言中进行地理探测器操作,可通过geode ...

  2. r-nacos v0.4.0版本发布

    r-nacos是一个用 rust重新实现的nacos. r-nacos比java实现的nacos更轻量.快速.稳定:合适在开发.测试.受资限服务等环境平替nacos服务使用. r-nacos v0.4 ...

  3. C# OpenCvSharp+ 微信二维码引擎实现二维码识别

    微信开源了其二维码的解码功能,并贡献给 OpenCV 社区.其开源的 wechat_qrcode 项目被收录到 OpenCV contrib 项目中.从 OpenCV 4.5.2 版本开始,就可以直接 ...

  4. C# 强大的网页处理类NSoup

    地址: https://github.com/GeReV/NSoup using System; using System.Collections.Generic; using System.Comp ...

  5. KingbaseESV8R6用户登录失败自动锁定后解锁遇到权限问题

    测试用户登录失败自动锁定 创建用户tee并授权. TEST=# create user tee; CREATE ROLE TEST=# alter user tee with createdb; AL ...

  6. defer 延迟调用【GO 基础】

    〇.前言 在 Go 语言中,defer 是一种用于延迟调用的关键字. defer 在 Go 语言中的地位非常重要,它是确保资源正确释放和程序健壮性的关键字. 本文将通过示例对其进行专门的详解. 一.d ...

  7. #笛卡尔树,dp#洛谷 7244 章节划分

    题目 分析 考虑段数受到答案限制,而答案为最大值的约数,那么枚举答案, 设\(dp[i]\)表示前\(i\)个位置分完最多可以分多少段只要\(dp[n]\geq k\)即合法. 那么\(dp[i]=\ ...

  8. C#开发的绑定类型默认应用例子 - 开源研究系列文章

    这次在用C#编写一个看图软件小工具,然后其它的基本完成了,就是绑定看图软件到那些个图片扩展名的时候碰到了问题,就是如何将看图软件绑定图片文件的默认应用,以及解绑默认应用.这个涉及到注册表操作,但是找度 ...

  9. pip提示升级pip版本

    今天在进行pip安装库的时候发现下面出现一段黄色的字体,如下图: 自由翻译: 大概意思就是说我现在安装的pip的版本是 20.0.2,最新版本是 20.1,希望我可以更新到最新的版本,并告诉了我怎么安 ...

  10. CentOS 利用pam控制ssh用户的登录及SSH安全配置

    CentOS 利用pam控制ssh用户的登录 有关pam的使用,请找相关的文档.下面只说两个简单的例子. 首先在/etc/pam.d/sshd加入一句: account    required     ...