Linux基础(六) thrift
1.RPC概念
RPC(Remote Procedure Call),远程过程调用,是一个分布式系统间通信的技术。最核心要解决的问题是,如何调用执行另一个地址空间上的函数、方法,就感觉如同在本地调用一样。这个是什么意思的呢?假设有两台主机host A和host B,host B中有一个函数,比如add()函数,那么host A调用host B的add()的过程,就叫做RPC。
那么针对RPC通过上图可以看到,在整个RPC通信过程中,需要考虑的主要问题有以下两点
- 序列化和反序列化,在请求端需要做到序列化将对象转换为二进制,在服务端需要做到反序列化将收到的二进制转化为对象。当然这边还需要涉及到一定的协议结构,这些觉得都是为了保证请求端和服务端能正确的处理发送相关调用信息;
- 传输的问题,针对RPC来说,需要确保通信的可靠,所以一般来说通信是建立在TCP之上的。
2.RP框架—thrift
Thrift是一个融合了序列化 +RPC的跨语言的RPC框架,最初由Facebook于2007年开发,2008年进入Apache开源项目。Thrift通过一个中间语言(IDL, 接口定义语言)来定义RPC的接口和数据类型,然后通过一个编译器生成不同语言的代码(目前支持C++,Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk和OCaml等),并由生成的代码负责RPC协议层和传输层的实,RPC是C-S模式的。
thrift使用步骤:
- Download Apache Thrift
- Build and Install the Apache Thrift compiler and libraries
- Writing a .thrift file
After the Thrift compiler is installed you will need to create a .thrift file. This file is an interface definition made up of thrift types and Services. The services you define in this file are implemented by the server and are called by any clients. - Generate Thrift file to source code
The Thrift compiler is used to generate your Thrift file into source code which is used by the different client libraries and the server you write. To generate the source from a Thrift file run
thrift –gen
To recursivly generate source code from a Thrift file and all other Thrift files included by it, run
thrift -r --gen <language> <Thrift filename>
具体Tutorial见:https://thrift.apache.org/tutorial/
2.1 match_service(服务端)C++实现:
2.1.1 在thrift文件夹中创建match.thrift
namespace cpp match_service
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),
}
2.1.2 在match_system文件夹中执行
thrift -r --gen cpp ../../thrift/match.thrift
生成文件,将生成的gen-cpp文件夹改名为match_server,将文件夹中的Match_server.skeleton.cpp 拿出来作为main.cpp进行编译:
g++ -c main.cpp match_server/*.cpp
g++ *.o -o main -lthrift
2.2 match_client(客户端)pyhton实现
2.2.1 在game/src文件夹中执行
thrift -r --gen py ../../thrift/match.thrift
将生成的gen-py文件夹改名为match_client
2.2.2 在src文件夹中建立文件client.py
from match_client.match import Match
from match_client.match.ttypes import User
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('127.0.0.1', 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()
打开服务端(在服务端输入 ./main)
再运行python3 client.py
即可完成从输入读入user信息
2.3 save_client实现
2.3.1 在thrift文件夹中创建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)
}
2.3.2 在match_system文件夹中执行
thrift -r --gen cpp ../../thrift/save.thrift
将生成的文件夹改名为save_client
并删除生成文件夹中的Save_server.skeleton.cpp(避免出现两个main函数)
编辑main.cpp,实现保存信息与用户匹配功能
#include "match_server/Match.h"
#include "save_client/Save.h"
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/concurrency/ThreadFactory.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/transport/TSocket.h>
#include <thrift/TToString.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> //条件变量,对锁mutex进行封装
#include <queue>
#include <vector>
#include <unistd.h>
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 ::save_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) //存储数据函数(在thrift官网复制下来然后改改)
{
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();
int res = client.save_data("acs_746", "a677dcd3", a, b);
if (!res) puts("success");
else puts("failed");
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}
bool check_match(uint32_t i, uint32_t j) //判断a,b是否匹配
{
auto a = users[i], b = users[j];
int dt = abs(a.score - b.score); //分差
int a_max_dif = wt[i] * 50;
int b_max_dif = wt[j] * 50;
return dt <= a_max_dif && dt <= b_max_dif;
}
void match() //匹配函数
{
for (uint32_t i = 0; i < wt.size(); i ++ )
wt[i] ++ ; // 等待秒数 + 1
while (users.size() > 1)
{
bool flag = true;
for (uint32_t i = 0; i < users.size(); i ++ )
{
for (uint32_t j = i + 1; j < users.size(); j ++ )
{
if (check_match(i, j)) //判断a,b是否匹配
{
auto a = users[i], b = users[j];
users.erase(users.begin() + j);
users.erase(users.begin() + i);
wt.erase(wt.begin() + j);
wt.erase(wt.begin() + i);
save_result(a.id, b.id);
flag = false;
break;
}
}
if (!flag) break;
}
if (flag) break;
}
}
void add(User user) //添加函数
{
users.push_back(user);
wt.push_back(0);
}
void remove(User user) //删除函数
{
for (uint32_t i = 0; i < users.size(); i ++ )
if (users[i].id == user.id)
{
users.erase(users.begin() + i);
wt.erase(wt.begin() + i);
break;
}
}
private:
vector<User> users;
vector<int> wt; // 等待时间, 单位:s
}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(); //唤醒条件变量
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(); //唤醒条件变量
return 0;
}
};
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;
}
};
void consume_task() //生产者-消费者模型
{
while (true) //一个死循环,查看有没有玩家匹配到一起
{
unique_lock<mutex> lck(message_queue.m);
if (message_queue.q.empty()) //如果队列为空(游戏刚开始时队列为空
{
// message_queue.cv.wait(lck); //等待唤醒条件变量,唤醒后才会继续执行
lck.unlock();
pool.match(); //匹配
sleep(1); //休息一秒,实现每一秒匹配一次
}
else
{
auto task = message_queue.q.front();
message_queue.q.pop();
lck.unlock();
if (task.type == "add") pool.add(task.user);
else if (task.type == "remove") pool.remove(task.user);
}
}
}
int main(int argc, char **argv) {
TThreadedServer server(
std::make_shared<MatchProcessorFactory>(std::make_shared<MatchCloneFactory>()),
std::make_shared<TServerSocket>(9090), //port
std::make_shared<TBufferedTransportFactory>(),
std::make_shared<TBinaryProtocolFactory>());
cout << "Start Match Server" << endl;
thread matching_thread(consume_task); //开启多线程
server.serve();
return 0;
}
2.4 编译文件,运行
g++ -c main.cpp match_server/*.cpp save_client/*.cpp
g++ *.o -o main -lthrift -pthread
运行查看效果: