文章目录
- 项目地址
- 前言
- 操作步骤
- src
- 跟着教程写报错了
- 格式化文件
- 修改具体业务代码
- cpp 编译
- 链接
- 持久化代码
- 游戏服务器
- 存档
- 如果打开了一些文件,但是我们只需要更新上传部分文件
- 生产者消费者模型
- 思考
- 挂起 tmux 和返回之前挂起的 tmux
- 项目的结构
- 配对服务接口
- 保存配对信息服务接口
- 创建 match_server 的逻辑
- 重命名 cpp 文件
- 删除主函数
- 生成 save_client
- 删除 main 函数
- 在 match_system/src 里面创建 main.cpp 函数
- 编译所有的 cpp 文件
- 链接 .o 文件,生成二进制文件(可执行文件)
- 现在 match_sever 所在的服务器的内容全部实现完了
- `game/src`路径下面生成 `match_client`
- 目前卡住了
- 重新开始写
- 根据接口文件生成一个 `cpp` 文件
- 编译 c++ 文件
- 链接
- 利用 thrift 生成 py 文件
- 成功使用 python 调用 c++(1.0)
- 多线程
- c++ 多线程头文件
- 生产者消费者模型
- 实现消息队列
- 锁
- 条件变量
- 多线程听蒙了
- 匹配池
- 报错
- 线程动态链接库
- 服务端把匹配池里面前两名用户配对(2.0)
- 把代码仓库的 save.thrift 复制到自己的本地服务器上
- 求密码的 md5sum
- save_client
- 删除 save_client 文件夹下的服务端文件
- 修改 main.cpp
- 修改代码格式
- 修改 ip 地址
- 编译所有 c++ 文件
- 链接所有文件
- 存到三冬四夏服务器 1
- 匹配系统 3.0
- 在 vim 里面剪切
- 分数差距在 50 分以内的用户匹配
- sleep
- 排序
- 遍历整个区间
- 删除 vim 里面的行号
- 标记
- 匹配系统 4.0
- 多线程服务器
- 把一些变量全部换成另一个变量
- 升级
- 匹配系统 5.0
- 教程的最后一次升级
- 提升代码能力
- 报错
- 匹配系统 6.0
- 总结
项目地址
前言
这个工具是用来传递服务器之间的请求,返回一定的结果,是 rpc
框架,可以理解为远程函数调用
操作步骤
创建一些 thrift
格式的文件,thrift
可以看成是一些接口
src
表示源文件
跟着教程写报错了
syntax error [FAILURE:/home/acs/thrift_lesson/thrift/match.thrift:11] Parser error during include pass.
,谷歌搜了一下,是因为文件写错了,自己是跟着视频写的,很有可能哪里抄的时候抄错了,我直接复制了一份代码,然后重新使用这个命令,就没有问题了
thrift -r --gen cpp ../thrift/match.thrift
后面的路径表示的是匹配的接口文件
格式化文件
gg=G
修改具体业务代码
把 match_server/Match_server.skeleton.cpp
这个文件重命名,主要是方便自己看,这个自动生成的名字太长了,教程主要实现的是,游戏和匹配机制之间的接口,包括用户 id
,用户名,分数,三个参数,做的第一个修改是在一些函数后面添加 return 0
让函数可以编译通过
cpp 编译
第一步编译,第二步链接,貌似一些东西联系起来了,我之前想做的基于 c++,flex,bison
的编译器,就是这个流程,用 flex
语法分析,用 bison
语义分析,然后 llvm
汇编,最后链接
在终端里面是这个命令,-c
后面都是文件的路径,要是只需要编译一个文件的话,把文件的名字写在 -c
后面就行
g++ -c main.cpp match_server/*.cpp
链接
g++ *.o -o main -lthrift
编译链接执行,非常丝滑,和之前的 add commit push
一样,都是连招
持久化代码
把编译文件和可执行文件都删除,然后把源文件 push
到代码仓库里面
如果使用 tmux
打开多个 pane
,但是 main.cpp
这个文件打开了没有关闭,上传到代码仓库的时候,这个表示被打开的状态的文件也会传过来,后缀是 swp
,我不需要这个文件,只需要源代码,所以关掉这个文件,再持久化一遍
现在点击 F5
刷新网页,要是自己面对一个重要的事情,比如说查成绩,紧张激动,然后查出来的结果不如人意,悔恨的时候就会想起来被自己虚度的无数美好时光,所以波士顿凯尔特人队你了解多少?队训知道吗?
“which hurts more,the pain of hard work or the pain of regret?"
ps 这个网站的文本居然复制不了…
游戏服务器
在游戏服务器里面创建一个 src
文件夹,游戏服务器也是一个文件夹模拟的,生成一份 python
代码
thrift -r --gen py ../../thrift/match.thrift
存档
幸好之前有笔记的存档,现在想要运行游戏的代码,需要把前面服务器的代码一起跑起来才可以运行
笔者在右边的 pane
里面执行一次游戏代码,左边的 pane
里面就会出现一个请求,表示的是,游戏服务器向匹配服务器发送一个请求,表示成功用 python
代码调用了 c++
代码
修改一些代码,使得这个代码更加具有一般性,可以新增和删除一些用户,和前面一样,用 python
代码调用 c++
代码
如果打开了一些文件,但是我们只需要更新上传部分文件
前面说,我们只需要把源代码上传到云端,编译文件和可执行文件都不需要上传到云端,前面说的是 restore --stage
把我们不需要的文件删除,但是我们还可以 git add client.py
,也就是只把我们需要的文件添加到暂存区,然后再 commit
,这样省事不少
生产者消费者模型
这个是在匹配的服务器里面的 c++
文件里面写,
有点喜欢报错,这个没啥办法
思考
完全跟不上教程,太难了,学的有点痛苦,想起了高中学不会的物理化学,其实,说实话,我高中好好花时间学,物理考一个 80
分,化学赋分之后考一个 86
分,应该完全没有问题的,那我就完全可以考一个接近 600
的分数,但是感觉也不是很大的一个优势,无非就是去一个 211
呗
感觉心法就是,花时间和它拼了,不要着急,慢慢把其中的逻辑关系理解清楚,关键不是在于速度,而是把这里面的逻辑关系梳理清楚,然后勤加练习,浙大翁恺说,计算机里面没有黑魔法,总有一天我一定可以把这里面每一行代码都搞清楚
老师讲其实主要还是看自己的毅力,这个本质上没啥难度,需要我们自己反复实现两三遍把这个东西熟悉下来,才算可以,学两三遍一般就可以搞清楚了
挂起 tmux 和返回之前挂起的 tmux
ctrl + a + d
表示挂起,tmux a
返回之前的 tmux
项目的结构
有一个说明文件,一个游戏文件夹(这个是发送配对请求的客户端),一个配对系统文件夹(这个是提供配对的服务端,发送保存请求的客户端,保存的服务端有一个接口,所以本地没有这个文件夹),一个 thrift
文件夹(里面是两个接口,或者可以看成是两条连接服务器的边,把不同的服务器连接起来,一个服务器请求服务,另一个服务器提供服务),src
表示的是源文件
配对服务接口
struct User {
1: i32 id,
2: string name,
3: i32 score
}
service Match {
/**
* user: 添加的用户信息
* info: 附加信息
* 在匹配池中添加一个名用户
*/
i32 add_user(1: User user, 2: string info),
/**
* user: 删除的用户信息
* info: 附加信息
* 从匹配池中删除一名用户
*/
i32 remove_user(1: User user, 2: string info),
}
i32
表示的是 int
,定义了一个结构体,语法有点奇怪,配对服务里面有两个函数,一个是新增一个用户,另一个是删除一个用户
保存配对信息服务接口
namespace cpp save_service
service Save {
/**
* username: myserver的名称
* password: myserver的密码的md5sum的前8位
* 用户名密码验证成功会返回0,验证失败会返回1
* 验证成功后,结果会被保存到myserver:homework/lesson_6/result.txt中
*/
i32 save_data(1: string username, 2: string password, 3: i32 player1_id, 4: i32 player2_id)
}
这个接口是把配对池里面配对成功的两个用户的用户编号传输到云服务器,namespace cpp save_service
表示将自动生成的头文件的命名空间设置为save_service
创建 match_server 的逻辑
thrift -r --gen cpp ../../thrift/match.thrift
创建好之后生成了一个新的cpp
文件
重命名 cpp 文件
把生成的 cpp
文件重命名为 match_server
删除主函数
进入到这个 match_server
里面,把 Match_server.skeleton.cpp
删除
生成 save_client
thrift -r --gen cpp ../../thrift/save.thrift
删除 main 函数
cpp
项目里面只能同时有一个 main
函数,所以前面 match_server
和这里的 save_client
文件里面的 main
函数都需要删除
在 match_system/src 里面创建 main.cpp 函数
(代码暂时不是自己复现的,是复制的,之后有时间,自己会对着教程复现一遍的,这个绝不偷懒,这个不复现完,不会继续往后面学新的内容),把文件 65
行的个人信息修改为自己的信息,md5sum
可以对自己的信息进行加密
编译所有的 cpp 文件
g++ -c main.cpp match_server/*.cpp save_client/*.cpp
链接 .o 文件,生成二进制文件(可执行文件)
g++ *.o -o main -lthrift -pthread
,main
表示的是可执行文件的文件名,-lthrift
表示调用编译好的 thrift
文件,-pthread
表示多线程
现在 match_sever 所在的服务器的内容全部实现完了
match_server
和 save_client
里面的所有内容都实现完了,然后执行 ./main
把这个程序运行起来
game/src
路径下面生成 match_client
thrift -r --gen py ../../thrift/match.thrift
目前卡住了
之前看教程的时候出现了一些问题,主要是没有跟着把代码敲一遍,做作业的时候,跟着教程,出现了一些错误,复制代码,把自己的账号信息, ip
地址加上去,还是无法通过这次作业,但是感觉这个完全不算一件难事,我一定可以把它做好的,我直接从第一个教程开始看算了,带着现在的理解过去看应该会好一些
雄关漫道真如铁,而今迈步从头越
就当是对自己心性的一个磨练,没啥大不了的
重新开始写
这个算是一个小项目,所以自己可以把这个项目包装一下,以后写到自己的简历里面。涉及多种语言(c++ 和 python),多线程,线程之间加锁,c++
的多文件编译,thrift
可以用来实现重启,恢复出厂设置,匹配,代码的评测,服务器端用 c++
来实现,效率会比较高。
thrift
可以理解为远程函数调用,之前不太懂是啥意思,现在基本把这个项目流程过了一遍之后懂了,就是比如有两个服务器,一个服务器调用另一个服务器的函数,这两个服务器可能彼此之间相隔很远,但是可以调用对方的函数,来给自己服务器提供一定的服务
所以 thrift
主要有三个步骤,第一步定义函数接口,第二步创建客户端,第三步创建服务端
在创建文件夹的时候,文件夹和文件的名字尽量避免减号,因为 python
里 import
的时候 -
会被识别成减号
游戏端和匹配端都是在 acserver
上面实现的 , 数据存储在 myserver
上面实现的,项目地址在 这儿_匹配系统
现在数据暂时没有存到数据库,只是存到文件里面,作为模拟
第二遍看,非常清楚,在这儿夸夸自己,其实这个并没有太难,或者说自己并没有自己想的那么笨,我们需要实现的是两条有向边,第一条有向边是游戏端(客户端)和匹配端(服务端),第二条有向边是请求存储端(客户端)和存储端(服务端,这个部分已经实现好了,我们只需要调用这个接口即可)
根据接口文件生成一个 cpp
文件
thrift -r --gen cpp ../../thrift/match.thrift
编译 c++ 文件
g++ -c main.cpp match_server/*.cpp
链接
链接的时候需要加上 thrift
的动态链接库 ,main
表示的是自定义的一个可执行文件的名字
g++ *.o -o main -lthrift
利用 thrift 生成 py 文件
thrift -r --gen py ../../thrift/match.thrift
成功使用 python 调用 c++(1.0)
多线程
我们在通过客户端向服务端新增用户,删除用户的时候,服务端需要有一个进程,在不断的做匹配操作,相当于一个并行的操作,不断地读取新的用户,不断地匹配玩家
c++ 多线程头文件
#include<thread>
生产者消费者模型
消费者就是不断地消耗我们的用户,两者之间用消息队列来通信
实现消息队列
#include<queue>
锁
mutex
,锁这个概念比较好理解,就是类似于一个东西的所有权,只要有一个人把一个东西上锁之后,其他人就无法使用这个上锁之后的东西,这个概念笔者在之前复习数据库事务分析的时候接触过,很早之前学习操作系统的时候也有所接触
#include<mutex>
条件变量
#include<condition_variable>
多线程听蒙了
云里雾里,没搞清楚里面代码的所有逻辑是啥,感觉主要是一些东西自己之前不知道,然后不知道的东西太多了,还要把它们串在一起,所以比较难
处理一些共享的变量的时候,执行结束之后一定要及时解锁,因为不解锁的话,会影响其他函数对这个变量的使用
匹配池
维护所有的玩家信息,面对对象的程序设计(搜了一下,其实主要就是封装)
报错
自己偷懒看了一下午电影,唐人街探案确实是经典,自己确实比较喜欢看现代的喜剧,悬疑犯罪动作都喜欢看
线程动态链接库
-pthread
服务端把匹配池里面前两名用户配对(2.0)
演示地址,主要是使用了多线程,条件变量,消息队列,锁,面向对象程序设计等相关的知识
把代码仓库的 save.thrift 复制到自己的本地服务器上
仓库地址,这个部分开始做,把匹配的信息,从一个服务器客户端传到服务器服务端的功能
求密码的 md5sum
这个算是一个加密,输入 md5sum
,按下回车,输入八位数密码,输入 ctrl+d
,然后得到的字符串的前面八位就是我们要求的字符串
save_client
acs@7eb6e095838c:~/match_system/matching_pool/src$ thrift -r --gen cpp ../../thrift/save.thrift
删除 save_client 文件夹下的服务端文件
Save_server.skeleton.cpp
修改 main.cpp
打开 thrift
官网,对着教程,client ,新增一些头文件,因为目前 main.cpp
里面是服务端的代码,匹配池这个节点提供匹配服务,请求存储服务,所以还需要一个客户端代码
新增头文件,把命名空间也添加进来(命名空间和 save.thrift
保持一致),把官网主函数的代码复制到 save_result
函数里面
修改代码格式
粘贴的时候,进入粘贴模式开始粘贴,粘贴完成之后,输入 gg=G
把代码格式化,格式化之后出现很多 --->---->
,输入 :set nopaste
退出粘贴模式,gg=G
再次格式化即可
修改 ip 地址
修改 game/src/client.py
这个文件,把 localhost
改成 127.0.0.1
,表示本地的地址
修改 main.cpp
,把 localhost
改成自己的服务器的地址 123.57.67.128
,这个服务器笔者将它命名为三冬四夏服务器 1
编译所有 c++ 文件
acs@7eb6e095838c:~/match_system/matching_pool/src$ g++ -c main.cpp match_server/*.cpp save_client/*.cpp
链接所有文件
g++ *.o -o main -lthrift -pthread
存到三冬四夏服务器 1
验证成功后,结果会被保存到myserver:homework/lesson_6/result.txt中
报错了,我正常操作,发现服务器上面完全没有这个文件,新建这个目录文件之后执行也正常,但是文本文件里面没有任何信息
仔细检查之后发现,是因为 md5sum
输入的时候多输入了一位数字,只需要八位数字,我输入了九位
匹配系统 3.0
在 vim 里面剪切
鼠标选中 + dd
剪切,p
粘贴
分数差距在 50 分以内的用户匹配
匹配系统 3.0
实现的是,把匹配池中前两个玩家匹配,意思是,只要进去两个玩家就把这两个玩家匹配,匹配池里面不会超过两名玩家,一旦达到两名玩家就会匹配成功并把这两位玩家从匹配池中删除
假设我们玩过王者荣耀或者绝地求生,我们就会知道,匹配机制会把我们匹配到和我们实力接近的玩家,这样我们才能有更好的游戏体验,假设我是青铜水平,把我和一个最强王者匹配到了一块了,我连队友说的补充状态是啥意思都不明白,对手是把伤害小数点后面的伤害都研究透彻的老鸟,我的胜算就非常小了
所以按照分数来进行匹配,这个非常关键
sleep
sleep(1);
表示休眠一秒,在这个头文件里#include<unistd.h>
,实现一秒钟匹配一次的效果
排序
把所有玩家按照分数进行排序,每一次都是匹配分差在 50
以内的两个玩家
sort(users.begin(),users.end(),[&](User& a,User& b){return a.score<b.score;});
遍历整个区间
//然后是从前往后遍历这个区间
for(uint_t i=1;i<users.size();i++)
{//uint32_t 表示无符号整数
auto a=users[i-1],b=users[i];
if(b.score-a.score<=50)
{//删除这两个用户,删除前一个用户之后,该用户的后一个用户的排序往前移动一位
//比如说删除第一个用户,第二个用户成了新的第一个用户
users.erase(users.begin()+i-1,users.begin()+i+1);
save_result(a.id,b.id);
break;
}
}
删除 vim 里面的行号
复制里面的代码的时候不想要行号,:set nonu
,nu
就是number
的意思应该,显示行号是 :set nu
标记
做一个标记,假设遍历整个匹配池,没有玩家可以匹配上就直接退出循环就好
bool flag=true;
//然后是从前往后遍历这个区间
for(uint32_t i=1;i<users.size();i++)
{//uint_t 表示无符号整数
auto a=users[i-1],b=users[i];
if(b.score-a.score<=50)
{//删除这两个用户,删除前一个用户之后,该用户的后一个用户的排序往前移动一位
//比如说删除第一个用户,第二个用户成了新的第一个用户
users.erase(users.begin()+i-1,users.begin()+i+1);
save_result(a.id,b.id);
flag=false;
break;
}
}
if(flag==true)
break;
匹配系统 4.0
匹配系统 4.0:把分差在 50
分以内的玩家匹配到一块儿
多线程服务器
把官网教程里面的代码复制到自己的 main.cpp
文件里面,把一些变量的名字修改一下
把一些变量全部换成另一个变量
:1,$s/word1/word2/g
,word1 word2
是自己可以修改的地方
升级
进行了上面的这些操作之后,就把单线程处理改成了多线程处理,有一个比较奇怪的地方,笔者基本按照教程写的,但是,教程编译报错了,我没有报错,可能是因为笔者把全部头文件加在了 main.cpp
前面,头文件重复应该没有啥影响,只要不缺失
不太行,链接的时候满屏的错误,还是那句话,没有 bug
的代码是不完美的,所以笔者现在去 debug
一下
搜了一下,应该是会产生一些影响的,所以我还是把头文件那里改一下
还是有问题,现在笔者准备再检查一次头文件,编译没有问题,链接的时候出现了一些问题,检查了一下,这些头文件都没有问题,重新手敲了一遍编译链接命令,没有任何问题了现在,难道之前命令输错了??
哦哦,编译的时候 main
这个名字忘记加上去了
匹配系统 5.0
单线程适合简单的顺序执行任务,适用于简单的应用或需要保证操作顺序性的场景;而多线程则适合需要提高并发性能和利用多核处理器优势的复杂应用,但需要注意线程间的同步与通信问题
匹配系统 5.0:把单线程处理变成多线程处理
教程的最后一次升级
下面是教程的最后一次升级,等待的时间越久,能匹配的范围越大,我们打游戏的时候可能遇到过这样的情况,就是我们段位比较高,和我们段位一样的玩家比较少,我们可能想打一局排位赛,但是迟迟匹配不到对手,我们可以设置一个函数,使得我们匹配的时间越久,我们可以匹配的范围越大,比如之前,匹配系统 4.0
设置的分数差距是 50
分,等到我们匹配了两秒之后,只要这个差距是 100
的就能匹配上,匹配三秒的时候,只要这个差距是 150
我们就能匹配上,相当于每秒钟匹配一次,每秒钟把这个范围扩大一次
自己之后实力变强之后,可以尝试自己把这个再升级升级
提升代码能力
盲打感觉真的非常重要,自己可以用一个非常舒适的方式操作电脑,真的得找个时间练习一下盲打,要是自己可以盲打,感觉自己的代码开发效率会提升很大,敲键盘就会变成艺术,就像是那些很厉害的人做算法题,或者钢琴家弹钢琴,他们对于自己专精的这个东西掌握的非常熟练,辛苦拼搏都不会觉得疲劳
笔者坐姿推胸器能 75kg
推一个,瞬间感觉杠铃卧推不香了,是,我们都倾向于去做自己擅长的事情
报错
编译的时候报错了,没事,没有 bug
的代码是不完美的!
原来是字母敲错了,主要是因为之前看到过 unity
这个单词,但是这里是 uint32_t
表示的是 32
位无符号整数
但是为啥卡住了呢又?没啥影响,现在可以了
匹配系统 6.0
匹配系统 6.0:匹配范围随时间推移增大
总结
前后感觉学了好几天,中断了一下其实,但是总的花的时间也挺多的,有些东西躲不开的,之前没有处理好,现在也要处理,第一遍看教程没有好好操作,还要看第二遍教程,自己独自面对一大堆报错的时候,一定要顶住,就像自己卧推或者跑步的时候,很有可能就是自己一个人,面对,能不能撑住,决定了能不能把那个杠铃推起来,能不能跑完这一公里