【问题标题】:Async reading in server-client: separate client messages服务器-客户端中的异步读取:单独的客户端消息
【发布时间】:2014-10-15 00:37:38
【问题描述】:

我正在使用AsynchronousServerSocketChannel 实现一个简单的服务器。出于测试目的,我创建了一个小型客户端原型,它发送两条消息,"hi""stackoverflow",然后断开连接。在服务器端,我读取到达的消息并将它们打印到标准输出。当客户端执行时,我期望收到:

message [hi], bytecount 2
message [stackoverflow], bytecount 13

问题是,有时当服务器调用读取回调时两条消息都已经到达,所以我得到了

message [histackoverflow], bytecount 15

改为。

问题是,是否可以在服务器端确保消息分别到达,如果可以,如何做?

这是我处理客户端连接的CompletionHandler 原型:

class CommunicationHandler implements CompletionHandler<AsynchronousSocketChannel, Void> {

    private final AsynchronousServerSocketChannel server;

    public CommunicationHandler(final AsynchronousServerSocketChannel server) {
        this.server = server;
    }

    @Override
    public void failed(Throwable ex, Void attachment) {}

    @Override
    public void completed(final AsynchronousSocketChannel client, Void attachment) {

        // handle client messages
        final ByteBuffer buffer = ByteBuffer.allocateDirect(Server.BUFFER_SIZE);
        final Session session = new Session();
        try {
            client.read(buffer, session, new CompletionHandler<Integer, Session>() {

                @Override
                public void completed(Integer byteCount, final Session currSession) {
                    if (byteCount == -1) {
                        return;
                    }
                    buffer.flip();
                    // TODO forward buffer to message handler (probably protocol?)
                    System.out.println("message [" + convertToString(buffer) + "], byteCount " + byteCount);
                    buffer.clear();
                    // read next message
                    client.read(buffer, currSession, this);
            }

            @Override
            public void failed(Throwable ex, final Session currSession) {}
        });
    }
    // accept the next connection
    server.accept(null, this);
}

ByteBufferString 的转换:

public static String convertToString(ByteBuffer bb) {
    final byte[] bytes = new byte[bb.remaining()];
    bb.duplicate().get(bytes);
    return new String(bytes);
}

这是一个测试客户端原型:

public class Client {

public final void start() {
    try (AsynchronousSocketChannel client = AsynchronousSocketChannel.open();) {

        Future<Void> connCall = client.connect(InetAddress.getByName("127.0.0.1"), 8060));
        connCall.get();
        // client is now connected
        // send greeting message
        Future<Integer> writeCall = client.write(Charset.forName("utf-8").encode(CharBuffer.wrap("hi")));
        writeCall.get();
//        Thread.sleep(5000L);
        writeCall = client.write(Charset.forName("utf-8").encode(CharBuffer.wrap("stackoverflow")));
        writeCall.get();
        client.close();
    } catch (IOException e) {
    } catch (InterruptedException ex) {
    } catch (ExecutionException ex) {
    }
}

【问题讨论】:

    标签: java asynchronous nio2


    【解决方案1】:

    除了在一次读取中获得两次(甚至更多)写入的可能性之外,对于较大的消息(通常大约 3k 或更多),您可以将一次写入拆分为多次读取。 TCP 是一种流协议,不保留记录边界,除非偶然:What is a message boundary? 通常有两种解决方案,尽管使用异步通道,我认为您需要自己进行缓冲区管理,这可能会令人困惑和困难测试:

    • 在每条记录前添加显式长度字段

    • 当有一个字节未被使用时,在每条记录后添加一个分隔符,或者可以使用转义符将数据与分隔符区分开来

    和其他几个已经尝试过的:

    • 正如您的评论所建议的,请等待足够长的时间,以便在发送第二个请求之前始终读取第一个请求。在开发人员使用的本地网络和测试系统上,这通常是几毫秒甚至更少;在真实的互联网上,它通常是几秒钟,有时是几分钟,理论上可能是几小时甚至几天。

    • 如果记录的长度永远不会超过几个片段(可能是 10k 左右),请使用 UDP(在 Java 中以 DatagramSocket 的形式提供,但不是作为 NIO 通道 AFAICS)并实现您自己的协议来处理消息丢失、重复和重新排序(这很难做到,并且在 30 年前在 TCP 中发现并避免或修复的一些晦涩的案例中经常以失败告终)

    • 使用 SCTP(在所有 AFAICS 中都没有 Java,也没有太多其他系统)

    除此之外:您的测试客户端以 UTF-8 格式发送数据,但 new String (byte[]) 使用默认编码,该编码取决于平台,不一定是 UTF-8。我不确定它是否得到保证,但实际上所有可用的编码都包括 ASCII 作为子集,并且您的示例数据是 ASCII。但是如果你想为它支持实际的 UTF-8 数据代码。

    【讨论】:

    • 您好,谢谢您的回复。它真的帮了我很多,也迫使我阅读 tcp 协议和它周围的东西:-)
    猜你喜欢
    • 2015-10-08
    • 2019-04-08
    • 2015-06-26
    • 2016-03-18
    • 1970-01-01
    • 2018-04-27
    • 2013-03-02
    • 2014-08-20
    • 1970-01-01
    相关资源
    最近更新 更多