【问题标题】:How to keep an Android grpc client with server streaming connection alive?如何使具有服务器流连接的 Android grpc 客户端保持活动状态?
【发布时间】:2020-07-08 09:38:17
【问题描述】:

我有一个 grpc-js 服务器和一个用于 Android 客户端的 Kotlin,用于进行服务器流式调用。这是 GRPCService 类。

class GRPCService {
    private val mChannel = ManagedChannelBuilder
        .forAddress(GRPC_HOST_ADDRESS, GRPC_HOST_PORT)
        .usePlaintext()
        .keepAliveTime(10, TimeUnit.SECONDS)
        .keepAliveWithoutCalls(true)
        .build()
    val asyncStub : ResponderServiceGrpc.ResponderServiceStub = 
        ResponderServiceGrpc.newStub(mChannel)
}

并且该方法是从前台服务调用的。

override fun onCreate() {
    super.onCreate()
    ...
    startForeground(MyNotificationBuilder.SERVICE_NOTIFICATION_ID, notificationBuilder.getServiceNotification())
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    val userId = sharedPreferencesManager.getInt(SharedPreferencesManager.USER_ID)
    val taskRequest = Responder.TaskRequest.newBuilder()
        .setUserId(userId)
        .build()

    grpcService.asyncStub.getTasks(taskRequest, object :
        StreamObserver<Responder.TaskResponse> {
        override fun onCompleted() {
            Log.d("grpc Tasks", "Completed")
        }

        override fun onError(t: Throwable?) {
            Log.d("grpc error cause", t?.cause.toString())
            t?.cause?.printStackTrace()
            Log.d("grpc error", "AFTER CAUSE")
            t!!.printStackTrace()
        }

        override fun onNext(value: Responder.TaskResponse?) {
            if (value != null) {
            
                when (value.command) {
                   ...
                }
            }
        }
    })
    return super.onStartCommand(intent, flags, startId)
}

连接打开并保持打开状态大约一分钟没有通信,然后失败并出现以下错误。

D/grpc error cause: null
D/grpc error: AFTER CAUSE
io.grpc.StatusRuntimeException: INTERNAL: Internal error
io.grpc.Status.asRuntimeException(Status.java:533)
io.grpc.stub.ClientCalls$StreamObserverToCallListenerAdapter.onClose(ClientCalls.java:460)
io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:426)
io.grpc.internal.ClientCallImpl.access$500(ClientCallImpl.java:66)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:689)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$900(ClientCallImpl.java:577)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:751)
io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:740)
io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)

使用以下选项创建 grpc-js 服务器。

var server = new grpc.Server({
    "grpc.http2.min_ping_interval_without_data_ms" : 10000,
    "grpc.keepalive_permit_without_calls" : true,
    "grpc.http2.min_time_between_pings_ms" : 10000,
    "grpc.keepalive_time_ms" : 10000,
    "grpc.http2.max_pings_without_data" : 0,
    'grpc.http2.min_ping_interval_without_data_ms':  5000
});

我也从未收到too many pings 错误。

我注意到,如果通过此连接进行定期通信(例如服务器每隔 30 秒左右用少量数据 ping 客户端),那么我不会收到错误,并且只要连接保持打开状态pinging 继续(测试了 2 天)。

如何在不定期 ping 客户端的情况下保持连接打开?

【问题讨论】:

    标签: android kotlin grpc grpc-java


    【解决方案1】:

    托管通道有一个名为 keepAliveWithoutCalls 的属性,其默认值为 false,如 here 所示。如果未将其设置为 true,则如果当前没有活动调用发生,则不会发生 keepAlive。你需要这样设置:

    private val mChannel = ManagedChannelBuilder
        .forAddress(GRPC_HOST_ADDRESS, GRPC_HOST_PORT)
        .usePlaintext()
        .keepAliveTime(30, TimeUnit.SECONDS)
        .keepAliveWithoutCalls(true)
        .build()
    

    您可能还必须在服务器上进行一些其他设置,以使连接保持打开状态而不传递任何数据。您可能会在服务器上收到“ping 太多”的错误消息。发生这种情况是因为 GRPC 需要一些其他设置。我不确定如何使用 JS 服务器来实现这一点,但这应该不会太难。这些设置包括:

    GRPC_ARG_HTTP2_MIN_RECV_PING_INTERVAL_WITHOUT_DATA_MS
    Minimum allowed time between a server receiving successive ping frames without sending any data/header/window_update frame.
    

    还有这个:

    GRPC_ARG_HTTP2_MIN_SENT_PING_INTERVAL_WITHOUT_DATA_MS
    Minimum time between sending successive ping frames without receiving any data/header/window_update frame, Int valued, milliseconds.
    

    还有这个:

    GRPC_ARG_KEEPALIVE_PERMIT_WITHOUT_CALLS
    Is it permissible to send keepalive pings without any outstanding streams.
    

    有一个Keepalive User Guide for gRPC,我建议您通读以了解 gRPC 应如何保持连接打开。这是所有服务器和客户端实现都应遵循的核心标准,但我注意到并非总是如此。你可以看看我之前问过的一个类似的问题here

    【讨论】:

    • 感谢结构良好的回答。我相应地编辑了我的问题,但错误仍然存​​在。我查看了您提供的所有链接,并在适用的情况下将其合并到我的代码中。我从来没有收到too many pings 错误。一直都是上面那个。我最初使用 grpc-js 客户端测试了我的服务器,并在那里收到相同的错误。
    • 只是为了确保您没有在 JS 服务器和 Android 客户端之间使用一些代理软件,例如 Nginx 或任何 L7 负载均衡器?这些可能有自己的超时和连接清理配置。
    • 我愿意。我正在使用 Nginx 将 GRPC 端口上的通信转发到 GRPC 服务器。我没有考虑到这一点。让我也调查一下。感谢您的提示。
    • 由于 Nginx 是 L7 代理,底层的 L4 TCP 连接在 Nginx 处终止。这意味着您需要配置 Nginx 以保持连接打开以匹配 gRPC 规范。由于 gRPC 使用 Http2。您可能需要检查 Nginx 端的一系列配置以确保其配置正确。老实说,我从未使用过 http2 代理。
    • 我的意思是 Nginx 不会“转发端口”。它实际上检查 http/http2 协议上的底层流量并做出诸如转发哪个主机之类的决定。
    【解决方案2】:

    您是否尝试过 ManagedChannelBuilder.keepAliveTime 设置 (https://github.com/grpc/grpc-java/blob/master/api/src/main/java/io/grpc/ManagedChannelBuilder.java#L357) ?我假设它将在服务器流式调用中间工作。

    【讨论】:

    • 感谢您的回答,但不幸的是,在大约一分钟不活动后,它仍然失败并出现相同的错误。我已编辑问题以反映我的更改并添加了更多上下文。
    猜你喜欢
    • 1970-01-01
    • 2020-10-07
    • 2016-12-19
    • 1970-01-01
    • 1970-01-01
    • 2016-06-05
    • 2021-05-02
    • 2015-11-22
    • 2011-08-18
    相关资源
    最近更新 更多