Here's an example app that sends messages between clients connected to a server. It's similar to a chat app.I think I've done something similar to what you're trying to do, and as long as you can commit to using only async messages it's possible to pull it off without having to start a server on the client to accept RPCs from the server.
When your RPC is marked as async the server doesn't send a response and the client doesn't try to read one. So, if all your RPC calls from the client to the server are async you have effectively freed up the inbound half of the socket connection. That means that you can use it for receiving async messages from the server - the only catch is that you have to start a new thread to read and dispatch the incoming async RPC calls.
In a typical Thrift RPC system you'd create a MyService.Processor on your server and a MyService.Client on your client. To do bidirectional async message sending you'll need to go a step further and create a MyService.Client on your server for each client that connects (this can be accomplished by providing your own TProcessorFactory) and then on each client you create a MyService.Processor. (This assumes that you've gone with a generic MyService definition like you described above that has a bunch of optional messages, another option would be to define separate service definitions for the client and server.) With two clients connected the objects in existence would look something like this:
Server:
MyService.Processor mainProcessor - handles incoming async RPCs
MyService.Client clientA - used to send outgoing async RPCs to ClientA
MyService.Client clientB - used to send outgoing async RPCs to ClientBClientA:
MyService.Client - used to send messages to Server
MyService.Processor clientProcessor - used (by a separate thread) to process incoming async RPCsClientB:
MyService.Client - used to send messages to Server MyService.Processor clientProcessor - used (by a separate thread) to process incoming async RPCsHopefully that explains the concept. If you need example code I can try and pull something together (it will be in Java). The nice thing about this method is that you don't have to establish two connections, so you can get around the firewall issues others have mentioned. I've been using this method on a service in production and haven't had any problems. When you have a separate thread in your client running a Processor you're basically blocking on a read, waiting for a message from the server. The benefit of this is that you're notified immediately when the server shuts down instead of having to wait until you send a message and then finding out that the TCP connection was reset.
Cheers,
Joel
final MessageDistributor messageDistributor = new MessageDistributor();
new Thread(messageDistributor).start();
TProcessorFactory processorFactory = new TProcessorFactory(null) {
@Override
public TProcessor getProcessor(TTransport trans) {
messageDistributor.addClient(new MessageServiceClient(trans));
return new MessageService.Processor(messageDistributor);
}
};
TServerTransport serverTransport = new TServerSocket(port);
TServer server = new TThreadPoolServer(processorFactory, serverTransport);
LOGGER.info("Server started");
server.serve();
public class MessageReceiver extends ConnectionRequiredRunnable {
private final MessageService.Processor processor;
private final TProtocol protocol;
public MessageReceiver(
TProtocol protocol,
MessageService.Iface messageService,
ConnectionStatusMonitor connectionMonitor) {
super(connectionMonitor, "Message Receiver");
this.protocol = protocol;
this.processor = new MessageService.Processor(messageService);
}
@Override
public void run() {
connectWait();
while (true) {
try {
while (processor.process(protocol, protocol) == true) { }
} catch (TException e) {
disconnected();
}
}
}
}
public class MessageSender extends ConnectionRequiredRunnable {
private final MessageService.Client client;
private final BlockingQueue<Message> msgSendQueue;
public MessageSender(
TProtocol protocol,
ConnectionStatusMonitor connectionMonitor) {
super(connectionMonitor, "Message Sender");
this.client = new MessageService.Client(protocol);
this.msgSendQueue = new LinkedBlockingQueue<Message>();
}
public void send(Message msg) {
msgSendQueue.add(msg);
}
@Override
public void run() {
connectWait();
while (true) {
try {
Message msg = msgSendQueue.take();
try {
client.sendMessage(msg);
} catch (TException e) {
// The message isn't lost, but it could end up being sent out of
// order - not ideal.
msgSendQueue.add(msg);
disconnected();
}
} catch (InterruptedException e) {
// Thread will be interrupted if connection is lost, we should wait
// for reconnection if that happens.
connectWait();
}
}
}
}
this.transport = new TSocket(server, port);
this.protocol = new TBinaryProtocol(transport);
this.connectionMonitor = new ConnectionStatusMonitor(transport);
this.sender = new MessageSender(protocol, connectionMonitor);
this.receiver = new MessageReceiver(protocol, messageHandler, connectionMonitor);
new Thread(sender).start();
new Thread(receiver).start();
this.connectionMonitor.tryOpen();
2009-04-03 16:28:44,029 INFO main com.joelpm.bidiMessages.server.Server:43 - Server started
2009-04-03 16:28:45,814 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:36 - Added client at 127.0.0.1
2009-04-03 16:28:45,822 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Hello there!)
2009-04-03 16:28:45,823 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 0)
2009-04-03 16:28:46,807 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 1)
2009-04-03 16:28:46,864 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:36 - Added client at 127.0.0.1
2009-04-03 16:28:46,895 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client2, message:Hello there!)
2009-04-03 16:28:46,897 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client2, message:Message 0)
2009-04-03 16:28:47,805 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 2)
2009-04-03 16:28:47,885 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client2, message:Message 1)
2009-04-03 16:28:48,806 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 3)
2009-04-03 16:28:48,885 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client2, message:Message 2)
2009-04-03 16:28:49,806 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 4)
2009-04-03 16:28:49,885 INFO pool-1-thread-2 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client2, message:Message 3)
^C2009-04-03 16:28:50,807 INFO pool-1-thread-1 com.joelpm.bidiMessages.server.MessageDistributor:66 - Adding message to queue:
Message(clientName:client1, message:Message 5)
$ java -jar Client/target/BidiMessages.Client-0.9-jar-with-dependencies.jar client1 localhost 10101
2009-04-03 16:28:45,792 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Receiver waiting for connection to be established.
2009-04-03 16:28:45,792 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Sender waiting for connection to be established.
2009-04-03 16:28:45,803 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:48 - Message Sender notified of connection, resuming execution
2009-04-03 16:28:45,806 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:48 - Message Receiver notified of connection, resuming execution
Got msg: Message(clientName:client1, message:Hello there!)
Got msg: Message(clientName:client1, message:Message 0)
Got msg: Message(clientName:client1, message:Message 1)
Got msg: Message(clientName:client2, message:Hello there!)
Got msg: Message(clientName:client2, message:Message 0)
Got msg: Message(clientName:client1, message:Message 2)
Got msg: Message(clientName:client2, message:Message 1)
Got msg: Message(clientName:client1, message:Message 3)
Got msg: Message(clientName:client2, message:Message 2)
Got msg: Message(clientName:client1, message:Message 4)
Got msg: Message(clientName:client2, message:Message 3)
Got msg: Message(clientName:client1, message:Message 5)
2009-04-03 16:28:51,146 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:30 - Message Receiver detected a disconnect from the server.
2009-04-03 16:28:51,148 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Receiver waiting for connection to be established.
2009-04-03 16:28:51,149 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Sender waiting for connection to be established.
$ java -jar Client/target/BidiMessages.Client-0.9-jar-with-dependencies.jar client2 localhost 10101
2009-04-03 16:28:46,851 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Sender waiting for connection to be established.
2009-04-03 16:28:46,854 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Receiver waiting for connection to be established.
2009-04-03 16:28:46,867 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:48 - Message Sender notified of connection, resuming execution
2009-04-03 16:28:46,879 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:48 - Message Receiver notified of connection, resuming execution
Got msg: Message(clientName:client2, message:Hello there!)
Got msg: Message(clientName:client2, message:Message 0)
Got msg: Message(clientName:client1, message:Message 2)
Got msg: Message(clientName:client2, message:Message 1)
Got msg: Message(clientName:client1, message:Message 3)
Got msg: Message(clientName:client2, message:Message 2)
Got msg: Message(clientName:client1, message:Message 4)
Got msg: Message(clientName:client2, message:Message 3)
Got msg: Message(clientName:client1, message:Message 5)
2009-04-03 16:28:51,146 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:30 - Message Receiver detected a disconnect from the server.
2009-04-03 16:28:51,147 INFO Thread-3 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Receiver waiting for connection to be established.
2009-04-03 16:28:51,147 INFO Thread-2 com.joelpm.bidiMessages.client.ConnectionRequiredRunnable:42 - Message Sender waiting for connection to be established.