06 thrift
参考自:Linux基础课。
说明:
-
thrift是RPC(remote procedure call)的一种实现方式,通过网络实现跨机器跨语言的函数调用。
-
目前主流的RPC的实现方式有:
-
(1)Google的
gRPC
; -
(2)Facebook的
thrift
; -
(3)阿里的
Dubbo
。
-
6.1 需求
下面以一个需求为例,讲解一下thrift
的使用方式:
-
我们要实现一个游戏在线匹配系统,有两台服务器:
acs、myserver
。 -
acs
需要实现两个模块:(1)游戏客户端:用于接收用户请求,使用python实现;(2)匹配系统:实现用户匹配 并 将数据持久化到myserver
上。 -
myserver
需要实现一个模块:持久化数据。 -
具体实现关系图如下图:
6.2 实现过程
说明:此项目根目录从thrift_lesson开始
- 首先在
acs
服务器的thrift_lesson
文件夹内创建如下文件夹:
game : 用于存储 游戏客户端代码
match_system : 用于存储 匹配系统代码
thrift : 用于存储 thrift的接口
- 进入
thrift
文件夹,创建match.thrift
文件,内容如下(关于.thrift
文件的编写可以参考:网址):
namespace cpp match_server // 定义命名空间
// 定义存储用户的结构体
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),
}
-
将该文件上传到云端。
-
进入
match_system
文件夹,新建src
文件夹并进入,然后输如下入命令:
thrift -r --gen cpp ../../thrift/match.thrift
- 之后会在
src
文件中生成gen-cpp
文件夹,目录结构如下:
- 将该文件夹重命名为
match_server
,命令如下:
mv gen-cpp/ match_server
- 将
match_server/Match_server.skeleton.cpp
移动到match_system/src
下,并重命名为main.cpp
,命令如下:
mv match_server/Match_server.skeleton.cpp main.cpp
main.cpp
中给我们定义好了接口,逻辑需要我们自己填充,初始内容如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "Match.h"
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
using namespace ::apache::thrift;
using namespace ::apache::thrift::protocol;
using namespace ::apache::thrift::transport;
using namespace ::apache::thrift::server;
using namespace ::match_server;
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");
}
int32_t remove_user(const User& user, const std::string& info) {
// Your implementation goes here
printf("remove_user\n");
}
};
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 TBufferedTransportFactory());
::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
server.serve();
return 0;
}
-
将上述函数
add_user、remove_user
添加return 0
,并将#include "Match.h"
改为#include "match_server/Match.h"
确保可以编译通过。令外在主函数中加入一句输出cout << "Start Match Server..." << endl;
,方便之后查看。 -
使用如下命令编译所有的
.cpp
文件:
g++ -c main.cpp match_server/*.cpp
- 然后将编译得到的
.o
文件进行链接,命令如下:
g++ *.o -o main -lthrift
- 此时生成可执行文件
main
,运行输出刚才我们加的话,说明运行成功,如下图:
-
然后将当前代码存储到云端(注意可执行文件、中间文件不要上传,例如
.o
文件)。 -
此时整个项目目录的结构如下:
- 此时,
acs
上的服务端架构搭建完成。此为匹配系统的1.0
版本,即还没有实现匹配功能。
下面搭建acs
上的客户端架构,使用python
语言。
- 进入
game
文件夹,创建src
文件夹并进入,,然后输如下入命令:
thrift -r --gen py ../../thrift/match.thrift
- 之后会在
src
文件中生成gen-py
文件夹,目录结构如下:
- 将该文件夹重命名为
match_client
,命令如下:
mv gen-py/ match_client
-
上图中
Match-remote
是python
作为服务端的代码,删除即可。 -
进入网址:Python Tutorial,复制客户端代码,并在
game/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
def main():
# 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(1, "wxx", 1500)
client.add_user(user, "")
# Close!
transport.close()
if __name__ == "__main__":
main()
- 此时开启两个终端,一个终端执行服务器,另一个执行
client.py
,可以看到两个进程通信了,如下图:
-
到此为止,我们实现了一个线程的
python
代码调用了另一个线程的c++
代码,此时acs
上的客户端架构搭建完成。 -
然后将当前代码存储到云端(注意不要上传
.pyc
文件)。 -
此时整个项目目录的结构如下:
完善acs
的客户端代码,打开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()
-
至此,
acs
的客户端书写完毕。 -
然后将当前代码存储到云端。
6.3 匹配系统2.0
-
在
6.2
中,我们已经在acs
的match_system
中实现了框架的搭建,下面就需要我们在thrift_lesson/match_system/src/main.cpp
实现我们后端的逻辑。 -
服务端要实现的功能:
-
(1)添加一个用户;
-
(2)删除一个用户;
-
(3)实现用户间的匹配。
-
-
在执行功能(3)的过程中需要同时可以添加和删除用户,即可以执行(1)(2),因此需要使用多线程进行并行执行。
-
我们需要为功能(3)单独开辟一个线程,用于两两匹配用户。
-
可以发现这里的需求就是一个生产者消费者模型:客户端不断有请求给到服务端,这个是生产者;然后匹配线程不断取出用户进行匹配,这个是消费者。
-
我们可以自己实现一个消息队列
message_queue
,生产者将任务task
放到该消息队列,消费者会从消息队列中取出消息并放置到匹配池中。 -
对
main.cpp
的修改如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#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_server;
using namespace std;
// 阻塞队列中的任务
struct Task {
User user;
string type;
};
// 消息队列(阻塞队列)
struct MessageQueue {
queue<Task> q;
// 当多个线程操作q时,需要加锁
mutex m;
// 当消费者发现队列中没有任务时,释放当前锁,并进入等待,防止死循环导致的CPU浪费
// 需要传入一个锁,释放这个锁后,当前线程就会被阻塞住,直到被别的线程唤醒
condition_variable cv;
}message_queue;
// 匹配池: 存放待匹配的用户
class Pool {
public:
// 持久化函数
void save_data(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_data(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(); // 唤醒使用该cv阻塞的消费者
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;
}
};
// 消费者线程执行的函数
// 如果消息队列中有消息,则取出消息放到匹配池中; 否则进入阻塞状态,等待生产者唤醒我
void consume_task() {
while (true) {
unique_lock<mutex> lck(message_queue.m); // 尝试对队列操作之前都要获取锁
if (message_queue.q.empty()) {
// 会释放锁lck, 并进入阻塞状态
message_queue.cv.wait(lck);
} 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);
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 TBufferedTransportFactory());
::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
cout << "Start Match Server..." << endl;
thread matching_thread(consume_task); // 启动消费者线程
server.serve(); // 生产者线程
return 0;
}
- 之后重新编译
main.cpp
,命令如下:
g++ -c main.cpp
- 然后重新链接,生成新的可执行文件,命令如下:
g++ *.o -o main -lthrift -pthread
-
此时我们实现了服务器端用户的匹配,这是最简单的匹配,只要匹配池中存在大于一个人,就取出两个人进行匹配,直到不足两个人。
-
执行结果如下:
- 将代码存入云端,该版本记为
2.0
。
-
接着实现把匹配的用户信息存入到另外一台服务器上,即
myserver
上。此时匹配系统(即acs
的thrift_lesson/match_system/src
)中还要实现一个客户端,该客户端需要和myserver
连接,并将数据持久化到myserver
服务器上。 -
进入网址:save.thrift,复制别人提供的
thrift
文件(这个就是提供的接口),复制到thrift_lesson/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)
}
- 进入
thrift_lesson/match_system/src
,生成对应代码,命令如下:
thrift -r --gen cpp ../../thrift/save.thrift
- 此时生成文件夹
gen-cpp
,重命名为save_client
,该文件夹内容如下:
-
此时必须将
Save_server.skeleton.cpp
删除,否则会有两个主函数。 -
然后更改
thrift_lesson/match_system/src/main.cpp
,将save_client
对应的代码引入到main.cpp
中,需要进入网址:C++ Tutorial中的Client,将缺少的头文件引入,并将该网址中对应的主函数的代码引入到main.cpp
中的save_result
函数中。 -
更改之后的主函数如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#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>
// save_client需要新加的内容
#include "save_client/Save.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.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_server;
// save_client需要新加的内容
using namespace ::save_service;
using namespace std;
// 阻塞队列中的任务
struct Task {
User user;
string type;
};
// 消息队列(阻塞队列)
struct MessageQueue {
queue<Task> q;
// 当多个线程操作q时,需要加锁
mutex m;
// 当消费者发现队列中没有任务时,释放当前锁,并进入等待,防止死循环导致的CPU浪费
// 需要传入一个锁,释放这个锁后,当前线程就会被阻塞住,直到被别的线程唤醒
condition_variable cv;
}message_queue;
// 匹配池: 存放待匹配的用户
class Pool {
public:
// 持久化函数
void save_data(int a, int b) {
printf("Match Result: %d %d\n", a, b);
// save_client需要新加的内容
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_38", "a2f17d72", a, b);
if (!res) puts("success");
else puts("failed");
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}
// 用户匹配函数
void match() {
while (users.size() > 1) {
auto a = users[0], b = users[1];
users.erase(users.begin());
users.erase(users.begin());
save_data(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(); // 唤醒使用该cv阻塞的消费者
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;
}
};
// 消费者线程执行的函数
// 如果消息队列中有消息,则取出消息放到匹配池中; 否则进入阻状态,等待生产者唤醒我
void consume_task() {
while (true) {
unique_lock<mutex> lck(message_queue.m); // 尝试对队列操作之前都要获取锁
if (message_queue.q.empty()) {
// 会释放锁lck, 并进入阻塞状态
message_queue.cv.wait(lck);
} 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);
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 TBufferedTransportFactory());
::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
cout << "Start Match Server..." << endl;
thread matching_thread(consume_task); // 启动消费者线程
server.serve(); // 生产者线程
return 0;
}
-
说明:
save_data
中的IP
是myserver
对应的IP
地址,对应的口令是myserver
的登陆密码MD5
值的前八位。 -
之后对代码进行编译和链接,命令如下:
g++ -c main.cpp save_client/*.cpp
g++ *.o -o main -lthrift -pthread
- 演示结果如下:
-
可以看在
acs
服务器上通过远程调用可以在另一个服务器myserver
实现了持久化。 -
将代码存储到云端。
6.4 匹配系统3.0
对匹配逻辑的升级:只需要修改main.cpp的逻辑即可。
-
前面的匹配逻辑十分简单,就是如果匹配池中有大于等于2个人,就每次选出两个人进行匹配。我们可以进行优化。
-
最直观的优化方式是:消费者线程每次发现消息队列为空时,不阻塞,而是尝试去匹配池中将用户进行匹配,每一秒钟匹配一次(防止CPU负载过高);另外在匹配池中选择用户进行匹配时,选择分差小于等于50的人进行匹配。
-
修改了
consume_task()
和match()
的逻辑,代码如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#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>
// save_client需要新加的内容
#include "save_client/Save.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#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_server;
// save_client需要新加的内容
using namespace ::save_service;
using namespace std;
// 阻塞队列中的任务
struct Task {
User user;
string type;
};
// 消息队列(阻塞队列)
struct MessageQueue {
queue<Task> q;
// 当多个线程操作q时,需要加锁
mutex m;
// 当消费者发现队列中没有任务时,释放当前锁,并进入等待,防止死循环导致的CPU浪费
// 需要传入一个锁,释放这个锁后,当前线程就会被阻塞住,直到被别的线程唤醒
condition_variable cv;
}message_queue;
// 匹配池: 存放待匹配的用户
class Pool {
public:
// 持久化函数
void save_data(int a, int b) {
printf("Match Result: %d %d\n", a, b);
// save_client需要新加的内容
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_38", "a2f17d72", a, b);
if (!res) puts("success");
else puts("failed");
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}
// 用户匹配函数
void match() {
while (users.size() > 1) {
sort(users.begin(), users.end(), [&](User &a, User &b){
return a.score < b.score;
});
bool flag = false; // 是否存在匹配成功的
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_data(a.id, b.id);
flag = true;
break;
}
}
if (!flag) break; // 防止死循环
}
}
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(); // 唤醒使用该cv阻塞的消费者
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;
}
};
// 消费者线程执行的函数
// 如果消息队列中有消息,则取出消息放到匹配池中; 否则进入阻塞状态,等待生产者唤醒我
void consume_task() {
while (true) {
unique_lock<mutex> lck(message_queue.m); // 尝试对队列操作之前都要获取锁
if (message_queue.q.empty()) {
// 会释放锁lck, 并进入阻塞状态
// 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);
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 TBufferedTransportFactory());
::std::shared_ptr<TProtocolFactory> protocolFactory(new TBinaryProtocolFactory());
TSimpleServer server(processor, serverTransport, transportFactory, protocolFactory);
cout << "Start Match Server..." << endl;
thread matching_thread(consume_task); // 启动消费者线程
server.serve(); // 生产者线程
return 0;
}
- 之后对代码进行编译和链接,命令如下:
g++ -c main.cpp
g++ *.o -o main -lthrift -pthread
- 演示结果如下,因为
1、3
分差小于50,因此匹配成功:
6.5 匹配系统4.0
现在接收请求的服务器是单线程的,即生产者是单线程的,main.cpp中main函数中TSimpleServer可以体现这一点,可以升级为多线程服务器。
-
参考网址::C++ Tutorial中的Server。在
main.cpp
中添加缺少的头文件,并将主函数中的server
改为多线程服务器(同时也要将对应的工厂类复制过来并修改)。 -
具体代码如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "match_server/Match.h"
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/concurrency/ThreadFactory.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/TToString.h>
// save_client需要新加的内容
#include "save_client/Save.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#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_server;
// save_client需要新加的内容
using namespace ::save_service;
using namespace std;
// 阻塞队列中的任务
struct Task {
User user;
string type;
};
// 消息队列(阻塞队列)
struct MessageQueue {
queue<Task> q;
// 当多个线程操作q时,需要加锁
mutex m;
// 当消费者发现队列中没有任务时,释放当前锁,并进入等待,防止死循环导致的CPU浪费
// 需要传入一个锁,释放这个锁后,当前线程就会被阻塞住,直到被别的线程唤醒
condition_variable cv;
}message_queue;
// 匹配池: 存放待匹配的用户
class Pool {
public:
// 持久化函数
void save_data(int a, int b) {
printf("Match Result: %d %d\n", a, b);
// save_client需要新加的内容
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_38", "a2f17d72", a, b);
if (!res) puts("success");
else puts("failed");
transport->close();
} catch (TException& tx) {
cout << "ERROR: " << tx.what() << endl;
}
}
// 用户匹配函数
void match() {
while (users.size() > 1) {
sort(users.begin(), users.end(), [&](User &a, User &b){
return a.score < b.score;
});
bool flag = false; // 是否存在匹配成功的
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_data(a.id, b.id);
flag = true;
break;
}
}
if (!flag) break; // 防止死循环
}
}
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(); // 唤醒使用该cv阻塞的消费者
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;
}
};
// 消费者线程执行的函数
// 如果消息队列中有消息,则取出消息放到匹配池中; 否则进入阻塞状态,等待生产者唤醒我
void consume_task() {
while (true) {
unique_lock<mutex> lck(message_queue.m); // 尝试对队列操作之前都要获取锁
if (message_queue.q.empty()) {
// 会释放锁lck, 并进入阻塞状态
// 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);
pool.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;
}
};
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;
}
- 演示效果和
3.0
相同,这里不在演示。
6.6 匹配系统5.0
上面的匹配机制还是存在问题,如果匹配系统中只有两个人且分差较大,两者都等待了很久,我们应该考虑将两者匹配上,但是上面的版本是无法做到的。
-
可以记录每个用户等待的时间,每经过一秒(对应
consume_task
中执行pool.match();
的次数),用户可以匹配的分数差值增加50。 -
具体代码如下:
// This autogenerated skeleton file illustrates how to build a server.
// You should copy it to another filename to avoid overwriting it.
#include "match_server/Match.h"
#include <thrift/concurrency/ThreadManager.h>
#include <thrift/concurrency/ThreadFactory.h>
#include <thrift/protocol/TBinaryProtocol.h>
#include <thrift/server/TSimpleServer.h>
#include <thrift/server/TThreadPoolServer.h>
#include <thrift/server/TThreadedServer.h>
#include <thrift/transport/TServerSocket.h>
#include <thrift/transport/TBufferTransports.h>
#include <thrift/transport/TTransportUtils.h>
#include <thrift/TToString.h>
// save_client需要新加的内容
#include "save_client/Save.h"
#include <thrift/transport/TSocket.h>
#include <thrift/transport/TTransportUtils.h>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#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_server;
// save_client需要新加的内容
using namespace ::save_service;
using namespace std;
// 阻塞队列中的任务
struct Task {
User user;
string type;
};
// 消息队列(阻塞队列)
struct MessageQueue {
queue<Task> q;
// 当多个线程操作q时,需要加锁
mutex m;
// 当消费者发现队列中没有任务时,释放当前锁,并进入等待,防止死循环导致的CPU浪费
// 需要传入一个锁,释放这个锁后,当前线程就会被阻塞住,直到被别的线程唤醒
condition_variable cv;
}message_queue;
// 匹配池: 存放待匹配的用户
class Pool {
public:
// 持久化函数
void save_data(int a, int b) {
printf("Match Result: %d %d\n", a, b);
// save_client需要新加的内容
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_38", "a2f17d72", 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) {
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;
// cout << dt << ' ' << a_max_dif << ' ' << b_max_dif << endl;
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 = false; // 是否存在匹配成功的
for (uint32_t i = 0; i < users.size(); i++) {
for (uint32_t j = i + 1; j < users.size(); j++) {
if (check_match(i, j)) {
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_data(a.id, b.id);
flag = true;
break;
}
}
if (flag) break; // 说明有匹配成功的,会导致users改变,需要跳出循环,重新匹配
}
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(); // 唤醒使用该cv阻塞的消费者
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;
}
};
// 消费者线程执行的函数
// 如果消息队列中有消息,则取出消息放到匹配池中; 否则进入阻塞状态,等待生产者唤醒我
void consume_task() {
while (true) {
unique_lock<mutex> lck(message_queue.m); // 尝试对队列操作之前都要获取锁
if (message_queue.q.empty()) {
// 会释放锁lck, 并进入阻塞状态
// 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);
// version5.0 中该位置不能有这句话,因为要保证每执行一次match用户等待时间+1s
// pool.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;
}
};
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;
}
- 之后对代码进行编译和链接,命令如下:
g++ -c main.cpp
g++ *.o -o main -lthrift -pthread
- 演示结果如下,因为
1、2
分差大于50,需要等待10秒后才会匹配成功: