前言

前两章教程,我们使用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. SpringBoot集成drools

    目录 1.背景 2.需求 3.实现 3.1 引入jar包 3.2 编写drools配置类 3.3 编写Person对象 3.4 编写drl文件 3.5 编写kmodule.xml文件 3.6 编写Co ...

  2. windows 远程桌面 复制粘贴 无效

    rdpclip.exe进程没有运行或运行异常. rdpclip 是让rdp协议(远程桌面协议)可以通过远程复制文件的,如果你使用rdp(3389)远程连接别人或者被别人连接,通常这个进程都会启动,他的 ...

  3. spring redis 工具类

    import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; ...

  4. read IEEE Standard for verilog(1)

    IEEE Standard for Verilog Hardware Description Language 英语说明阅读,首先看导读.目录.摘要等内容. 摘要: 1 Abstract: The V ...

  5. Spring Cloud 服务的注册与发现之eureka客户端注册

    1.在客户端maven项目中添加eureka客户端依赖 <dependency> <groupId>org.springframework.cloud</groupId& ...

  6. KingbaseES V8R6 数据库运维案例之 -- root用户securecmd连接'Permission denied'错误

    案例分析: 在KingbaseES V8R6数据库在不支持ssh连接的系统环境,可以通过securecmdd服务建立主机之间的通讯,默认securecmdd服务建立用户之间的互信,通过publicke ...

  7. 怎样更直观的查看KingbaseES数据库日志

    数据库日志相关参数:默认设置 log_destination = 'stderr' # Valid values are combinations of # stderr, csvlog, syslo ...

  8. 【已解决】Python使用sql语句模糊查询报错:python中TypeError: not enough arguments for format string

    2021-03-26-19:54:13 今天下午对世界疫情详情数据库进行模糊查询. 首先要明确一点:使用模糊查询就会用到WHERE语句中的LIKE语句,主要说一下在使用 % 的时候要注意的点: 在Py ...

  9. 学习Source Generators之IncrementalValueProvider

    前面我们使用了IIncrementalGenerator来生成代码,接下来我们来详细了解下IIncrementalGenerator的核心部分IncrementalValueProvider. 介绍 ...

  10. #点分树 or LCT#洛谷 4115 Qtree4

    两次LCT的Access操作就可以求LCA啦 题目 给出一棵树,支持单点反色和查询全局最远白点 分析(点分树) 点分树的做法就是考虑点分树上的父亲把子树分成若干个部分, 那么所谓的白点直径可以把子树的 ...