简体   繁体   English

Java,SocketChannel选择器,将写通道与阻塞队列结合在一起

[英]Java, SocketChannel selector, combine write channel with blocking queue

I am currently trying to operate a SocketChannel from one thread (I previously achieved what I wanted to do with two thread and regular sockets, but two threads per client seemed a little excessive). 我目前正在尝试从一个线程操作一个SocketChannel(我以前实现了我想使用两个线程和常规套接字的功能,但是每个客户端两个线程似乎有点多余)。 I want to be able to read when there is data to read (selector works fine for that). 我希望能够在有要读取的数据时进行读取(选择器可以正常工作)。 I only want to write when there are items in a blocking queue (in my example, I have frame queue). 我只想在阻塞队列中有项目时写(在我的示例中,我有帧队列)。

        @Override
        public void run() {
            super.run();

            SelectionKey readKey = null;
            try {
                final int interests = SelectionKey.OP_READ;
                socketChannel.configureBlocking(false);
                readKey = socketChannel.register(selector, interests);
            } catch (IOException e) {
                e.printStackTrace();
                try {
                    close();
                } catch (Exception e1) {
                    throw new RuntimeException("FAILURE");
                }
            }

            if (null != readKey) {
                while (running) {
                    try {
                        System.out.println("LOOP ENTRY");
                        selector.select();

                        if (readKey.isReadable()) {
                            System.out.println("IS READABLE");
                        }

                        if (readKey.isWritable() && (null != framesQueues.peek())) {
                            System.out.println("IS WRITEABLE");
                        }

                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

Now, what it does is, it loops furiously without stopping, and that's obviously bad. 现在,它所做的是,它不断循环而没有停止,这显然很糟糕。 I'm looking for a way I could have my selector wakeup when there is an item in my blocking queue, or when there are bytes to read. 我正在寻找一种方法,当阻塞队列中有一个项目或需要读取的字节时,可以唤醒选择器。 Is there a tooling in NIO that allows that? NIO中是否有工具可以做到这一点?

If not, what could I implement? 如果没有,我该怎么办? Am I doomed of using two threads per client? 我注定要为每个客户端使用两个线程吗? It's for a hobby project, but I'm trying to achieve as low latency as possible, so looping with a sleep is not what I want. 这是一个业余项目,但是我正在尝试实现尽可能低的延迟,因此我不想要睡眠循环。

I was messing around with nio sockets, and I threw something together which is hopefully easy enough to understand. 我当时正忙于使用nio套接字,我把一些东西放在一起,希望它很容易理解。 All you need to do is telnet localhost 5050 . 您需要做的就是telnet localhost 5050 I don't have access to the rest of your code, so I have no idea what you're missing. 我无权访问其余代码,因此我不知道您缺少什么。 I would assume that you aren't clearing the selected keys from the selector though, or possible not changing the interest ops to (READ) only once you are finished writing. 我认为您虽然没有从选择器中清除选定的键,或者可能仅在完成写入后才将兴趣操作更改为(READ)。

public static void main(String... args) throws IOException {
    final Selector selector = Selector.open();

    //every 10 seconds this thread will go through all the connections and
    //send "(x times) (date) to every client
    new Thread() {
        public void run() {
            for (int i = 0; selector.isOpen(); i++) {
                for (SelectionKey key : selector.keys()) {
                    if (key.channel() instanceof SocketChannel) {
                        ((Queue<ByteBuffer>) key.attachment()).add(ByteBuffer.wrap((i + " - " + new Date() + "\n").getBytes()));
                        key.interestOps(OP_READ | OP_WRITE); //enable write flag
                    }
                }

                selector.wakeup(); //wakeup so it can get to work and begin writing
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                }
            }
        }
    }.start();


    //create server on port 5050
    ServerSocketChannel server = ServerSocketChannel.open();
    server.configureBlocking(false);
    server.bind(new InetSocketAddress(5050));
    server.register(selector, OP_ACCEPT);

    //reusable buffer
    final ByteBuffer readBuffer = ByteBuffer.allocate(0x1000);

    while (selector.isOpen()) {
        int selected = selector.select();
        System.out.println("Selected " + selected + (selected == 1 ? " key." : " keys."));
        if (selected > 0) {
            for (SelectionKey key : selector.selectedKeys()) {
                if (key.isValid() && key.isReadable()) {
                    System.out.println("Readable: " + key.channel());
                    SocketChannel socket = ((SocketChannel) key.channel());
                    readBuffer.clear();
                    int read = socket.read(readBuffer);
                    if (read == -1) {
                        System.out.println("Socket Closed " + key.channel());
                        socket.close();
                        continue; //socket is closed. continue loop
                    }

                    //we will add what the client sent to the queue to echo it back
                    if (read > 0) {
                        readBuffer.flip();
                        ByteBuffer buffer = ByteBuffer.allocate(readBuffer.remaining());
                        ((Queue<Buffer>) key.attachment()).add(buffer.put(readBuffer).flip());
                        key.interestOps(OP_WRITE | OP_READ); //enable write flag
                    }
                }

                if (key.isValid() && key.isWritable()) {
                    System.out.println("Writable: " + key.channel());
                    SocketChannel socket = (SocketChannel) key.channel();

                    //retrieve attachment(ArrayBlockingQueue<ByteBuffer>)
                    Queue<Buffer> dataToWrite = (Queue<Buffer>) key.attachment();

                    //only remove from queue once we have completely written
                    //this is why we call peek first, and only remove once (buffer.remaining() == 0)
                    for (ByteBuffer buffer; (buffer = (ByteBuffer) dataToWrite.peek()) != null;) {
                        socket.write(buffer);
                        if (buffer.remaining() == 0) dataToWrite.remove();
                        else break; //can not write anymore. Wait for next write event
                    }

                    //once queue is empty we need to stop watching for write events
                    if (dataToWrite.isEmpty()) key.interestOps(OP_READ);
                }

                if (key.isValid() && key.isAcceptable()) {
                    System.out.println("Acceptable: " + key.channel());
                    SocketChannel socket = ((ServerSocketChannel) key.channel()).accept();
                    socket.configureBlocking(false);

                    //add a ArrayBlockingQueue<ByteBuffer> as an attachment for the socket
                    socket.register(selector, OP_READ, new ArrayBlockingQueue<ByteBuffer>(1000));
                }
            }
            selector.selectedKeys().clear(); //must clear all when finished or loop will continue selecting nothing
        }
    }
}

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

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