How to get server response with netty client

client-server, java, netty

Solution

Returning a `Future<String>` for the method is simple, we are going to implement the following method signature:

public Futute<String> sendMessage(String msg) {

The is relatively easy to do when you are known with the async programming structures. To solve the design problem, we are going to do the following steps:

When a message is written, add a `Promise<String>` to a `ArrayBlockingQueue<Promise>`

This will serve as a list of what messages have recently been send, and allows us to change our `Future<String>` objects return result.

When a message arrives back into the handler, resolve it against the head of the `Queue`

This allows us to get the correct future to change.

Update the state of the `Promise<String>`

We call `promise.setSuccess()` to finally set the state on the object, this will propagate back to the future object.

Example code

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    private ChannelHandlerContext ctx;
    private BlockingQueue<Promise<String>> messageList = new ArrayBlockingQueue<>(16);

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        super.channelActive(ctx);
        this.ctx = ctx;
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        super.channelInactive(ctx);
        synchronized(this){
            Promise<String> prom;
            while((prom = messageList.poll()) != null) 
                prom.setFailure(new IOException("Connection lost"));
            messageList = null;
        }
    }

    public Future<String> sendMessage(String message) {
        if(ctx == null) 
            throw new IllegalStateException();
        return sendMessage(message, ctx.executor().newPromise());
    }

    public Future<String> sendMessage(String message, Promise<String> prom) {
        synchronized(this){
            if(messageList == null) {
                // Connection closed
                prom.setFailure(new IllegalStateException());
            } else if(messageList.offer(prom)) { 
                // Connection open and message accepted
                ctx.writeAndFlush(message).addListener();
            } else { 
                // Connection open and message rejected
                prom.setFailure(new BufferOverflowException());
            }
            return prom;
        }
    }
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, String msg) {
        synchronized(this){
            if(messageList != null) {
                 messageList.poll().setSuccess(msg);
            }
        }
    }
}

Documentation breakdown

`private ChannelHandlerContext ctx;`

Used to store our reference to the ChannelHandlerContext, we use this so we can create promises

`private BlockingQueue<Promise<String>> messageList = new ArrayBlockingQueue<>();`

We keep the past messages in this list so we can change the result of the future

`public void channelActive(ChannelHandlerContext ctx)`

Called by netty when the connection becomes active. Init our variables here.

`public void channelInactive(ChannelHandlerContext ctx)`

Called by netty when the connection becomes inactive, either due to error or normal connection close.

`protected void messageReceived(ChannelHandlerContext ctx, String msg)`

Called by netty when a new message arrives, here pick out the head of the queue, and then we call setsuccess on it.

Warning advise

When using futures, there is 1 thing you need to lookout for, do not call get() from 1 of the netty threads if the future isn't done yet, failure to follow this simple rule will either result in a deadlock or a `BlockingOperationException`.

Problem

I want to write a netty based client. It should have method public String send(String msg); which should return response from the server or some future - doesen't matter. Also it should be multithreaded. Like this: ``` public class Client { public static void main(String[] args) throws InterruptedException { Client client = new Client(); } private Channel channel; public Client() throws InterruptedException { EventLoopGroup loopGroup = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(loopGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new StringDecoder()). addLast(new StringEncoder()). addLast(new ClientHandler()); } }); channel = b.connect("localhost", 9091).sync().channel(); } public String sendMessage(String msg) { channel.writeAndFlush(msg); return ??????????; } ``` } And I don't get how can I retrieve response from server after I invoke writeAndFlush(); What should I do? Also I use Netty 4.0.18.Final

Original source