【linux】06 thrift

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-remotepython作为服务端的代码,删除即可。

  • 进入网址: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中,我们已经在acsmatch_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上。此时匹配系统(即acsthrift_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中的IPmyserver对应的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秒后才会匹配成功:

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值