具有异步调用者的Netty同步客户端

时间:2016-01-19 12:48:25

标签: request netty synchronous reply

我正在创建一个服务器,它使用来自JMS,SNMP,HTTP等众多来源的命令。这些都是异步的并且工作正常。服务器维护与单个遗留硬件项的单个连接,该硬件具有带有自定义TCP协议的请求/回复体系结构。 理想情况下,我想要一个像这样的阻塞类型方法

的命令
public Response issueCommandToLegacyHardware(Command command)

或这种异步类型方法

public Future<Response> issueCommandToLegacyHardware(Command command)

我对Netty和异步编程比较陌生,基本上就像我一样学习它。我目前的想法是,我的LegacyHardwareClient班级将public synchronized issueCommandToLegacyHardware(Command command),将写入到旧硬件的客户端频道,然后take()将会阻止SynchronousQueue<Response>。管道中的ChannelInboundHandler将offer() Response SynchronousQueue>Response> take()<img src='http://example.net/static/img/image.jpg' style='width:500px;height:228px;'> 允许/static/img/image.jpg取消阻止并接收数据。

这太复杂了吗?我可以看一下同步Netty客户端实现的示例吗? Netty有什么最佳实践吗? 我显然可以只使用标准的Java套接字,但是Netty解析自定义协议的能力以及易维护性太强大了,无法放弃。

更新: 关于实现,我使用了ArrayBlockingQueue&lt;&gt;(),我使用put()和remove()而不是offer()和remove()。因为我希望确保只有在回复了任何活动请求时才会发送对旧硬件的后续请求,因为传统硬件行为不确定。

如果没有主动阻塞take()请求而没有另一方,则offer()和remove()对我不起作用的原因是offer()命令不会传递任何内容。反之亦然,除非阻塞put()调用插入数据,否则remove()不会返回任何内容。 我无法使用put()/ remove(),因为永远不会到达remove()语句,因为没有写入通道的请求来触发将调用remove()的事件。我无法使用offer()/ take(),因为offer()语句将返回false,因为尚未执行take()调用。 使用容量为1的ArrayBlockingQueue&lt;&gt;(),可确保一次只能执行一个命令。任何其他命令将阻塞,直到有足够的空间插入,容量为1这意味着它必须是空的。一旦从传统硬件接收到响应,就完成了队列的清空。这确保了对传统硬件的良好同步行为,但为传统硬件的用户提供了异步API,其中有许多。

1 个答案:

答案 0 :(得分:4)

不是使用SynchronousQueue<Response>以阻止方式设计应用程序,而是使用SynchronousQueue<Promise<Response>>以非阻塞方式设计应用程序。

然后,您的public Future<Response> issueCommandToLegacyHardware(Command command)应使用offer()向网络添加DefaultPromise<>(),然后netty管道可以使用remove()获取该请求的响应,通知我使用remove()代替take(),因为只有在特殊情况下才会出现任何元素。

快速实施这可能是:

public class MyLastHandler extends SimpleInboundHandler<Response> {
    private final SynchronousQueue<Promise<Response>> queue;

    public MyLastHandler (SynchronousQueue<Promise<Response>> queue) {
        super();
        this.queue = queue;
    }

    // The following is called messageReceived(ChannelHandlerContext, Response) in 5.0.
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Response msg) {
        this.queue.remove().setSuccss(msg); // Or setFailure(Throwable)
    }
}

上面的处理程序应该放在链的最后。

public Future<Response> issueCommandToLegacyHardware(Command command)的实现可以看起来:

Channel channel = ....;
SynchronousQueue<Promise<Response>> queue = ....;

public Future<Response> issueCommandToLegacyHardware(Command command) {
    return issueCommandToLegacyHardware(command, channel.eventLoop().newPromise());
}

public Future<Response> issueCommandToLegacyHardware(Command command, Promise<Response> promise) {
    queue.offer(promise);
    channel.write(command);
    return promise;
}

使用issueCommandToLegacyHardware上的重载方法也是Channel.write使用的设计模式,这使它非常灵活。

此设计模式可在客户端代码中使用如下:

issueCommandToLegacyHardware(
    Command.TAKE_OVER_THE_WORLD_WITH_FIRE, 
    channel.eventLoop().newPromise()
).addListener(
    (Future<Response> f) -> {
        System.out.println("We have taken over the world: " + f.get());
    }
);

这种设计模式的优点是不会在任何地方使用任何不需要的阻塞,只需简单的异步逻辑。

附录I:Javadoc:

Promise Future DefaultPromise