Title

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]

需要记录当前同学等待的秒数和匹配的轮次

 

 

注:课后了解一下锁和多线程

posted @ 2023-10-10 16:47  长大想当太空人  阅读(35)  评论(0)    收藏  举报