网络编程学习笔记-1
国庆期间,我在B站游戏开发区知名UP主Voidmatrix的指导下对如何制作联机游戏进行了初步的学习,学习了服务器程序是如何与客户程序进行沟通,初步使用了SDL_net的TCP协议,此篇文章对学习、探索过程的按照时间顺序进行回顾。
第一步-研究哈基米大冒险
在此之前,我只有些许计算机网络的理论知识,对于网络编程基本上一无所知,所以第一步先是研究V佬的最新的联机游戏教程-哈基米大冒险的源码,哈基米大冒险的玩法和金山打字的生死时速相同,我没有去看源码中和玩法实现相关的内容,优先去找实现联机功能的部分, 要实现联机的功能,就要让客户端之间用相同的数据去渲染画面,在服务器程序源码便可以找到这些数据
接着我便开始寻找程序是怎样同步数据,在速览客户端和服务器的源码后,确定了这两段代码就是实现数据同步的部分。
服务器:
在服务器程序中定义了客户端可以发送的请求,在接收到客户端的请求后可以解析客户端请求的信息,然后响应客户端给客户端提供信息(我感觉就像定义了几个方法,然后客户端那边一调用就可以返回数据)
客户端:
客户端这边运行的过程总结一下:
- 客户端程序先会向服务器程序发送登陆请求,服务器返回玩家ID,若返回值不为-1则说明成功加入了游戏
- 发送请求获取文本的请求,获取打字游戏的文本
- 创建一个新线程,每隔一段时间发送一次同步请求,上传自己的游戏进度,并获取另外一个玩家的游戏进度。
第二步-制作联机版井字棋
在研究完最重要的实现数据同步的方法后,紧接着便开始着手实践,先从最简单的内容开始做起,联机版井字棋。
先从差异开始进行分析,联机版井字棋和哈基米大冒险有一个区别,哈基米大冒险客户端只会修改自己的进度数据,这个进度数据不会对另外一个客户端的操作产生影响,因此进度数据只需要同步没有什么限制,而井字棋不同,已经落子的地方不能再次落子,并且落子是回合制的,一人落一次子,不能在对方的回合落子。
理解了以上差异后便可以开始编写代码了:
服务器:
定义了登陆、更新、落子三个请求,登陆请求会返回给玩家它的ID,更新请求会返回游戏数据 *(一个一维数组,共存储10个数字,前九个数字是棋盘信息,0为空,1、2为玩家落子点,第十个数字是游戏阶段,当为1、2时对应玩家1、2胜利)*,在V佬的提示下,使用了JSON文件进行传输,落子请求会接收一个2位数数字,十位数是玩家ID,个位数代表落子位置,服务器会存储当前落子的玩家ID,以及整个棋盘,若玩家ID正确,且落子位置合法,便会更新棋盘并且切换玩家ID。客户端:
客户端这边和哈基米大冒险区别不算特别大,都是登陆、新建线程更新,主要的区别是玩家点击棋盘时会发送落子请求,携带落子位置的信息 *(不管点击的位置是不是有棋子,是不是当前落子的玩家都会发送,由服务器来处理)*。
在完成了联机版井字棋,兴冲冲的测试了几波,感觉十分良好,结果被抓出了没做平局检测(
第三步-制作联机版提瓦特幸存者
这部分内容因为最后制作的不算很好,我就大概讲一下制作的思路吧。 (动画播放最刚开始没有想好所以没有同步,以及不知道什么地方写的有问题导致游戏操作时常会有特别大的延迟)
- 初步思路: 我最刚开始想过服务器仅储存敌人、玩家、子弹的位置信息,然后像什么移动、击杀敌人之类的逻辑全丢在客户端当中去实现,和哈基米大冒险的设计基本一致,大概就是两个原版的提瓦特幸存者,中间用个服务器来同步数据,但是这样的设计会导致很多问题,在哈基米大冒险中不会出现数据的冲突,玩家一只会修改进度一,玩家二只会修改进度二,但是提瓦特幸存者中不一样,可能出现以下情况,玩家一被一个敌人击杀,而玩家二在这之前已经把那个敌人击杀,但是还未来得及更新服务器的数据,因此我抛弃了这样的设计。
- 最终思路: 客户端只负责向服务器发送携带玩家当前移动的方向信息的移动请求,子弹、玩家、敌人的位置更新、碰撞全部由服务器来进行处理,客户端还要时刻向服务器发送更新请求,服务器响应时,使用JSON文件将所有敌人、子弹、玩家的位置信息提供给客户端。
而后客户端再通过位置信息进行渲染 (此处还应该携带一个提供时间信息的数据,客户端根据该时间进行动画渲染)
第四步-SDL_net初步学习
这一步做了个很简单东西,客户端不断的向服务器发送时间戳,服务器则在每次接收到客户端发送的数据后向所有客户端发送一个接收信息的计数。
更加重要的是学习了使用TCP协议传输数据和Select模型。
关于使用TCP协议进行数据传输有哪些需要注意的部分,我也写了篇博客:使用TCP进行数据传输需要注意的问题集
在这里大致介绍一下Select模型:
和HTTP协议每次完成请求、响应后就会断开连接不同,TCP协议的连接会持续存在,直到释放 (实际上HTTP的步骤就是先建立个TCP连接完成请求、响应后释放TCP连接)
最开始我做了个循环,先接收所有客户端的数据,再向所有客户端发送数据,但是接收信息的过程实际上是会阻塞直到连接中有可接收的数据,因此我想到了为每个客户端都开一个线程,用于接收信息,这样就避免了阻塞对程序的影响。
然而为每个客户端单独开一个线程其实会造成很多浪费,每个线程大多数时间都在等待数据,因此诞生许多IO多路复用技术,Select模型就是其中一个。
在Select模型中,每个线程可以处理多个连接,当某个连接中有可用数据时,才会进行数据接收,这样就既避免了阻塞,又可以一个线程处理多个连接。
文章内容就这些了,十分感谢V佬的指导,我会珍惜这难得的机会努力学习技术的(ง •_•)ง
如果有小伙伴需要的话,源码位置:https://github.com/FlyingfishFantasticfan/net_project