2013-11-22 51 views
3

我很困惑這個問題。我有一個Apache Thrift 0.9.0客戶端和服務器。客戶代碼如下:TTransportException當使用TFramedTransport

this.transport = new TSocket(this.server, this.port); 
final TProtocol protocol = new TBinaryProtocol(this.transport); 
this.client = new ZKProtoService.Client(protocol); 

這工作正常。但是,如果我嘗試包裹在TFramedTransport

this.transport = new TSocket(this.server, this.port); 
final TProtocol protocol = new TBinaryProtocol(new TFramedTransport(this.transport)); 
this.client = new ZKProtoService.Client(protocol); 

運輸得到以下晦澀(沒有解釋任何消息)的客戶端異常。服務器端顯示沒有錯誤。

org.apache.thrift.transport.TTransportException 
    at org.apache.thrift.transport.TIOStreamTransport.read(TIOStreamTransport.java:132) 
    at org.apache.thrift.transport.TTransport.readAll(TTransport.java:84) 
    at org.apache.thrift.transport.TFramedTransport.readFrame(TFramedTransport.java:129) 
    at org.apache.thrift.transport.TFramedTransport.read(TFramedTransport.java:101) 
    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.TServiceClient.receiveBase(TServiceClient.java:69) 
    at com.blablabla.android.core.device.proto.ProtoService$Client.recv_open(ProtoService.java:108) 
    at com.blablabla.android.core.device.proto.ProtoService$Client.open(ProtoService.java:95) 
    at com.blablabla.simpleprotoclient.proto.ProtoClient.initializeCommunication(ProtoClient.java:411) 
    at com.blablabla.simpleprotoclient.proto.ProtoClient.doWork(ProtoClient.java:269) 
    at com.blablabla.simpleprotoclient.proto.ProtoClient.run(ProtoClient.java:499) 
    at java.lang.Thread.run(Thread.java:724) 

如果我使用TCompactProtocol,而不是TBinaryProtocol它也失敗。

在服務器端我有我自己的類擴展TProcessor,因爲我需要重用現有的服務處理器(該服務的服務器端IFace實現)這個客戶端:

@Override 
public boolean process(final TProtocol in, final TProtocol out) 
     throws TException { 
    final TTransport t = in.getTransport(); 
    final TSocket socket = (TSocket) t; 
    socket.setTimeout(ProtoServer.SOCKET_TIMEOUT); 
    final String clientAddress = socket.getSocket().getInetAddress() 
      .getHostAddress(); 
    final int clientPort = socket.getSocket().getPort(); 
    final String clientRemote = clientAddress + ":" + clientPort; 
    ProtoService.Processor<ProtoServiceHandler> processor = PROCESSORS 
      .get(clientRemote); 
    if (processor == null) { 
     final ProtoServiceHandler handler = new ProtoServiceHandler(
       clientRemote); 
     processor = new ProtoService.Processor<ProtoServiceHandler>(
       handler); 
     PROCESSORS.put(clientRemote, processor); 
     HANDLERS.put(clientRemote, handler); 
     ProtoClientConnectionChecker.addNewConnection(clientRemote, 
       socket); 
    } 
    return processor.process(in, out); 
} 

而且我這是怎麼開始服務器端:

TServerTransport serverTransport = new TServerSocket(DEFAULT_CONTROL_PORT); 
TServer server = new TThreadPoolServer(new TThreadPoolServer.Args(
      serverTransport).processor(new ControlProcessor())); 
Thread thControlServer = new Thread(new StartServer("Control", server)); 
thControlServer.start(); 

我有一些問題:

  • 重用服務處理程序實例是否正確或我不應該這樣做?
  • 爲什麼在使用TFramedTransportTCompactProtocol時會失敗?如何解決這個問題?

對這個問題的任何幫助是值得歡迎的。提前致謝!

回答

4

我遇到了同樣的問題,最終找到了答案。可以在服務器上設置傳輸類型,儘管這從我在網上找到的大多數教程和示例中都不太清楚。看看TServer.Args類的所有方法(或其他服務器的參數類,它們擴展爲TServer.Args)。有方法inputTransportFactoryoutputTransportFactory。您可以使用new TFramedTransport.Factory()作爲每種方法的輸入來聲明服務器應使用哪種傳輸。在斯卡拉:

val handler = new ServiceStatusHandler 
    val processor = new ServiceStatus.Processor(handler) 
    val serverTransport = new TServerSocket(9090) 
    val args = new TServer.Args(serverTransport) 
    .processor(processor) 
    .inputTransportFactory(new TFramedTransport.Factory) 
    .outputTransportFactory(new TFramedTransport.Factory) 
    val server = new TSimpleServer(args) 
    println("Starting the simple server...") 
    server.serve() 

請注意,如果您使用的是TAsyncClient,你不知道你使用的運輸選擇。您必須使用TNonblockingTransport,它只有一個標準實現,TNonblockingSocket,它在內部包裝您在幀傳輸中使用的任何協議。它實際上並沒有將您選擇的協議包裝在TFramedTransport中,但它確實將幀的長度預先寫入了它所寫入的內容,並且期望服務器也預先計劃響應的長度。這在我找到的任何地方都沒有記錄,但是如果您查看源代碼並嘗試使用不同的組合,則會發現使用TSimpleServer必須使用TFramedTransport才能使其與異步客戶端一起使用。

順便說一下,還有一點值得注意的是,文檔中說TNonblockingServer必須在運輸的最外層使用TFramedTransport。但是,這些示例並未在TNonblockingServer.Args中顯示此設置,但您仍然發現您必須在客戶端使用TFramedTransport才能在服務器上成功執行rpc。這是因爲TNonblockingServer.Args默認情況下將其輸入和輸出協議設置爲TFramedTransport(您可以使用反射來查看此超類等級的字段或構造函數AbstractNonblockingServerArgs的源代碼中 - 您可以覆蓋輸入和輸出傳輸,但由於文檔中討論的原因,服務器可能會失敗)。

+0

太棒了,感謝您分享您的解決方案! – m0skit0

+0

我還說了些什麼? **總是在兩端使用完全相同的協議/傳輸棧**! – JensG

2

當問題發生在框架中,但它沒有框架的情況下工作,那麼你在兩端都有不兼容的協議棧。選擇下列之一:

  • 要麼修改服務器代碼中使用框架以及
  • 或不使用客戶端

一個好的經驗法則是,要使用的框架兩端完全相同的協議/傳輸棧。在特定的情況下,它爆炸了,因爲framed添加了一個4字節的標題,其中包含了後面的消息的大小。如果服務器不使用分幀,客戶端發送的這些額外的四個字節將作爲消息的一部分被解釋(錯誤地)。

儘管該答案中的示例代碼爲 TNonblockingServer in thrift crashes when TFramedTransport opens用於C++,但在服務器上添加框架應該與Java非常相似。

PS:是的,重新使用您的處理程序完全可以。典型的處理程序是無狀態的東西。

+1

感謝您的回答。我不知道你可以定義服務器端傳輸。我真的不知道該怎麼做。 [AFAIK](http://people.apache.org/~thejas/thrift-0.9/javadoc/org/apache/thrift/transport/TServerTransport.html)只有2個服務器傳輸類,而[還有幾個](在客戶端http://people.apache.org/~jfarrell/thrift/0.6.1/javadoc/org/apache/thrift/transport/TTransport.html)。我編輯了我的問題以包含服務器端傳輸部分。 – m0skit0

+0

嗯,我看到我應該使用[此服務器](http://people.apache.org/~jfarrell/thrift/0.6.1/javadoc/org/apache/thrift/server/THsHaServer.html),而不是,對吧? – m0skit0

+0

關於運輸,有兩種。 *端點*傳輸,連接到底層介質,如流傳輸,套接字和*分層*傳輸,如框架,緩衝等。後者與特定介質無關,它們僅作爲一種軟件「過濾器」。至少在理論上,分層傳輸可以以任何組合方式堆疊到端點傳輸上。 - 關於服務器,這些只是不同特性的不同實現。服務器本身不影響協議或傳輸。 – JensG