简体   繁体   English

GRPC/C++ - 服务器仅从双向流中读取第一条消息

[英]GRPC/C++ - Server reads only the first message from the bidi stream

I am using gRPC with C++.我在 C++ 中使用 gRPC。 And I have an async server and sync client.我有一个异步服务器和同步客户端。 The rpc is of type bidirectional stream. rpc 是双向流类型。

Here is how I send message with client:这是我与客户端发送消息的方式:

class ConnectionService {
public:
    ConnectionService(std::shared_ptr<Channel> channel)
            : stub_(Connection::NewStub(channel)) {}

    void HearthBeat() {
        ClientContext context;

        std::shared_ptr<grpc::ClientReaderWriter<Pulse, Pulse> > stream(
                stub_->HearthBeat(&context));

        std::thread writer([stream]() {
            for (int i = 0; i < 100; ++i) {
                Pulse p;
                p.set_rate(50);
                stream->Write(p);
            }
            stream->WritesDone();
        });

        Pulse server_pulse;
        while (stream->Read(&server_pulse)) {
            std::cout << "Got message " << server_pulse.rate()<< std::endl;
        }
        writer.join();
        Status status = stream->Finish();
        if (!status.ok()) {
            std::cout << "RouteChat rpc failed." << std::endl;
        }
    }

private:
    std::unique_ptr<Connection::Stub> stub_;
};

Here is how I read it and reply on the server:这是我在服务器上阅读和回复的方式:

void Vibranium::ConnectionManager::HearthBeatMethod::Create() {
    connectionService_->RequestHearthBeat(&ctx_, &stream_, cq_, cq_,this);
    status_ = PROCESS;
}

void Vibranium::ConnectionManager::HearthBeatMethod::Process() {
    new HearthBeatMethod(connectionService_, cq_);
    stream_.Read(&request_, this);
    status_ = READ_CALLED;
}

bool Vibranium::ConnectionManager::HearthBeatMethod::CheckForClientMetadata() {
    return false;
}

void Vibranium::ConnectionManager::HearthBeatMethod::ReadStream() {
    std::cout << "Received: " << request_.rate() << std::endl;
    reply_.set_rate(65);
    std::cout << "Rate replied: " << reply_.rate() << std::endl;
    stream_.Write(reply_, this);
    status_ = WRITE_CALLED;
}

void Vibranium::ConnectionManager::HearthBeatMethod::WriteToStream() {
    stream_.Finish(grpc::Status::OK, this);
    status_ = FINISH;
}

Here is how I start the server:这是我启动服务器的方式:

void Vibranium::Server::Run() {
    std::string server_address(serverIp_+":"+serverPort_);

    grpc::ServerBuilder builder;
    // Listen on the given address without any authentication mechanism.
    builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIME_MS, 3000);
    builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_TIMEOUT_MS, 3000);
    builder.AddChannelArgument(GRPC_ARG_HTTP2_BDP_PROBE, 1);
    builder.AddChannelArgument(GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS, 1);
    builder.AddChannelArgument(GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS, 1000);
    builder.AddChannelArgument(GRPC_ARG_HTTP2_MIN_SENT_PING_INTERVAL_WITHOUT_DATA_MS, 3000);
    builder.AddChannelArgument(GRPC_ARG_HTTP2_MAX_PINGS_WITHOUT_DATA, 0);
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    RegisterServices(builder);
    cq_ = builder.AddCompletionQueue();
    // Finally assemble the server.
    server_ = builder.BuildAndStart();
    std::cout << "Server listening on " << server_address << std::endl;

    // Proceed to the server's main loop.
    HandleRpcs();
}

void Vibranium::Server::HandleRpcs() {
    RegisterMethods();
    void* tag;  // uniquely identifies a request.
    bool ok;
    while (true) {
        GPR_ASSERT(cq_->Next(&tag, &ok));
        GPR_ASSERT(ok);
        static_cast<ServiceMethod*>(tag)->Proceed();
    }
}

Here is what Proceed();这是Proceed(); is doing:是在做:

void ServiceMethod::Proceed() {
    if (status_ == CREATE) {
        Create();
    } else if (status_ == PROCESS) {
        CheckClient();
        Process();
    } else if(status_ == READ_CALLED){
        ReadStream();
    } else if(status_ == WRITE_CALLED){
        WriteToStream();
    } else {
        Finish();
    }
}

void ServiceMethod::Finish() {
    GPR_ASSERT(status_ == FINISH);
    // Once in the FINISH state, deallocate ourselves (ServiceMethod).
    delete this;
}

So when I trigger the client it sends 1 message instead of 100 as described in the for loop.因此,当我触发客户端时,它会发送 1 条消息而不是 for 循环中描述的 100 条消息。 On server I can see output:在服务器上我可以看到输出:

Received: 50
Rate replied: 65

And on client the output is:在客户端,输出是:

Got message 65

So by this I can see that there is communication between the client and the server however it seems that the server receives and send back just the fist message.所以通过这个我可以看到客户端和服务器之间有通信,但是服务器似乎只接收和发送第一条消息。 Why is that and how can I fix it?为什么会这样,我该如何解决?

I think the immediate cause of the crash you're seeing is that in HearthBeatMethod::Process() , you are starting both a read and write using the same tag, and that tag is not even initialized (it's a void* tag that is never given a value), so there's basically no way you can tell when either of these operations is complete.我认为您看到的崩溃的直接原因是在HearthBeatMethod::Process() ,您使用相同的标签开始读取和写入,并且该标签甚至没有初始化(它是一个void* tag从未给出值),因此您基本上无法判断这些操作中的任何一个何时完成。 And more importantly (this is probably where the crash is happening), the code in Server::HandleRpcs() that is polling your completion queue is assuming that every tag is actually the address of a ServiceMethod object, whose Proceed() method will be called.更重要的是(这可能是崩溃发生的地方), Server::HandleRpcs()中轮询完成队列的代码假设每个标签实际上是一个ServiceMethod对象的地址,其Proceed()方法将是叫。 Since the tag that's coming back is an uninitialized pointer, you're basically calling a method on an arbitrary address, which is causing the crash.由于返回的标记是未初始化的指针,因此您基本上是在任意地址上调用方法,这会导致崩溃。

I also have a couple of comments about the broader design here, in case they're useful.我也有一些关于这里更广泛设计的评论,以防它们有用。

First, for a bidi streaming service, you probably need more states than simply CREATE, PROCESS, and FINISH, because you can only have one read or write in progress at once.首先,对于双向流媒体服务,您可能需要更多的状态,而不仅仅是 CREATE、PROCESS 和 FINISH,因为您一次只能进行一个读取或写入。 You need to know when each read finishes so that you can wait to start the next one until after the previous one is completed.您需要知道每次读取何时完成,以便您可以等到上一次读取完成后再开始下一次读取。 Same thing for writes.写同样的事情。 And note that reads and writes are not synchronized together at all, so they can start and finish at completely different times.请注意,读取和写入根本不同步,因此它们可以在完全不同的时间开始和结束。

And second, it's not clear to me why you are creating a separate streaming RPC to deal with connection management.其次,我不清楚你为什么要创建一个单独的流式 RPC 来处理连接管理。 The gRPC channel itself should deal with connection management for you; gRPC 通道本身应该为您处理连接管理; in your application, you should not need to worry about that.在您的应用程序中,您无需担心。 In principle, the application should just send the individual RPCs it wants and let the channel handle the connection management for you.原则上,应用程序应该只发送它想要的单个 RPC,让通道为您处理连接管理。 If the reason you care about this is that you are doing some sort of session affinity thing, then consider just using a streaming RPC for your application itself.如果您关心这一点的原因是您正在做某种会话关联性的事情,那么请考虑为您的应用程序本身使用流式 RPC。

I hope this info is helpful.我希望这些信息有帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM