linux学习记录(thrift) 10.10~10.11~10.12

做一个游戏匹配系统
1、定义接口
2、server
3、cilent
thrift:跨语言的服务部署框架,rpc框架,远程函数调用
任务:
实现游戏节点,匹配节点(两个服务,match_system文件(节点2),game文件(节点1),thrift文件存所有thrift接口)
游戏节点到匹配系统的有向边(实现match_client端、实现match_server端)
匹配节点到数据存储的有向边(实现save_client端,server端已经实现)
接口1:
match.thrift
声明命名空间:
namespace cpp match_service
声明User结构体:
struct User {
1: i32 id,
2: string name,
3: i32 score
}
定义函数:
service Match { i32 add_user(1: User user, 2: string info), i32 remove_user(1: User user, 2: string info), }
服务端1(match_server - main.cpp):
cd match_system
mkdir src
cd src
引用前面写的match.thrift生成c++代码
thrift -r --gen cpp ../../thrift/match.thrift
改个名:mv gen-cpp/ match_server
把生成的文件搬过来并改名main.cpp:mv match_server/Match_server.skeleton.cpp main.cpp
修改main.cpp
先在add_user和remove_user函数里面加return 0,并修改Match.h路径(因为main.cpp是从原来match_server文件夹中mv出来了,现在地址不在match_server里面,而Match.h还仍然在match_server里面),使编译通过
附 c++ 的编译:
(1)编译(gcc -c 其实是预处理-编译-汇编,生成 .o 文件):g++ -c main.cpp match_server/*.cpp = 编译 main.cpp 和 match_server 里面所有的.cpp文件
(2)链接:g++ *.o -o main -lthrift = 将所有 .o 文件链接,变为可执行文件main 然后 -lthrift 是因为需要用到thrift的动态链接库
可以加一行输出 "Start Match server" 验证
git push 到云端(最好不要将 .o 和 可执行文件push上去)
再修改server端:
一方面server端需要读用户进来,一方面server端需要不断进行匹配,再将信息传给另外一个服务器
两个操作完全独立,并行操作
需要给我们的匹配专门单开一个线程
经典的生产者消费者模型:
头文件:
#include<thread>
开线程:
thread matching_thread(consume_task);
生产者:
通过add_user() remove_user(),每次add或者remove,就会产生一个Task任务
消费者:
不停地消耗Task资源
生产者和消费者之间通信媒介:方法如消费队列---锁mutex,PV操作,P(m),V(m),在pv之间处理一些并行问题,实现解决线程之间的互斥访问,这样每次只有一个线程使用资源
条件变量--对锁进行了封装
#include <mutex> //锁 #include <condition_variable> //条件变量
struct Task { User user; //用户 string type; //操作类型 }; struct MessageQueue { queue<Task> q; mutex m; condition_variable cv; }message_queue; //消息队列
// 玩家池 class Pool { public: void save_result(int a,int b) { printf("Match Result: %d %d\n", a, b); } // 匹配函数 void match() { while (users.size() > 1) { auto a = users[0], b = users[1]; users.erase(users.begin()); users.erase(users.begin()); save_result(a.id, b.id); } } // 添加玩家 void add(User user) { users.push_back(user); } // 删除玩家 void remove(User user) { for (uint32_t i = 0; i < users.size(); i ++ ) if (users[i].id == user.id) { users.erase(users.begin() + i); break; } } private: vector<User> users; }pool;
生产者:
class MatchHandler : virtual public MatchIf { public: MatchHandler() { // Your initialization goes here } int32_t add_user(const User& user, const std::string& info) { // Your implementation goes here printf("add_user\n"); unique_lock<mutex> lck(message_queue.m); // 自动解锁 message_queue.q.push({user, "add"}); message_queue.cv.notify_all(); // v释放一个Task资源 return 0; } int32_t remove_user(const User& user, const std::string& info) { // Your implementation goes here printf("remove_user\n"); unique_lock<mutex> lck(message_queue.m); message_queue.q.push({user, "remove"}); message_queue.cv.notify_all(); // v释放一个Task资源 return 0; } };
消费者:
void consume_task() { while (true) { unique_lock<mutex> lck(message_queue.m); // 当前队列里面无Task if (message_queue.q.empty()) { // 如果无Task,希望此进程卡住,等待Task资源到来再执行,降低CPU利用率 message_queue.cv.wait(lck);// p申请资源 } // 队列里有Task else { auto task = message_queue.q.front(); message_queue.q.pop(); lck.unlock(); // 上两行对Task队列的共享操作,处理完就解锁,这里需要解锁是因为本函数死循环,而自动解锁是函数结束时,所以要手动解锁 // 弹出Task后去do Task if (task.type == "add") pool.add(task.user); else if (task.type == "remove") pool.remove(task.user); pool.match(); } } }
客户端1(match_client - client.py):
cd game
mkdir src
cd src
引用前面写的match.thrift生成python代码
thrift -r --gen py ../../thrift/match.thrift
改个名:mv gen-py/ match_client
cd match
看到有个可执行文件Match-remote,这个是如果要用python的服务器端有用,我们是客户端,所以可以删
rm Match-remote
官网有python客户端的例子,直接复制过来 : Apache Thrift - Python
前四行是为了将当前路径加到python的环境变量,我们没有用,删掉
改前两行的from和import内容:
from match_client.match import Match
from match_client.match.ttypes import User
删掉 transport.open() 到 transport.close() 之间的内容 (中间为了写业务代码)
transport.open() 上面 改 client = Match.Client(protocol)
加下面这段,好习惯:
if __name__ == "__main__": main()
加个User试下:
user = User(1, 'tkw', 1500)
client.add_user(user,"")
进 game/src 运行下,先打开match_system/src,运行服务端 ./main
再运行客户端 python3 client.py 成功在py中调用了c++的add_user函数
git add .一下
git restore --staged *.pyc pyc文件最好删掉
改代码,client.py:
python从终端读东西
from sys import stdin
改函数
def operate(op, user_id, username, score):
user = User(user_id, username, score)
if op == "add": client.add_user(user, "") elif op == "remove": client.remove_user(user, "")
def main(): for line in stdin: op, user_id, username, score = line.split(' ') operate(op, int(user_id), username, int(score))
改为从终端输入user信息
client端完成
接口2(save.thrift)
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) }
md5值怎么看?
先md5sum,再输入密码,再 ctrl+d
md5值的前8位 就是 password 要填的信息(就是把原来密码加密了一下)4afbeaaf
客户端2(save_client):
cd 到match_system/src
thrift -r --gen cpp ../../thrift/save.thrift
mv gen-cpp/ save_client 改名
cd save_client
rm Save_server.skeleton.cpp (和服务端相关,我们这个是客户端)
修改main.cpp
加头文件:
#include "save_client/Save.h"
#include <thrift/transport/TTransportUtils.h>
#include <thrift/transport/TSocket.h>
然后将官网Apache Thrift - C++
client中int main里的代码复制到Class Pool的save_result(int a,int b)函数
改为服务器ip
std::shared_ptr<TTransport> socket(new TSocket("123.57.47.211", 9090));
删掉transport->open()到transport->close()之间的内容,加一行
client.save_data("acs_3144", "4afbeaaf", a, b);
编译(gcc -c 其实是预处理-编译-汇编,生成 .o 文件)+链接
g++ -c save_client/*.cpp
g++ -c main.cpp
g++ *.o -o main -lthrift -pthread
升级匹配系统(3.0版本):
现在是每次匹配前两个人
改成每1s匹配一次,如果存在两人分差<50则匹配
sleep函数头文件:
#include <unistd.h>
// 匹配函数,发现两名玩家分值<=50就匹配上 void match() { while (users.size() > 1) { sort(users.begin(), users.end(), [&](User& a, User b){ return a.score < b.score; }); bool flag = true; for (uint32_t i = 1; i < users.size(); i ++ ) { 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) break; // 防止死循环 } }
// 当前队列里面无Task if (message_queue.q.empty()) { // 如果无Task,希望此进程卡住,等待Task资源到来再执行,降低CPU利用率 //message_queue.cv.wait(lck);// p申请资源 //换思路:每一秒钟看下能否匹配 lck.unlock(); match(); sleep(1); }
gcc -c main.cpp
g++ *.o -o main -lthrift -pthread
改多线程:
加头文件
#include <thrift/concurrency/ThreadManager.h> #include <thrift/concurrency/ThreadFactory.h>
#include <thrift/server/TThreadedServer.h>#include <thrift/TToString.h>
删原来单线程定义方式,换下面
TThreadedServer server( std::make_shared<CalculatorProcessorFactory>(std::make_shared<CalculatorCloneFactory>()), std::make_shared<TServerSocket>(9090), //port std::make_shared<TBufferedTransportFactory>(), std::make_shared<TBinaryProtocolFactory>());
抄过来
class CalculatorCloneFactory : virtual public CalculatorIfFactory { public: ~CalculatorCloneFactory() override = default; CalculatorIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) override { std::shared_ptr<TSocket> sock = std::dynamic_pointer_cast<TSocket>(connInfo.transport); cout << "Incoming connection\n"; cout << "\tSocketInfo: " << sock->getSocketInfo() << "\n"; cout << "\tPeerHost: " << sock->getPeerHost() << "\n"; cout << "\tPeerAddress: " << sock->getPeerAddress() << "\n"; cout << "\tPeerPort: " << sock->getPeerPort() << "\n"; return new CalculatorHandler; } void releaseHandler( ::shared::SharedServiceIf* handler) override { delete handler; } };
将所有的Caculator替换成Match
:1,$s/Calculator/Match/g
::shared::SharedService改Match
class MatchCloneFactory : virtual public MatchIfFactory { public: ~MatchCloneFactory() override = default; MatchIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) override { std::shared_ptr<TSocket> sock = std::dynamic_pointer_cast<TSocket>(connInfo.transport); /* cout << "Incoming connection\n"; cout << "\tSocketInfo: " << sock->getSocketInfo() << "\n"; cout << "\tPeerHost: " << sock->getPeerHost() << "\n"; cout << "\tPeerAddress: " << sock->getPeerAddress() << "\n"; cout << "\tPeerPort: " << sock->getPeerPort() << "\n"; */ return new MatchHandler; } void releaseHandler(MatchIf* handler) override { delete handler; } };
生产者变为了多线程
gcc -c main.cpp
g++ *.o -o main -lthrift -pthread
再次升级:
比如两个人已经匹配一小时还没匹配到,凑合一下
动态优先级调度算法
传过来的user分为s
第1s:搜[s-50,s+50]
第2s:搜[s-100,s+100]
第3s:搜[s-150,s+150]
需要记录当前同学等待的秒数和匹配的轮次
注:课后了解一下锁和多线程

浙公网安备 33010602011771号