JAVA NIO 之 Selector 组件

NIO 重要功能就是实现多路复用。Selector是SelectableChannel对象的多路复用器。一些基础知识:

选择器(Selector):选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。

可选择通道(SelectableChannel):这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的

父类。例如:ServerSocketChannel、SocketChannel。可选择通道可以被注册到选择器上。

选择键(SelectionKey):选择键封装了特定的通道与特定的选择器的注册关系。

前面的一篇文章NIO简介中介绍了传统io的同步阻塞服务器实现,现在来看看NIO多路复用服务器的实现。NIO 利用单线程轮询事件机制,定位就绪的Channel,决定执行什么,

仅仅 select()方法阶段是阻塞的。这样一个选择器避免了之前的多个客服端时切换线程的问题。下面的一张图能描述这种场景:

 

 代码实现:

服务器server:

public class SelectSockets {

    private static int PORT_NUMBER = 9011;

    /**
     * allocateDirect(1024) 此方法创建的buffer无法调用array();直接内存
     */
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] argv) throws Exception {
        new SelectSockets().go(argv);
    }

    public void go(String[] argv) throws Exception {
        System.out.println("Listening on port " + PORT_NUMBER);
        // 创建ServerSocketChannel
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 获得ServerSocket
        ServerSocket serverSocket = serverChannel.socket();
        // 创建Selector
        Selector selector = Selector.open();
        // 绑定
        serverSocket.bind(new InetSocketAddress(PORT_NUMBER));
        // false设置为非阻塞模式
        serverChannel.configureBlocking(false);
        // 注册通道
        ////ServerSocketChannel只能注册SelectionKey.OP_ACCEPT;register(Selector sel, int ops)的ops参数可以通过serverSocketChannel.validOps()获取。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            //选择器select有三种方式,这种带时间的表示,没有连接阻塞10秒后继续或者有连接进来时继续
            int n = selector.select(10000);
            if (n == 0) {
                continue;
            }
            //selectedKeys()已选择的键
            Iterator it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                //检查是否有效
                if (!key.isValid()) {
                    continue;
                }
                //accept
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel channel = server.accept();
                    System.out.println ("Incoming connection from: "+ channel.socket().getRemoteSocketAddress( ));
                    registerChannel(selector, channel, SelectionKey.OP_READ);
                    buffer.clear();
                    buffer.put("你好,我是服务器!\r\n".getBytes());
                    buffer.flip();
                    channel.write(buffer);
                }
                //if(key.isReadable())等价于if((key.readyOps( ) & SelectionKey.OP_READ) != 0)
                if (key.isReadable()) {
                    readHandler(key);
                }
                it.remove();
            }
        }
    }

    /**
     * 设置感兴趣的通道属性
     * @param selector
     * @param channel
     * @param ops
     * @throws Exception
     */
    protected void registerChannel(Selector selector, SelectableChannel channel, int ops) throws Exception {
        if (channel == null) {
            return;
        }
        channel.configureBlocking(false);
        channel.register(selector, ops);
    }

    /**
     * 处理读取数据
     * @param key
     * @throws Exception
     */
    protected void readHandler(SelectionKey key) throws Exception {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        int count;
        StringBuilder sb = new StringBuilder();
        ByteBuffer tmpByteBuffer = ByteBuffer.allocate(1024);
        //读取客服端消息
        while ((count = socketChannel.read(tmpByteBuffer)) > 0) {
            tmpByteBuffer.flip();
            sb.append(new String(tmpByteBuffer.array()));
            // 这里可以回写给客服端
            while (tmpByteBuffer.hasRemaining()) {
                socketChannel.write(tmpByteBuffer);
            }
            tmpByteBuffer.clear();
        }
        System.out.println("客服端"+socketChannel.socket().getRemoteSocketAddress()+"说:"+sb.toString());

        if (count < 0) {
            // Close channel on EOF, invalidates the key
            socketChannel.close();
        }
    }

}

  客服端:

/**
 * @author monkjavaer
 * @date 2018/10/23 22:23
 */
public class Client {

    private static final int BUFFER_SIZE = 1024;
    private static  int PORT = 9011;
    private static String[] messages =
            {"今天读到一句话,觉得很好:但行好事,莫问前程。"};

    public static void main(String[] args) {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            InetSocketAddress address =new InetSocketAddress(inetAddress, PORT);
            SocketChannel socketChannel = SocketChannel.open(address);

            for (String msg: messages) {
                ByteBuffer myBuffer=ByteBuffer.allocate(BUFFER_SIZE);
                myBuffer.put(msg.getBytes());
                myBuffer.flip();
                int bytesWritten = socketChannel.write(myBuffer);
                logger(String.format("Sending Message...: %s\nbytesWritten...: %d",msg, bytesWritten));
            }
            logger("Closing Client connection...");
            socketChannel.close();
        } catch (IOException e) {
            logger(e.getMessage());
            e.printStackTrace();
        }
    }

    public static void logger(String msg) {
        System.out.println(msg);
    }

}

  也可以用telnet命令测试:

    telnet 127.0.0.1 9011

 

posted @ 2018-10-23 22:58  monkjavaer  阅读(250)  评论(0编辑  收藏  举报