【问题标题】:SocketChannel.write() writing problemSocketChannel.write() 写入问题
【发布时间】:2011-05-06 03:24:47
【问题描述】:

这里的问题是我可以看到数据正在写入套接字,但并不总是被发送。

这是一个代码片段

ByteBuffer writeBuffer = ByteBuffer.allocate(8192);
writeBuffer.clear();
writeBuffer.put("heartbeat".getBytes());


writeBuffer.flip();
LOG.debug("is connected: " + socketChannel.isConnected());
int bytesWritten = 0;
if (key.isWritable()) {
    while (writeBuffer.hasRemaining()) {
        bytesWritten += socketChannel.write(writeBuffer);
    }
}

我使用 TCPMon 来查看实际数据是否被写入套接字 - 它确实如此。

但是使用 WireShark(另一种网络监控工具)我看不到通过 NIC 的数据包。

任何帮助将不胜感激

【问题讨论】:

    标签: java nio


    【解决方案1】:

    无论如何,您的代码都是错误的。如果写入返回零,则套接字发送缓冲区已满,因此您应该注册 OP_WRITE 并返回选择循环,而不是浪费时间旋转直到再次有空间。您目前的技术使其他服务渠道饥饿并浪费 CPU 周期。

    此外,此时测试isConnected() 是徒劳的。这是。你连接了它。该方法告诉您的是套接字的状态,而不是连接。

    【讨论】:

    • 我确实注册了 OP_WRITE(从上面的代码发出),否则 key.isWritable() 将返回 false。无论如何,到目前为止,bytesWritten 总是返回 > 0 并且从未陷入循环......但是。另外由于某种原因,我以前的 cmets 已被删除。
    • 虽然我同意 hasRemaining() 是邪恶的 - 我会重构。
    • @Vueey 代码仍然错误。有一天你会遇到一个完整的套接字发送缓冲区,我描述的问题会立即出现。事实上,这是 OP_WRITE 的唯一实际用途:告诉您套接字发送缓冲区中的空间何时可用,并且仅在遇到已满缓冲区时使用它才有用。其余时间你可以直接写。
    • EJP:我也不明白。我还被一个 select() 调用返回并且通道的选择键在其 readyOps 字段中具有“OP_WRITE”而被唤醒。所以,我自然会期待下一次调用 channel.write(bufs);不会返回 0... 但它仍然会。
    【解决方案2】:

    如下尝试

    /**
     * @param socketChannel
     * @param buf
     * @return no. of bytes written to the socket
     * @throws IOException
     */
    public static int writeByteBuffer(SocketChannel socketChannel, ByteBuffer buf) throws IOException {
    
        boolean blocking = socketChannel.isBlocking();
        Selector selector = Selector.open();
        int totalWritten = 0;
        try {
    
            socketChannel.configureBlocking(false);
    
            // pass SelectionKey.OP_READ | SelectionKey.OP_WRITE for read and
            // write
            socketChannel.register(selector, SelectionKey.OP_WRITE);
    
            selector.select();
    
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
    
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
    
            outerOfWriting: while (keyIterator.hasNext()) {
    
                SelectionKey key = keyIterator.next();
    
                boolean writable = key.isWritable();
    
                if (writable) {
    
                    SocketChannel channel = (SocketChannel) key.channel();
    
                    boolean hasRemaining = false;
                    while (hasRemaining = buf.hasRemaining()) {
                        int written = channel.write(buf);
                        totalWritten += written;
    
                        if (written == 0) {                         
                            selector.select();
                            selectedKeys = selector.selectedKeys();
                            keyIterator = selectedKeys.iterator();
                            continue outerOfWriting;
                        }
                    }
    
                    if (!hasRemaining) {
                        key.cancel();
                        break;
                    }
                }
            }
    
        } finally {
            try {
                selector.close();
                socketChannel.configureBlocking(blocking);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        return totalWritten;
    }
    
    public static void main(String[] args) {
    
        try {
            ByteBuffer writeBuffer = ByteBuffer.allocate(8192);
            writeBuffer.clear();
            writeBuffer.put("heartbeat".getBytes());
            writeBuffer.flip();
    
            SocketChannel socketChannel = null;//initialize
            writeByteBuffer(socketChannel, writeBuffer);
    
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    【讨论】:

    • 为什么?为什么仅仅因为套接字发送缓冲区已满而关闭选择器?这没有任何意义,当然也不能回答问题。
    • @EJP 如果缓冲区已满(即写入 == 0),那么我正在尝试注册一个新的选择器,因此需要取消并关闭前一个选择器。
    • 我可以看到。我问你为什么,你没有回答。这是毫无意义。您不需要新的选择器。这完全没有任何作用。
    • @EJP 我认为注册新的选择器将利用新的发送缓冲区大小,这样它会使写入速度更快,但你是对的,它没有效果。我现在已经删除了不需要的代码。我在linux机器上遇到过性能问题,即使是300KB的数据写入也很慢,channel.write经常返回0所以我想注册新的选择器会提高写入速度。
    • 你错了。选择器与套接字发送缓冲区大小无关。在本机级别,选择器甚至不存在:它是 Java NIO API 的产物;所以重新创建它除了浪费时间之外没有任何效果。您是否有某些原因没有在回答中给出这个理由?或者当你被问到,两天前?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-20
    • 2021-04-28
    • 1970-01-01
    • 2012-02-10
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多