0

我正在使用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);
}

ByteBuffer转换String

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) {
    }
}
4

1 回答 1

2

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

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

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

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

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

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

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

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

于 2014-08-22T03:49:56.247 回答