坦克大战的网络对战实现C++(客户端+服务端)

坦克大战本为书本《C++项目开发实战入门》中的一个基于MFC实现的单机游戏。因为个人兴趣便将其中的双人游戏改为网络双人对战。
服务器是使用C++实现,实际上写了两个版本,主要为通信协议不同,一个为TCP协议另一个为UDP协议,客户端对应也改了两个版本,但因为UDP协议的版本最后完成的,所以客户端的UDP版更完善。本文也以UDP版本为基准进行讨论。
数据库使用MySQL,主要用于登录时候账号密码的验证。储存信息并不多,信息为用户UID,用户账号以及用户密码。
用户账号信息
数据传输使用Protobuf 3。
其中 游戏对战中使用的类结构

//For UDP_Server 
syntax="proto3";

message prome
{  uint64 state=1; //游戏的状态 
   Account account=2;
   PVP pvp=3;
   Game game=4;
   Bullet bullet=5;
}


message Account  //登录状态为0
{

   uint64 uid=1;//用户UID
   string name=2;
   string password=3;
}

message PVP //匹配状态为2,设计用于分别是否断开重连的状态,现只用于请求对战匹配。
{
   uint64 setout=1;// 
   uint64 relog=2;
 
}  
message Game //游戏状态为4
{
   float tankx=1;//坦克 X轴方向位置
   float tanky=2;//坦克 Y轴方向位置
   uint64 dire=3; //坦克的转向,左为1,右为2,上为3,下为4
}

message Bullet     //游戏状态为5,state==5表示对战坦克进行开枪动作。
{				   //客户端中子弹是有归属的,比如属于本地player01坦克或属于对战player02坦克,但任何子弹和任何坦克相遇,坦克都会爆炸。
   float bulletx=1;//为了简化传输信息的大小,不再传输子弹位置的信息。
   float bullety=2;//当state==5时候,对战player02坦克的位置上发射的子弹是归属于本地player01坦克的。
                   //换句话说,对战player02坦克的子弹是本地player01坦克替它隔空发射的。
}
state= =0,表示请求验证账号密码。  
state= =1, 表示账号密码正确 
state= =2,表示客户端请求匹配对手。
state= =3客户端进行游戏初始化,传输坦克最初位置。
state= =4 客户端处于游戏阶段,传输坦克位置信息。
state= =5 表示坦克进行开枪动作	
state= =6 坦克被击中结束游戏。
state= =7 表示客户端退出,断开连接。

游戏逻辑如下图
游戏逻辑
服务器本来打算使用多进程,但游戏实在过于简单,服务器只负责简单的密码验证,匹配对手以及处理信息,所以服务器无论采用TCP还是UDP的都是单线程的。其实服务器已经创建了多一个进程,但完全不需要也用不着,只调一个 cout 函数,只当练习一下也没有将服务器优化回单进程。
TCP版本的服务器通信方面采用Linux特有的I/O复用模型-epoll。在epoll中为了避免输入缓冲中有数据就会一直通知该事件(注册到发生变化额文件描述符),造成重复多次向客户端发送相同数据,可能造成游戏操作延迟。所以epoll默认的将以条件触发的事件注册方式更改为以边缘触发的方式调用。同时边缘触发方式下,以阻塞方式工作的read&write函数有可能引起服务器的长时间停顿。因此,将套接字改为非阻塞模式。
客户端是根据玩家操作发送包的,一个动作发送一次包,但由于动作过于高频,丢包重传会导致延迟的发生,而且会越来越大。为了解决延迟问题重新写了 一个UDP版本。
而在服务器匹配方面,当state==2时,会查询游戏数组匹配对手。所谓的游戏数组是一个结构体数组,结构体里面包含这用户UID,用户IP地址,游戏状态以及对战对手的数组下标。结构体如下:

//For UDP_Server 
struct PVP_array{
					int oneuid;//用户uid
					int oppo;//对战对手的数组下标
					struct sockaddr_in one_adr;//用户IP地址
					int pstate;//游戏状态
				}; 
pstate==0 时,表示该结构体为空,可充填用户信息。
pstate==1 时,表示该结构体以充填用户信息。
pstate==2 时,表示该结构体用户正在匹配对战对手。
pstate==3时,表示该结构体用户匹配对战对手完成,此时对战对手数组下标会赋值于结构体中的oppo。

因为用户信息并不多所以使用结构体存储用户的信息,如果用户信息比较多使用类实现会更好。TCP版本的结构体更为简单,因为长连接原因所以直接使用套接字描述符作为不同用户匹配,对战时的唯一标识,连UID都不使用,这时用户账号密码的意义只是登录的钥匙而已没有其它用处,比如用相同账号密码登陆的两个用户可以进行登录对战。而在UDP版本结构体中增加了UID解决了这一个问题,同时使用UID作为用户的唯一标识。
客户端上耗了最多时间。为了能修改客户端,大部分时间都是在读代码。简单熟悉了下MFC给客户端加上了登录界面,也添加了用于通信的类。本来坦克大战游戏中是有迷宫地图的,地图是每次游戏是随机生成的,但是已经不调用随机生成地图的函数了,这样两个客户端联网进行游戏时,可以省略加载统一地图的步骤。坦克原本是按上方向键前进,下方向键后退,左方向键左转,右方向键右转这样的操控方式不太符合个人习惯,便更改为与游戏《1999坦克大战》的操作方式一样,按上方向键往上前进,下方向键往下前进,左方向键往左前进,右方向键往右前进。坦克速度,子弹速度以及子弹数量也做了一些修改。在TCP版的客户端中起初是打算在一个线程完成游戏的操作,信息发送以及信息接收的,于是使用了select函数,把select放到子弹检测的循环中,但是需要坦克发射才开始循环,这时候才更新对战对手的信息。找不到select的合适位置,就在游戏初始化的位置多开了一个线程用于接受对战对手的信息。这时候其实不必使用select函数了,但为了方便TCP版本的客户端中还是调用select函数。

演示视频:

MyVideo_1

代码连接Github坦克大战
UDP_Tank.rar,TCP_Tank.rar两个是编译完成的UDP和TCP客服端。
UDP_Game.h,UDP_Game.cpp是UDP客户端主要的游戏逻辑部分代码,主要改动也在其中。TCP_Game.h,TCP_Game.cpp亦是如此。
message.bp,mysql_data.h,myql_data.cpp以及UDP_main.cpp可在linux环境下编译成UDP服务器的文件。TCP文件同理。

  • 5
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值