简体   繁体   English

Java NIO从远程机器上读写

[英]Java NIO reading and writing from distant machine

I'd like to use the NIO to send/receive data to/from a distant machine. 我想使用NIO向/从远程机器发送/接收数据。 I can send or receive data at any time, when i need to send data i just send it without any queries from the distant machine, and the distant machine send me data at regular interval. 我可以随时发送或接收数据,当我需要发送数据时,我只是发送它而没有来自远程计算机的任何查询,并且远程计算机会定期发送给我数据。 I don't understand the NIO mechanism. 我不了解NIO机制。 What generates and read or write event on the Selector SelectionKey ? 什么在选择器SelectionKey上生成和读取或写入事件? Is it possible to use only one ServerSocketChannel on my side, to read data from the distant machine et to write data to it ? 是否可以仅在我这一边使用一个ServerSocketChannel从远程计算机读取数据并将数据写入其中? That is what i understand but i don't see how the writing event can be triggered... Thank you for your explanation. 这就是我的理解,但我不知道如何触发书写事件……谢谢您的解释。

I already did some coding and i can read data coming in from the distant machine, but cannot write. 我已经做过一些编码,可以读取来自远程计算机的数据,但无法写入。 I use Selector and i don't know how can i write data. 我使用选择器,我不知道该如何写数据。 The logged message "handle write" is never written, but in wireshark i can see my packet. 记录的消息“ handle write”从未被写入,但是在wireshark中我可以看到我的数据包。

    public class ServerSelector {

    private static final Logger logger = Logger.getLogger(ServerSelector.class.getName());
    private static final int TIMEOUT = 3000; // Wait timeout (milliseconds)
    private static final int MAXTRIES = 3;
    private final Selector selector;

    public ServerSelector(Controller controller, int... servPorts) throws IOException {
        if (servPorts.length <= 0) {
            throw new IllegalArgumentException("Parameter(s) : <Port>...");
        }
        Handler consolehHandler = new ConsoleHandler();
        consolehHandler.setLevel(Level.INFO);
        logger.addHandler(consolehHandler);

        // Create a selector to multiplex listening sockets and connections
        selector = Selector.open();

        // Create listening socket channel for each port and register selector
        for (int servPort : servPorts) {
            ServerSocketChannel listnChannel = ServerSocketChannel.open();
            listnChannel.socket().bind(new InetSocketAddress(servPort));

            listnChannel.configureBlocking(false); // must be nonblocking to register
            // Register selector with channel.  The returned key is ignored
            listnChannel.register(selector, SelectionKey.OP_ACCEPT);
        }

        // Create a handler that will implement the protocol
        IOProtocol protocol = new IOProtocol();

        int tries = 0;
        // Run forever, processing available I/O operations
        while (tries < MAXTRIES) {
            // Wait for some channel to be ready (or timeout)
            if (selector.select(TIMEOUT) == 0) { // returns # of ready chans
                System.out.println(".");
                tries += 1;
                continue;
            }

            // Get iterator on set of keys with I/O to process
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next(); // Key is a bit mask
                // Server socket channel has pending connection requests?
                if (key.isAcceptable()) {
                    logger.log(Level.INFO, "handle accept");
                    protocol.handleAccept(key, controller);
                }

                // Client socket channel has pending data?
                if (key.isReadable()) {
                    logger.log(Level.INFO, "handle read");
                    protocol.handleRead(key);
                }

                // Client socket channel is available for writing and
                // key is valid (i.e., channel not closed) ?
                if (key.isValid() && key.isWritable()) {
                    logger.log(Level.INFO, "handle write");
                    protocol.handleWrite(key);
                }
                keyIter.remove(); // remove from set of selected keys
                tries = 0;
            }
        }
    }
}

The protocol 协议

    public class IOProtocol implements Protocol {

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

    IOProtocol() {
        Handler consolehHandler = new ConsoleHandler();
        consolehHandler.setLevel(Level.INFO);
        logger.addHandler(consolehHandler);
    }

    /**
     *
     * @param key
     * @throws IOException
     */
    @Override
    public void handleAccept(SelectionKey key, Controller controller) throws IOException {
        SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept();
        clntChan.configureBlocking(false); // Must be nonblocking to register
        controller.setCommChannel(clntChan);
        // Register the selector with new channel for read and attach byte buffer
        SelectionKey socketKey = clntChan.register(key.selector(), SelectionKey.OP_READ | SelectionKey.OP_WRITE, controller);
    }

    /**
     * Client socket channel has pending data
     *
     * @param key
     * @throws IOException
     */
    @Override
    public void handleRead(SelectionKey key) throws IOException {
        Controller ctrller = (Controller)key.attachment();
        try {
            ctrller.readData();
        } catch (CommandUnknownException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
        key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    }

    /**
     * Channel is available for writing, and key is valid (i.e., client channel
     * not closed).
     *
     * @param key
     * @throws IOException
     */
    @Override
    public void handleWrite(SelectionKey key) throws IOException {

        Controller ctrl = (Controller)key.attachment();
        ctrl.writePendingData();
        if (!buf.hasRemaining()) { // Buffer completely written ?
            // Nothing left, so no longer interested in writes
            key.interestOps(SelectionKey.OP_READ);
        }
    buf.compact();
    }
}

The controller 控制器

    /**
     * Fill buffer with data.
     * @param msg The data to be sent
     * @throws IOException 
     */
    private void writeData(AbstractMsg msg) throws IOException {
//        
        writeBuffer = ByteBuffer.allocate(msg.getSize() + 4);
        writeBuffer.putInt(msg.getSize());
        msg.writeHeader(writeBuffer);
        msg.writeData(writeBuffer);
        logger.log(Level.INFO, "Write data - message size : {0}", new Object[]{msg.getSize()});
        logger.log(Level.INFO, "Write data - message : {0}", new Object[]{msg});
    }

    /**
     * Write to the SocketChannel
     * @throws IOException 
     */
    public void writePendingData() throws IOException {
        commChannel.write(writeBuffer);
    }

ServerSocketChannel is used to make a connection, but not send data. ServerSocketChannel用于建立连接,但不发送数据。 You need one ServerSocketChannel and one SocketChannel per each connection. 每个连接需要一个ServerSocketChannel和一个SocketChannel

Examples of reading and writing using SocketChannel : 使用SocketChannel进行读写的示例:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

Your program will sleep at second line until data will come. 您的程序将在第二行进入睡眠状态,直到数据到来。 You need to put this code in infinite loop and run it in background Thread . 您需要将此代码置于无限循环中,并在后台Thread运行它。 When data came you can process it from this thread, then wait for another data to come. 当数据到来时,您可以从该线程处理它,然后等待其他数据到来。

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put("Hello!".getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

There is no blocking methods, so if you sending small byte buffer you can call this from your main Thread . 没有阻塞方法,因此,如果发送小字节缓冲区,则可以从主Thread调用此方法。

Source 资源

ADD: Don't set OP_WRITE key on new connection. 添加:不要在新连接上设置OP_WRITE键。 Only OP_READ . OP_READ When you want to write some data you need to notify selector that you want to send something and send it in events loop. 当您要写入一些数据时,需要通知选择器您要发送某些内容并在事件循环中发送它。 Good solution is to make a Queue of outcoming messages. 好的解决方案是将待发邮件放入Queue Then follow this steps: 然后按照以下步骤操作:

  • adding data to Queue Queue添加数据
  • setting OP_WRITE to channel's key OP_WRITE设置为通道的键
  • in while (keyIter.hasNext()) loop you'll have writable key , write all data from queue and remove OP_WRITE key. while (keyIter.hasNext())循环中,您将具有writable key ,从队列写入所有数据并删除OP_WRITE密钥。

It's hard for me to understand your code, but I think you'll find out what's the problem. 我很难理解您的代码,但我想您会发现问题所在。 Also if you want to have only one connection there is no need to use Selector . 另外,如果只想建立一个连接,则无需使用Selector And this is weird that you binding few ServerSocketChannels . 绑定几个ServerSocketChannels很奇怪。

I would suggest you use blocking NIO (which is the default behaviour for SocketChannel) You don't need to use a Selector but you can use one thread for reading and another for writing. 我建议您使用阻塞NIO(这是SocketChannel的默认行为),您无需使用选择器,但可以使用一个线程进行读取,而使用另一个线程进行写入。


Based on your example. 根据您的示例。

private final ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024*1024);

private void writeData(AbstractMsg msg) {
    writeBuffer.clear();
    writeBuffer.putInt(0); // set later
    msg.writeHeader(writeBuffer);
    msg.writeData(writeBuffer);
    writeBuffer.putInt(0, writeBuffer.position());

    writeBuffer.flip();
    while(writeBuffer.hasRemaining())
        commChannel.write(writeBuffer);
}

What generates and read or write event on the Selector SelectionKey? 什么在选择器SelectionKey上生成和读取或写入事件?

OP_READ: presence of data or an EOS in the socket receive buffer. OP_READ:套接字接收缓冲区中存在数据或EOS。

OP_WRITE: room in the socket send buffer. OP_WRITE:在套接字发送缓冲区中的空间。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM