Netty - 如何在客户端获取服务器响应

10

我对Netty有了初步的了解,但是还有一个概念一直在困扰我,而且我在教程等资料中找不到任何相关信息。首先我知道Netty是异步的,但是客户端必须有一种方法来调用服务器并能够在处理程序之外获得响应。让我解释更多。

我有一个客户端如下所示。请注意,我知道它被引导并且每次调用都会建立新的连接,这只是为了使示例更小更简洁。请忽略这个事实。

Client.java

// ServerResponse is a result from the server, in this case 
// a list of users of the system (ignore that each time it's all bootstrapped).

public User[] callServerForInformationFromGUIWidget()
{
    ClientBootstrap bootstrap = new ClientBootstrap(...);
    bootstrap.setPipelineFactory(...);

    ChannelFuture future = bootstrap.connect(new InetSocketAddress(host, port));
    Channel channel = future.awaitUninterruptibly().getChannel();

    // Where request is a POJO sent to the server, 
    // with a request such as get me a list of users
    RequestPojo request = new RequestPojo(requestUserListCommand);

    ChannelFuture lastWriteFuture = channel.write(request);

    if(lastWriteFuture != null)
        lastWriteFuture.awaitUninterruptibly();
}

现在我明白了如何在服务器上获取数据并返回结果。唯一的问题是如何在客户端处理它?是的,clientHandler类可以做类似以下的事情:

ClientHandler.java

@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) 
{
    User[] users = (User[])e.getMessage();
}
问题是客户端代码如何获得结果?所有示例都类似于聊天服务,其中事件触发客户端上的其他内容,而不是等待响应。即使是我找到的http客户端示例也缺乏此功能。总体文档非常好,但缺乏如何执行回调的说明。无论如何,在这种情况下,我需要客户端从服务器获取响应,并根据结果执行必要的操作。
换句话说,我该如何编写客户端来执行类似以下的操作:
IdealClient.java
// ServerResponse is a result from the server, in this case 
// a list of users of the system.

public User[] callServerForInformationFromGUIWidget()
{
    ...
    RequestPojo request = new RequestPojo(requestUserListCommand);
    ChannelFuture lastWriteFuture = channel.write(request);

    if(lastWriteFuture != null)
        lastWriteFuture.awaitUninterruptibly();

    User[] users = resultFromCallToServer();

    performSomeAction(users);
}

因为处理程序不知道谁在寻找答案,或者谁提出了问题。而且如果在处理程序中完成,那么该如何实现?

回到我对示例的评论,http客户端(和处理程序)示例只是将结果转储到System.out。如果您有一个GUI,您要如何将请求的结果传递给GUI?我从未见过任何此类示例。

2个回答

5

Jestan是正确的。在我的情况下,我有一个客户端需要处理价格数据。我使用Antlr进行解析。我在解析器中触发事件,但在我的情况下,我的协议是基于字符串的。以下是一个没有Antlr的示例,在您的情况下,我将传递String消息给用户。

//----------------- Event --------------
public class DataChangeEvent {
    private String message;

    public DataChangeEvent(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }


}

//----------------- Listener --------------
public interface DataChangeListenter {
    public void dataChangeEvent(DataChangeEvent event);
}

//----------------- Event Handler that fires the dataChange events --------------
// This class needs to be static since you need to register all your classes that want to be notified of data change events
public class DataChangedHandler {
    private static List<DataChangeListenter> listeners = new ArrayList<DataChangeListenter>();

    public static void registerDataChangeListener(DataChangeListenter listener) {
        listeners.add(listener);
    }

    public static void fireDataChange(DataChangeEvent dataChangeEvent) {
        for(DataChangeListenter listenter : listeners) {
            listenter.dataChangeEvent(dataChangeEvent);
        }
    }
}

//----------------- Example class that implements the listener and registers itself for events --------------
public class ProcessMessage implements DataChangeListenter {

    public ProcessMessage() {
        DataChangedHandler.registerDataChangeListener(this);
    }

    public void dataChangeEvent(DataChangeEvent event) {
        //Depending on your protocal, I use Antlr to parse my message
        System.out.println(event.getMessage());
    }


}

//---------------- Netty Handler -----------
public class TelnetClientHandler extends SimpleChannelHandler {

    private static final Logger logger = Logger.getLogger(TelnetClientHandler.class.getName());

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
        String message = (String) e.getMessage();
        DataChangedHandler.fireDataChange(message);
    }
}

我正在尝试按照您的注册监听器指南进行操作,但是我收到了“不兼容类型:无法将字符串转换为DataChangeEvent”的错误消息...出了什么问题?谢谢。 - Phobox

1

您需要在Handler中使用messageReceived()来处理它。我不确定您的问题是什么。我猜想您有一个响应,其取决于请求的内容?也许是您正在进行某些操作的具体描述,其中响应必须知道来自哪个请求。您可以尝试在Handler中传递一个长期存在的对象,该对象知道未完成的请求,并在接收到响应时匹配响应。管道工厂方法可以向Handler传递对管理器类型对象的引用。

这基本上就是我想说的。您的Handler是在PipelineFactory中创建的,因此很容易从那里向Handler传递参数:

    bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
        public ChannelPipeline getPipeline() throws Exception {
            ChannelPipeline pipeline = Channels.pipeline();

            pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()));
            pipeline.addLast("decoder", new XMLDecoder() );
            pipeline.addLast("encoder", new XMLEncoder() );
            // notice here I'm passing two objects to the Handler so it can 
            // call the UI.
            pipeline.addLast("handler", new MyHandler(param1, param2)); 

            return pipeline;
        }
    });

当您创建管道时,您将在新连接上添加处理程序。只需传递一个或多个对象,允许它与UI或控制器进行通信。


例如,在http客户端示例中,结果只是转储到System.out。如果您有一个GUI需要将结果传递给它怎么办?例如,它必须传递到处理程序不知道的JTextArea或者可能是JDialog等。换句话说,按下JButton并且事件通过客户端调用服务器。如何基本地将服务器的结果传递回JButton的调用方法。 - Stephane Grenier
此外,因为JButton的处理程序可能会在JTextArea中显示结果,所以它可能会是一个JDialog等。 - Stephane Grenier
1
你想使用回调(监听器)来处理响应,就像 hotpottao 异步模式 http://hotpotato.biasedbit.com/ 一样(它也使用 Netty),那么你必须在 messageReceived 中处理它 :) ,我认为这种回调默认情况下不提供,因为它们更具体于协议/应用程序的功能是什么? - Jestan Nirojan
我修改了我的答案,并加入了代码来尝试解释我在原始答案中的意思。我会有一个控制器,可以根据需要更新我的UI,并将其传递给处理程序。我不会直接将单个UI元素传递给处理程序,因为这会在我的低级网络和我选择使用的UI组件之间产生过多的耦合。 - chubbsondubs
@chubbard 这就是我缺失的部分。我不理解你所说的:“你可以做的一件事情是将一个长期存在的对象传递给处理程序,该处理程序知道未完成的请求,并在接收到响应时匹配它。管道工厂方法可以向处理程序传递对管理器类型对象的引用。”现在回想起来,我明白了,这很有道理,但是如果没有看到代码或者还在学习框架,尝试理解它就太难了。 - Stephane Grenier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接