7

我有一个从 Linux 上运行的 Java 应用程序提供的 Thrift API。我正在使用 .NET 客户端连接到 API 并执行操作。

对服务的前几次调用工作正常,没有错误,但随后(似乎是随机的)调用将“挂起”。如果我强制退出客户端并尝试重新连接,则服务会再次挂起,或者我的客户端出现以下错误:

Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
   at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
   at Thrift.Transport.TStreamTransport.Read(Byte[] buf, Int32 off, Int32 len) 
   (etc.)

当我使用 JConsole 获取线程转储时,服务器已开启accept()

"Thread-1" prio=10 tid=0x00002aaad457a800 nid=0x79c7 runnable [0x00000000434af000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
        at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
    - locked <0x00000005c0fef470> (a java.net.SocksSocketImpl)
    at java.net.ServerSocket.implAccept(ServerSocket.java:462)
    at java.net.ServerSocket.accept(ServerSocket.java:430)
    at org.apache.thrift.transport.TServerSocket.acceptImpl(TServerSocket.java:113)
    at org.apache.thrift.transport.TServerSocket.acceptImpl(TServerSocket.java:35)
    at org.apache.thrift.transport.TServerTransport.accept(TServerTransport.java:31)
    at org.apache.thrift.server.TSimpleServer.serve(TSimpleServer.java:63)

netstat在服务器上显示与服务端口的连接,这些连接TIME_WAIT在我强制退出客户端几分钟后最终消失(正如预期的那样)。

设置 Thrift 服务的代码如下:

        int port = thriftServicePort;
        String host = thriftServiceHost;
        InetAddress adr = InetAddress.getByName(host);
        InetSocketAddress address = new InetSocketAddress(adr, port);
        TServerTransport serverTransport = new TServerSocket(address);
        TServer server = new TSimpleServer(new TServer.Args(serverTransport).processor((org.apache.thrift.TProcessor)processor));

        server.serve();

请注意,我们正在使用TServerTransport带有显式主机名或 IP 地址的构造函数。我怀疑我应该将其更改为采用仅指定端口(最终绑定到InetAddress.anyLocalAddress())的构造函数。或者,我想我可以将服务配置为绑定到“通配符”地址(“0.0.0.0”)。

我应该提一下,该服务不是托管在开放的 Internet 上的。它托管在专用网络中,我正在使用 SSH 隧道来访问它。因此,服务绑定的主机名无法在我的本地网络中解析(尽管我可以通过隧道建立初始连接)。我想知道这是否类似于RMI TCP 回调问题

是否有关于正在发生的事情的技术解释(如果这是一个常见问题),或者我可以采取的其他故障排除步骤?

更新

今天遇到了同样的问题,但这一次jstack表明 Thrift 服务器永远阻塞了从输入流中读取:

"Thread-1" prio=10 tid=0x00002aaad43fc000 nid=0x60b3 runnable [0x0000000041741000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
            at java.net.SocketInputStream.read(SocketInputStream.java:129)
        at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:127)
        at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84)
        at org.apache.thrift.protocol.TBinaryProtocol.readAll(TBinaryProtocol.java:378)
        at org.apache.thrift.protocol.TBinaryProtocol.readI32(TBinaryProtocol.java:297)
        at org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin(TBinaryProtocol.java:204)
        at org.apache.thrift.TBaseProcessor.process(TBaseProcessor.java:22)
        at org.apache.thrift.server.TSimpleServer.serve(TSimpleServer.java:70)

所以我们需要在TServerSocket构造函数中设置一个“客户端超时”。但是为什么这会导致应用程序在阻塞时也拒绝连接accept()呢?

4

4 回答 4

4

从您的堆栈跟踪来看,您似乎正在使用 TSimpleServer,其javadocs 说

用于测试的简单单线程服务器。

您可能想要使用的是TThreadPoolServer

最有可能发生的事情是 TSimpleServer 的单线程被阻塞,等待死客户端响应或超时。而且因为 TSimpleServer 是单线程的,所以没有线程可用于处理其他请求。

于 2013-01-27T21:44:44.867 回答
3

我有一些建议。您提到对服务器的前几次调用有效,然后出现挂起。这是一个线索。发生这种情况的一种情况是客户端没有将字节完全发送到服务器。我不熟悉 TSimpleServer,但我假设它侦听端口并具有一些二进制协议,并希望任何客户端都能在该协议中与之交谈。您的 .net 客户端通过发送字节与该服务器通信。如果它没有正确刷新其输出缓冲区,那么它可能不会将所有字节发送到服务器,从而挂起服务器。

在 Java 中,这可能发生在客户端,如下所示:

BufferedOutputStream stream = new BufferedOutputStream(socket.getOutputstream()) //get the socket stream to write 
stream.write(content);//write everything that needs to be written 
stream.flush();//if flush() is not called, could result in server getting incomplete packets resulting in hangs!!!

建议:

a) 浏览您的 .net 客户端代码。查看实际与服务器通信的代码的任何部分是否正确调用了等效的 flush() 或 cleanup 方法。注意:我从他们的文档中看到他们的传输层定义了一个 flush()。您应该扫描您的 .net 代码,看看它是否使用传输方法。http://thrift.apache.org/docs/concepts/

b) 为了进一步调试,您可以尝试编写一个模拟您的 .net 客户端的小型 Java 客户端。在您的 linux 机器(运行 TSimpleServer 的同一台机器)上运行 java 客户端。看看它是否会导致同样的问题。如果是这样,您可以调试您的 java 客户端并找到根本原因。如果没有,您可以在 .net 客户端运行的地方运行它,看看是否有任何问题并从那里获取。

编辑 :c) 我可以在这里看到Java中的示例节俭客户端代码:https ://chamibuddhika.wordpress.com/2011/10/02/apache-thrift-quickstart-tutorial/ 我注意到 transport.open(); //做一些代码 transport.close(); 正如 a) 中所建议的,您可以查看您的 .net 客户端代码,看看您是否在完成时调用了传输方法 flush() 和 close()

于 2013-01-31T07:51:34.503 回答
0

将 Thrift 服务投标到通配符地址(“0.0.0.0”)解决了问题,不再挂起。

使用多线程服务器将使应用程序更具响应性,但仍会导致请求挂起/不完整。

如果有人偶然发现这个问题并且可以提供更完整的解释以及它与 Java RMI TCP 回调问题(我在我的问题中链接到)的关系,请为你投票。

于 2013-02-05T20:36:12.273 回答
0

我有一个类似的 c++ 服务器/客户端环境。

c++ 客户端调用一个方法(attributeDefinitionsAliases)并等待响应。

c++ 服务器开始写入套接字但锁定。Wireshark 捕获:

在此处输入图像描述 在c++服务器上关闭c++客户端后,出现异常:

Thrift 内部消息:TSocket::write_partial() send() : errno = 10054

Thrift 内部消息:TConnectedClient 死了:write() send(): errno = 10054

编辑1: 这不是一个节俭的问题。服务器启动/启动的方式似乎有问题。我有一个应用程序(启动器应用程序),它使用 QProcess ( https://doc.qt.io/archives/qt-4.8/qprocess.html )启动/启动服务器,使用 popen工作正常

于 2019-09-16T07:00:20.143 回答