前言

前两章教程,我们使用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. C# ASP.NET MVC 配置 跨域访问

    在web.config文件中的 system.webServer 节点下 增加如下配置        <httpProtocol>             <customHeader ...

  2. linux安装jdk压缩包版

    1.下载压缩包可以选择国内大厂的jdk镜像网站下载速度很快, 比如华为的:https://repo.huaweicloud.com/java/jdk/ 2.查看Linux系统是否有自带的jdk: 输入 ...

  3. pymysql连接、关闭、查询,python如何操作mysql数据库

    1 def get_conn(): 2 """ 3 :return: 连接,游标 4 """ 5 # 创建连接 6 conn = pymys ...

  4. Java学习路线之redis

    1.redis 大数据时代三V:海量Volume.多样Variety.实时Velocity 大数据时代三高:高并发.高可用(无限套娃+彼此监控).高性能 - Redis(Remote Dictiona ...

  5. C++ atomic

    atomic 每个 std::atomic 模板的实例化和全特化定义一个原子类型.若一个线程写入原子对象,同时另一线程从它读取,则行为良好定义. 另外,对原子对象的访问可以建立线程间同步,并按 std ...

  6. #dp#D 导出子图

    代码 #include <cstdio> #include <cctype> #include <algorithm> #define rr register us ...

  7. el-table边框颜色修改—骨灰级

    一.前言说明 1. 网上很多都是通过上下左右边框方式,如: .el-table { border-bottom: 1px solid black; border-right: 1px solid bl ...

  8. Android 开发入门(1)

    0x01 准备 (1)概述 安卓(Android)基于 Linux 内核开发的操作系统,由 Google 等领导开发. (2)版本 Android 版本号 API 发布时间 Android 14 - ...

  9. 润乾报表与 ActiveReport JS 功能对比

    简介 润乾报表是用于报表制作的大型企业级报表软件,核心特点在于开创性地提出了非线性报表数学模型,采用了革命性的多源关联分片.不规则分组.自由格间运算.行列对称等技术,使得复杂报表的设计简单化,以往难以 ...

  10. 动态尺寸模型优化实践之Shape Constraint IR Part I

    简介: 在本系列分享中我们将介绍BladeDISC在动态shape语义下做性能优化的一些实践和思考.本次分享的是我们最近开展的有关shape constraint IR的工作,Part I 中我们将介 ...