【问题标题】:Java NIO with InputStream.available()带有 InputStream.available() 的 Java NIO
【发布时间】:2015-05-31 09:49:32
【问题描述】:

美好的一天。

我正在使用 NIO 多路复用,我不想在我的应用程序中分配额外的缓冲区,直到我确定套接字中有足够的字节来读取整个应用程序数据包。每个应用数据包包含 4 个字节的数据包长度(标头)和随后的数据包主体字节。如果可用,我想读取 4 个字节的数据包长度,然后如果可用,则读取数据包主体的字节。所以,代码看起来像这样:

private final ByteBuffer READ_BUFFER = ByteBuffer.wrap(new byte[READ_BUFFER_SIZE]);
...
Iterator<SelectionKey> selectedKeys = selector.selectedKeys().iterator();

while (selectedKeys.hasNext())
{
    SelectionKey key = selectedKeys.next();
    selectedKeys.remove();

    Connection con = (Connection) key.attachment();

    switch (key.readyOps())
    {
        case SelectionKey.OP_ACCEPT:
            acceptConnection(key);
            break;
        case SelectionKey.OP_READ:
            readPacket(key, con);    //**
    }
}
...
private final void readPacket(final SelectionKey key, final Connection con)
{   
    READ_BUFFER.clear();
    int result = -2;

    try
    {
        result = con.read(READ_BUFFER);
    }
    catch (IOException e) {}

    //* packet processing goes here
}

在*中我们可以得到:

  1. 不到 4 个字节来读取标头;
  2. 4 个字节用于标头,但没有足够的字节用于数据包主体
  3. 足够的字节来读取 N 个数据包(标头 + 正文),但是 (N+1) 的一部分 包

在 3 种情况中的任何一种情况下,我都必须将读取的数据存储在额外的 ByteBuffer 中,因为 READ_BUFFER 将用于 ** 中的下一个连接。

我想做什么:

public class Connection
{
    private final ByteChannel byteChannel;
    private final InputStream in;
    private int lastPacketSize = -1;

    Connection(final Socket socket)
    {
        byteChannel = socket.getChannel();
        in = socket.getInputStream();
    }

    int read(final ByteBuffer buf) throws IOException
    {
        return byteChannel.read(buf);
    }

    int available()
    {
        return in.available();
    }

    void setLastPacketSize(int size)
    {
        lastPacketSize = size;
    }

    int getLastPacketSize()
    {
        return lastPacketSize;
    }
}

private final IntBuffer HEADER_BUFFER = IntBuffer.wrap(new int[1]);
private final void readPacket(final SelectionKey key, final Connection con)
{
    int packetSize = con.getLastPacketSize();
    if (packetSize < 0)
    {
        if (con.available() < 4) return;

        int result = -2;
        HEADER_BUFFER.clear();
        try
        {
            result = con.read(HEADER_BUFFER);
        }
        catch (IOException e) {}

        if (result != 4)
        {
            closeConnection(key);
            return;
        }

        packetSize = HEADER_BUFFER.get();
        con.setLastPacketSize(packetSize);
    }

    if (con.available() < packetSize) return;

    result = -2;

    READ_BUFFER.clear();
    READ_BUFFER.limit(packetSize);
    try
    {
        result = con.read(READ_BUFFER);
    }
    catch (IOException e) {}

    if (result != packetSize)
    {
        closeConnection(key);
        return;
    }
    con.setLastPacketSize(-1);

    //packet processing goes here
}

我发现 Socket 的 InputStream 在 Linux 上使用 ioctl(fd, FIONREAD, pbytes)。在这种情况下我可以依赖 con.available() 还是会失败?如果它会失败,原因是什么?如何改进此代码以克服这些原因?

【问题讨论】:

    标签: java sockets nio


    【解决方案1】:

    如果您使用的是非阻塞模式,则根本不能使用InputStream。您永远无法运行此代码。

    您尝试做的事情没有多大意义。每次 OP_READ 触发时读取,直到您有足够的数据来处理。

    【讨论】:

    • 我的想法是只读取数据包长度字节并处理数据包,其余的将保留在套接字缓冲区中。在这种情况下,我只需要长度 >= packetLength 的缓冲区。如果我有太多的客户端并且这些客户端产生了太多的数据包,它们将填满套接字缓冲区,然后是应用程序级缓冲区,然后 TCP 流控制将要求客户端停止发送更多数据。因此,这些客户端中的每一个都需要额外的内存。
    猜你喜欢
    • 1970-01-01
    • 2011-05-02
    • 1970-01-01
    • 2015-07-17
    • 2011-04-11
    • 1970-01-01
    • 2013-05-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多