cpp-ipc icon indicating copy to clipboard operation
cpp-ipc copied to clipboard

如何实现类似c/s架构的通信

Open jumpapex opened this issue 2 years ago • 2 comments

mutouyun, 谢谢你分享的这个项目! 目前我有个程序打算使用你的这个项目。 我的程序是同一台机器上上类似C/S的进程间通信。一个Server,多个client,client向server发送请求,server只是被动响应客户端的请求,msg可能会比较大,几百K到几兆。 我希望是每个client和server之间有专用的IPC通道,甚至server可以为每个通道建立专用的thread。 研究了一下,不知道如何建立这样的IPC通道,希望能得到你的指点。 谢谢! paul

jumpapex avatar Aug 27 '21 08:08 jumpapex

如果内存够用的话,你可以用chan<relat::single, relat::single, trans::unicast>,这是一个单向通道,只能单收单发。 服务端每个连接线程都使用 receiver 模式 connect 一个这样的通道,客户端则使用 sender 模式。

如果希望尽量节省内存,你可以直接用 channel,它相当于 chan<relat::multi, relat::multi, trans::broadcast>,它能支持多个客户端(也就是 sender)同时给一个服务端(receiver)发送数据。 在使用时需要注意,因为所有的数据是发送到一个通道上的,因此数据的分派需要你自己来做。 建议通过一个线程拿取消息,再分派到合适的工作队列/工作线程上做具体的处理。

PS:除非数据包不大于data_length,否则不要使用chan<relat::multi, relat::multi, trans::unicast>,当前这个模式存在bug(见 issue-46)。

mutouyun avatar Aug 27 '21 08:08 mutouyun

我尝试实现了一个rpc服务demo,代码如下

// 理想的rpc request是多client写,单server读,但是如下定义会报错,所以改成多读,确认数据包不大于dada_length
// request = ipc::chan<ipc::relat::multi, ipc::relat::single, ipc::trans::unicast>;
using request = ipc::chan<ipc::relat::multi, ipc::relat::multi, ipc::trans::unicast>;
// response是单server写,单client读
using response = ipc::chan<ipc::relat::single, ipc::relat::single, ipc::trans::unicast>;

void srv(std::string const& name) {
    request req{(name + "-req").c_str(), ipc::receiver};
    std::map<std::string, response*> res_map;

    while (true) {
        auto recv = req.recv();
        if (!recv.empty()) {
            std::string value = static_cast<char*>(recv.data());
            std::cout << "request:" << value << std::endl;

            response *rep;
            auto iter = res_map.find(value);
            if (iter == res_map.end()) {
                rep = new response{(value+":"+name).c_str(), ipc::sender};
                res_map[value] = rep;
            } else {
                rep = iter->second;
            }

            while (!rep->send(("rpc-" + value).c_str())) {
                rep->wait_for_recv(1);
            }
        }
    }
}

void clt(std::string const& name, std::string const& value) {
    request req{(name+"-req").c_str(), ipc::sender};
    response rep{(value+":"+name).c_str(), ipc::receiver};

    while (true) {
        while (!req.send(value.c_str())) {
            req.wait_for_recv(1);
        }

        auto recv = rep.recv();
        if (!recv.empty()) {
            std::string res = static_cast<char*>(recv.data());
            std::cout << "response:" << res << std::endl;
        }
    }
}

/*
./ipc_test srv service
./ipc_test clt service input
*/

int main(int argc, char** argv) {
    if (argc < 3) {
        return 0;
    }

    std::string mode = argv[1];
    std::string name = argv[2];
    std::string value;

    if (mode == "clt") {
        if (argc < 4) {
            return 0;
        }
        value = argv[3];
    }

    if (mode == "srv") {
        srv(name);
    } else if (mode == "clt") {
        clt(name, value);
    }
}

以上代码在正常运行时可以正常工作,但是如果手动ctrl-c中止client,再次启动client时会必然提示 fail: send, there is no receiver on this connection. 如果运行中手动ctrl-c中止server,再次启动server若干次后会读不到client请求,这些问题都可以通过rm /dev/shm/* 解决。

将request和response都改为channel后,client可以在手动中止再次重启时正常收发数据了,但是server端还是一旦中止重启后就无法接收数据,另外我不确认这里把unicast改为broadcast是否会有性能损失。

我希望rpc能在不稳定运行场景下提供一个可靠的服务,所以请问一下解决的思路是什么。

pi1ot avatar Sep 09 '21 03:09 pi1ot