1、该系列为ACWing中Linux基础课,已购买正版,课程作者为yxc
2、y总培训真的是业界良心,大家有时间可以报一下
3、为啥写在这儿,问就是oneNote的内存不够了QAQ
thrift教程
概述
写一个游戏/网站,并运行在一台或多台服务器上,此时每个同学会选择匹配对手,此时需要匹配服务(实现匹配池)。玩家点开匹配则向匹配系统发出请求或调用函数(例如add_user,表示在系统中添加一名玩家,remove_user匹配池中取消一名玩家)。匹配池负责不断添加玩家,删除玩家,发现分数相近的玩家将匹配结果发送给另外一个服务器(如数据存储服务器)。thrift是里面的有向边,想调用服务器的某个函数可以用thrift实现,负责传送数据,接收结果。
每一个节点是不同的进程,不同的进程可以用不同的语言来写。
thrift又被称为RPC框架(远程函数调用)。实质是操作系统的远程进程调用,将对方机器的进程通过协议进行调用,对方机器会判定这个调用是不是合法的,比如这个用户是不是我这里的注册用户。
写接口是为了使用thrift命令生成服务端中接口的实现函数,客户端通过client调用服务端的函数即可。
使用场景
1、定义接口
2、服务端生成server;
3、请求端用client请求服务;
实现
上课代码地址:acwing_git_thrift_lesson
1、实现游戏节点(server和client端)
2、实现匹配池(client端)
3、数据存储(已实现)
步骤
1、mkdir match_system game thrift
创建匹配系统、游戏端、存放各种thrift接口
2、定义添加玩家和删除玩家的接口;
3、在thrift
文件夹下acs@61f2b97ca5ed:~/thrift_lesson/thrift$ vim match.thrift
,内容如下:
- 定义命名空间namespace;
- 定义结构体struct,传输用户信息;
- 定义函数service,里面定义了两个接口,返回值是i32(int);
namespace cpp match_service # namespace 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), }
4、用接口实现具体的节点
(1)实现服务端的match_service, 通过接口实现c++版本服务器
- 进入match_service文件夹,创建
src
文件夹存源文件; - 通过
thrift -r --gen cpp ../../thrift/match.thrift
创建c++版本服务器,其中../../thrift/match.thrift
是接口路径,生成gen-cpp
文件夹; mv gen-cpp/ match_server
修改文件名;mv match_server/Match_server.skeleton.cpp main.cpp
;- 修改
main.cpp
接口定义好了,业务逻辑还没有定义好; main.cpp
添加模块并编译g++ -c main.cpp match_server/*.cpp
;- 链接文件
g++ *.o -o main -lthrift
用了thrift动态链接库要加-lthrift
; - 运行文件命令
./main
; - git保存(除.o和可执行文件
main
)
(2)实现客户端的match_service
- game文件夹中创建
src
源文件夹; - 生成python服务器端,命令
thrift -r --gen py ../../thrift/match.thrift
; - 重命名
mv gen-py/ match_client
; - 删除文件
rm Match-remote
,因为只实现客户端; - 添加
/src/client.py
文件,写入python客户端的例子:from match_client.match import Match from tutorial.ttypes import InvalidOperation, Operation, Work from thrift import Thrift from thrift.transport import TSocket from thrift.transport import TTransport from thrift.protocol import TBinaryProtocol from sys import stdin def operate(op, user_id, username, score): # Make socket transport = TSocket.TSocket('localhost', 9090) # Buffering is critical. Raw sockets are very slow transport = TTransport.TBufferedTransport(transport) # Wrap in a protocol protocol = TBinaryProtocol.TBinaryProtocol(transport) # Create a client to use the protocol encoder client = Match.Client(protocol) # Connect! transport.open() user = User(user_id, username, score) if op == "add": client.add_user(user,"") elif op == "remove": client.remove_user(user,"") # Close! transport.close() def main(): for line in stdin: op, user_id, username, score = line.split(' ') operate(op, int(user_id), username, int(score)) if __name__ == "__main__": main()
python3 client.py
运行(同时运行./main
,能够实现用python调用c++的代码);- git保存(除
.pyc
文件git restore --stage *.pyc
)
(2)实现匹配的server端
-
采用并行,在部分线程加入/删除用户的同时,另外一个线程进行匹配,并传给数据存储服务器;
-
修改
main.cpp
,加入线程操作函数(生产者-消费者模型),代码如下; -
生产者和消费者之间通信的媒介可以用:消息队列;
#include "match_server/Match.h" #include <thrift/protocol/TBinaryProtocol.h> #include <thrift/server/TSimpleServer.h> #include <thrift/transport/TServerSocket.h> #include <thrift/transport/TBufferTransports.h> #include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> #include <vector> using namespace ::apache::thrift; using namespace ::apache::thrift::protocol; using namespace ::apache::thrift::transport; using namespace ::apache::thrift::server; using namespace ::match_service; using namespace std; 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; } class MatchHandler : virtual public MatchIf { public: Match Handler() { // 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);//lock上锁 message_queue.q.push({user,"add"}); message_queue.cv.notify_all(); //huan xing 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);//lock message_queue.q.push({user,"remove"}); message_queue.cv.notify_all(); //huan xing return 0; } }; void consumer_task() { while(true) { unique_lock<mutex> lck(message_queue.m); if (message_queue.q.empty()) { message_queue.cv.wait(lck); //zu se } else { auto task = message_queue.q.front(); message_queue.q.pop(); lck.unlock();//for doing task and add/remove users in time // do task if (task.type == "add") pool.add(task.user); else if (task.type == "remove") pool.remove(task.user); pool.match(); } } } int main(int argc, char **argv) { int port = 9090; ::std::shared_ptr<MatchHandler> handler(new MatchHandler()); ::std::shared_ptr<TProcessor> processor(new MatchProcessor(handler)); ::std::shared_ptr<TServerTransport> serverTransport(new TServerSocket( port)); ::std::shared_ptr<TTransportFactory> transportFactory(new TBufferedTra nsportFactory()); ::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtoco lFactory()); TSimpleServer server(processor, serverTransport, transportFactory, pro tocolFactory); cout << "Start Match Server" << endl; thread matching_thread(consumer_task); server.serve(); return 0; }
-
编译时加入线程的动态链接库
g++ *.o -o main -lthrift -pthread
;
(3)实现数据存入的server端
-
username: myserver的名称
-
md5sum
输入myserver的密码(+回车+ctrl+d
)加密后的前8位;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) }
(4)实现数据存入的client端
-
在
match_system/src/
文件夹内生成``,命令thrift -r --gen cpp ../../thrift/save.thrift
; -
mv gen-cpp/ save_client
; -
rm Save_server.skeleton.cpp
; -
将save的client端代码写入项目:进入
main.cpp
,将thrift官方教程手册抄下来,代码中加入:#include "save_client/Save.h" #include <thrift/transport/TSocket.h> #include <thrift/transport/TTransportUtils.h> using namespace ::save_service; class Pool { public: void save_result(int a, int b) { printf("Match Result: %d %d\n", a, b); std::shared_ptr<TTransport> socket(new TSocket("123.57.47.211", 9090)); std::shared_ptr<TTransport> transport(new TBufferedTransport(socket)); std::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport)); SaveClient client(protocol); try { transport->open(); client.save_data("acs_xxxx","xxxxxxxx", a, b); transport->close(); } catch (TException& tx) { cout << "ERROR: " << tx.what() << endl; }
-
myserver修改IP地址,在
client.py
中transport = TSocket.TSocket('127.0.0.1', 9090)
,在main.cpp
中;
(5)升级匹配系统
main.cpp
中修改,按照分差匹配,每1秒钟看一次;#include <unistd.h> 70 void match() 71 { 72 while (users.size()>1) 73 { 74 sort(users.begin(), users.end(), [&](User& a, User b){ 75 return a.score < b.score; 76 }); 77 78 bool flag = true; 79 for (uint32_t i = 1; i < users.size(); i++) 80 { 81 auto a = users[i - 1], b = users[i]; 82 if (b.score - a.score <= 50) 83 { 84 users.erase(users.begin() + i -1, users.begin() + i + 1); 85 save_result(a.id, b.id); 86 87 flag = false; 88 break; 89 90 } 91 } 92 if (flag) break; 93 } 94 } ============================================================= 145 void consumer_task() 146 { 147 while(true) 148 { 149 unique_lock<mutex> lck(message_queue.m); 150 if (message_queue.q.empty()) 151 { 152 //message_queue.cv.wait(lck); //zu se 153 lck.unlock(); 154 pool.match(); 155 sleep(1); 156 } 157 else 158 { 159 auto task = message_queue.q.front(); 160 message_queue.q.pop(); 161 lck.unlock();//for doing task and add/remove users in time 162 163 // do task 164 if (task.type == "add") pool.add(task.user); 165 else if (task.type == "remove") pool.remove(task.user); 166 pool.match(); 167 } 168 } 169 }
(6)升级服务端为多线程
- 修改原有的
main.cpp
文件;#include <thrift/concurrency/ThreadManager.h> #include <thrift/concurrency/ThreadFactory.h> #include <thrift/TToString.h> #include <thrift/server/TThreadPoolServer.h> #include <thrift/server/TThreadedServer.h> 150 class MatchCloneFactory : virtual public MatchIfFactory { 151 public: 152 ~MatchCloneFactory() override = default; 153 MatchIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) override 154 { 155 std::shared_ptr<TSocket> sock = std::dynamic_pointer_cast<TSocket>(connInfo.trans port); 156 cout << "Incoming connection\n"; 157 cout << "\tSocketInfo: " << sock->getSocketInfo() << "\n"; 158 cout << "\tPeerHost: " << sock->getPeerHost() << "\n"; 159 cout << "\tPeerAddress: " << sock->getPeerAddress() << "\n"; 160 cout << "\tPeerPort: " << sock->getPeerPort() << "\n"; 161 return new MatchHandler; 162 } 163 void releaseHandler(MatchIf* handler) override { 164 delete handler; 165 } 166 }; ============================================== 195 int main(int argc, char **argv) { 196 197 TThreadedServer server( 198 std::make_shared<MatchProcessorFactory>(std::make_shared<MatchCloneFactory>()), 199 std::make_shared<TServerSocket>(9090), //port 200 std::make_shared<TBufferedTransportFactory>(), 201 std::make_shared<TBinaryProtocolFactory>()); 202 203 204 cout << "Start Match Server" << endl; 205 206 thread matching_thread(consumer_task); 207 208 server.serve(); 209 return 0; 210 }
**(7)匹配机制优化,**在没有分数相近的用户时,寻找其他分数段的用户
-
增加玩家已经被循环了多少次(等待时间);
75 bool check_match(uint32_t i, uint32_t j) 76 { 77 auto a = users[i], b = users[j]; 78 79 int dt = abs(a.score - b.score); 80 int a_max_dif = wt[i] * 50; 81 int b_max_dif = wt[j] * 50; 82 83 return dt <= a_max_dif && dt <= b_max_dif; 84 } 85 86 void match() 87 { 88 for (uint32_t i=0; i < wt.size(); i ++) 89 wt[i] ++; // waiting time +1 90 while (users.size() > 1) 91 { 92 bool flag = true; 93 for (uint32_t i = 0; i < users.size(); i++) 94 { 95 for (uint32_t j = i + 1; j < users.size(); j++) 96 { 97 if (check_match(i, j)) 98 { 99 auto a = users[i], b = users[j]; 100 users.erase(users.begin() + j); 101 users.erase(users.begin() + i); 102 wt.erase(wt.begin() + j); 103 wt.erase(wt.begin() + i); 104 save_result(a.id, b.id); 105 flag = false; 106 break; 107 } 108 109 } 110 if (!flag) break; 111 } 112 if (flag) break; 113 } 114 } 115 116 void add(User user) 117 { 118 users.push_back(user); 119 wt.push_back(0); 120 } 121 122 void remove(User user) 123 { 124 for (uint32_t i = 0; i<users.size(); i++) 125 if (users[i].id == user.id) 126 { 127 users.erase(users.begin() + i); 128 wt.erase(wt.begin() +i); 129 break; 130 } 131 } 132 private: 133 vector<User> users; 134 vector<int> wt; //waiting time
全部项目已上传至:
https://git.acwing.com/zxy_12138/thrift_lesson/