当前位置: 首页 > news >正文

简析gRPC client 连接管理

简析gRPC client 连接管理

背景

客户端skd 使用gRPC作为通信协议,定时(大概是120s)向服务器发送pingServer 请求。
服务端是80端口,如xxx:80. 

问题

发现客户端不断的端口重连服务器的。
使用netstat -antp

clipboard.png

如图, 如标红的服务器地址连接是TIME_WAIT,后面有和服务器建立连接 ESTABLISHED。
TIME_WAIT 状态表明是client 端主动断开了连接。

这和我之前的认知有点冲突,gRPC 应该是长连接,为什么这里每次都断开呢,这样不就长了短连接了吗?
而且客户端主动断开的,会不会是client端哪里有问题?

带着疑问,在client 抓了一包,
发现client 总是受到一个 length 为17 的包,然后就开始FIN 包,走TCP 挥手的流程。
使用WireShark 对tcpdump的结果查看,发现这个length 17 的包,是一个GOAWAY 包。

如图:

clipboard.png

这个是HTTP2定义的一个“优雅”退出的机制。

这里有HTTP2 GOAWAY stream 包的说明。

HTTP2 GOAWAY 说明



根据之前的对gRPC的了解,gRPC client 会解析域名,然后会维护一个lb 负载均衡,
这个应该是gRPC对idle 连接的管理。pingServer 的时间间隔是120s, 但是gRPC 认为中间是idle连接,
所以通知client 关闭空闲连接?

为了验证这个想法,修改了一下gRPC 的demo, 因为我们client 端使用是cpp 的gRPC 异步调用方式,
所以更加gRPC 的异步demo, 写了一个简单访问服务器的async_client

代码:


#include <iostream>
#include <memory>
#include <string>

#include <grpcpp/grpcpp.h>
#include <grpc/support/log.h>
#include <thread>

#include "gateway.grpc.pb.h"

using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using yournamespace::PingReq;
using yournamespace::PingResp;
using yournamespace::srv;

class GatewayClient {
  public:
    explicit GatewayClient(std::shared_ptr<Channel> channel)
            : stub_(srv::NewStub(channel)) {}

    // Assembles the client's payload and sends it to the server.
    //void PingServer(const std::string& user) {
    void PingServer() {
        // Data we are sending to the server.
        PingReq request;
        request.set_peerid("1111111111111113");
        request.set_clientinfo("");

        request.set_capability(1);
        request.add_iplist(4197554190);
        request.set_tcpport(8080);
        request.set_udpport(8080);
        request.set_upnpip(4197554190);
        request.set_upnpport(8080);
        request.set_connectnum(10000);
        request.set_downloadingspeed(100);
        request.set_uploadingspeed(10);
        request.set_maxdownloadspeed(0);
        request.set_maxuploadspeed(0);

        // Call object to store rpc data
        AsyncClientCall* call = new AsyncClientCall;

        // stub_->PrepareAsyncSayHello() creates an RPC object, returning
        // an instance to store in "call" but does not actually start the RPC
        // Because we are using the asynchronous API, we need to hold on to
        // the "call" instance in order to get updates on the ongoing RPC.
        call->response_reader =
            stub_->AsyncPing(&call->context, request, &cq_);

        // StartCall initiates the RPC call
        //call->response_reader->StartCall();

        // Request that, upon completion of the RPC, "reply" be updated with the
        // server's response; "status" with the indication of whether the operation
        // was successful. Tag the request with the memory address of the call object.
        call->response_reader->Finish(&call->reply, &call->status, (void*)call);

    }

    // Loop while listening for completed responses.
    // Prints out the response from the server.
    void AsyncCompleteRpc() {
        void* got_tag;
        bool ok = false;

        // Block until the next result is available in the completion queue "cq".
        while (cq_.Next(&got_tag, &ok)) {
            // The tag in this example is the memory location of the call object
            AsyncClientCall* call = static_cast<AsyncClientCall*>(got_tag);

            // Verify that the request was completed successfully. Note that "ok"
            // corresponds solely to the request for updates introduced by Finish().
            GPR_ASSERT(ok);

            if (call->status.ok())
                std::cout << "xNetClient received: " << call->reply.code() << "  task:" << call->reply.tasks_size() <<"  pinginterval:"<< call->reply.pinginterval() << std::endl;
            else
                //std::cout << "RPC failed" << std::endl;
            std::cout << ": status = " << call->status.error_code() << " (" << call->status.error_message() << ")" << std::endl;

            // Once we're complete, deallocate the call object.
            delete call;
        }
    }

  private:

    // struct for keeping state and data information
    struct AsyncClientCall {
        // Container for the data we expect from the server.
        PingResp reply;

        // Context for the client. It could be used to convey extra information to
        // the server and/or tweak certain RPC behaviors.
        ClientContext context;

        // Storage for the status of the RPC upon completion.
        Status status;


        std::unique_ptr<ClientAsyncResponseReader<PingResp>> response_reader;
    };

    // Out of the passed in Channel comes the stub, stored here, our view of the
    // server's exposed services.
    std::unique_ptr<srv::Stub> stub_;

    // The producer-consumer queue we use to communicate asynchronously with the
    // gRPC runtime.
    CompletionQueue cq_;
};

int main(int argc, char** argv) {


    // Instantiate the client. It requires a channel, out of which the actual RPCs
    // are created. This channel models a connection to an endpoint (in this case,
    // localhost at port 50051). We indicate that the channel isn't authenticated
    // (use of InsecureChannelCredentials()).

    if (argc < 2){
    std::cout << "usage: " <<argv[0]<< " domain:port" << std::endl;
    std::cout << "eg: " <<argv[0]<< " gw.xnet.xcloud.sandai.net:80" << std::endl;
    return 0;
    }

    GatewayClient xNetClient(grpc::CreateChannel( argv[1], grpc::InsecureChannelCredentials()));

    // Spawn reader thread that loops indefinitely
    std::thread thread_ = std::thread(&GatewayClient::AsyncCompleteRpc, &xNetClient);

    for (int i = 0; i < 1000; i++) {
        xNetClient.PingServer();  // The actual RPC call!
        std::this_thread::sleep_for(std::chrono::seconds(120));
    }

    std::cout << "Press control-c to quit" << std::endl << std::endl;
    thread_.join();  //blocks forever

    return 0;
}

接下来的时间很简单,运行一下。
使用netstat -natp 观察,可以重新。 async_client 也是断开,重连。
进一步调试发现,把发包的时间修改为10s 的时候,可以保持连接,大于10s基本上连接就会断开。

小结

小结一下:
gRPC 管理连接的方式,默认情况下,大于10s没有数据发送,gRPC 就会认为是个idle 连接。server 端会给client 端发送一个GOAWAY 的包。client 收到这个包之后就会主动关闭连接。下次需要发包的时候,就会重新建立连接。

目前还不知道是不是有配置项修改这个值,对gRPC 的机制还不是很熟,后面再研究一下。

相关文章:

  • Java应用性能调优
  • BOM和DOM
  • Linux top命令的用法详细详解
  • StringUtils中常用方法leftPad(),rightPad(),center()
  • docker的持久化存储和共享存储和网络架构
  • 项目经理需了解的技术
  • HTTP头和网页分离方法
  • 架构师成长之路-基于android fragment通信的面向对象的万能接口
  • python学习之老男孩python全栈第九期_day004知识点总结
  • 人人快速开发平台
  • shell执行和crontab执行结果不一样的问题
  • 面对谷歌AI,这真的不是拔电线就能够解决的
  • Linux中使用sendmail发送邮件,指定任意邮件发送人
  • linux基础之Vim
  • python如何解决MD5对文件加密出现粘包的情况
  • [case10]使用RSQL实现端到端的动态查询
  • 《网管员必读——网络组建》(第2版)电子课件下载
  • 【干货分享】SpringCloud微服务架构分布式组件如何共享session对象
  • 【许晓笛】 EOS 智能合约案例解析(3)
  • codis proxy处理流程
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • java正则表式的使用
  • jquery ajax学习笔记
  • Python中eval与exec的使用及区别
  • UEditor初始化失败(实例已存在,但视图未渲染出来,单页化)
  • unity如何实现一个固定宽度的orthagraphic相机
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 猫头鹰的深夜翻译:JDK9 NotNullOrElse方法
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 硬币翻转问题,区间操作
  • 用 vue 组件自定义 v-model, 实现一个 Tab 组件。
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • Linux权限管理(week1_day5)--技术流ken
  • 交换综合实验一
  • 没有任何编程基础可以直接学习python语言吗?学会后能够做什么? ...
  • !$boo在php中什么意思,php前戏
  • #NOIP 2014# day.2 T2 寻找道路
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (zt)最盛行的警世狂言(爆笑)
  • (二)PySpark3:SparkSQL编程
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • .bashrc在哪里,alias妙用
  • .Net Attribute详解(上)-Attribute本质以及一个简单示例
  • .NET Core 中的路径问题
  • .NET Micro Framework初体验(二)
  • .NET Remoting学习笔记(三)信道
  • .NET 使用 ILRepack 合并多个程序集(替代 ILMerge),避免引入额外的依赖
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .net反混淆脱壳工具de4dot的使用
  • @Bean, @Component, @Configuration简析
  • @cacheable 是否缓存成功_Spring Cache缓存注解
  • @converter 只能用mysql吗_python-MySQLConverter对象没有mysql-connector属性’...
  • [ 手记 ] 关于tomcat开机启动设置问题
  • [2544]最短路 (两种算法)(HDU)
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt