NIO非阻塞式I/O通信说明

以下是本人所理解的原理图:
这里写图片描述

用代码来解释吧:

/**
 * NIO 非阻塞时IO
 * 阻塞式 IO问题:
 * 1、客户端过多时,要对每一个client都要创建线程 ServerSocket.accept()处理,会导致创建大量线程,每个线程都要占用占空间和cpu时间
 * 2、阻塞可能导致上下文切换,且大部分切换无意义。
 *
 * NIO特点:
 * 1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。
 2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。
 3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
 *
 */

/**
 * 原理:
 * 服务端和客户端各维护一个管理通道的对象selector,监听各个通道上的事件
 * 服务端:
 * 如果selector注册了读事件,客户端发数据过来,NIO服务端就会在selector添加一个读事件。
 * 服务端的处理线程会轮询访问selector,如果访问selector时发现有感兴趣的时间到达,则处理这些事件
 * 如果没有感兴趣的事件,则处理线程会一直阻塞直到感兴趣的事件到达为止。
 *
 *
 通道上可以注册的事件:
 key.isAcceptable()
 key.isConnectable()
 key.isReadable()
 key.isWritable()

 key.isValid()
 *
 */

/**
 * NIO服务端
 */
class NIOServer{
    //通道管理器
 private Selector selector;

    /**
 * 获得一个ServerSocket通道,并对该通道做一些初始化工作
 * @param port
 */
 public void initServer(int port) throws IOException {

        /**
 * 1、打开一个ServerSocketChannel
 * channel是client和server对接的通道,client selector一旦接到其注册的事件,就会推给channel;
 * server selector从channel拿到的事件如果是其之前注册的,则推给处理线程进行处理
 * 2、设置非阻塞
 * 3、将sockect绑定到指定端口
 * 4、打开通道管理器
 * 5、管理器注册事件:服务端接收客户端连接事件SelectionKey.OP_ACCEPT
 */
 //1
 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2
 serverSocketChannel.configureBlocking(false);
        //3
 serverSocketChannel.bind(new InetSocketAddress(port));
        //4
 this.selector = Selector.open();
        //5
 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    }

    /**
 * 处理线程组采用轮询的方式监听selector是否存在需要处理的事件,有则处理
 */
 public void selectorListener() throws IOException {

        System.out.println("========== start selector ");
        //轮询处理线程访问selector,注册的事件没到达时,一直阻塞
 while(true){
            //select其实就是监控所有的处理线程
 selector.select();
            //遍历注册事件
 Iterator i = this.selector.selectedKeys().iterator();
            while (i.hasNext()){
                SelectionKey key = (SelectionKey) i.next();
                //删除已选key防止重复处理
 i.remove();
                //判断是否是客户端请求连接事件 SelectionKey.OP_ACCEPT
 if (key.isAcceptable()){
                    //获取服务器上当前处理线程的channel
 ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    //获取和客户端的连接通道
 SocketChannel channel = server.accept();
                    //设置成非阻塞式
 channel.configureBlocking(false);
                    //像客户端发送一条消息
 channel.write(ByteBuffer.wrap(new String("send to client....").getBytes()));
                    //连接成功后为了收到client的消息,需要给通道设置读权限
 channel.register(this.selector,SelectionKey.OP_READ);

                }else if (key.isReadable()){
                    //此时的selector监控到的处理线程是进行处理读客户端的处理事件
 read(key);
                }
            }
        }
    }

    //读取client发来的消息
 public void read(SelectionKey key) throws IOException {
         //获取事件发生的连接通道
 SocketChannel channel = (SocketChannel) key.channel();
        //创建缓冲区
 ByteBuffer buffer = ByteBuffer.allocate(50);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("====server接收到的消息: "+msg);
        ByteBuffer out = ByteBuffer.wrap(msg.getBytes());
        //将消息回送到客户端
 channel.write(out);

    }
    /**
 * 服务端启动
 * @param str
 * @throws IOException
 */
 public static void main(String[] str) throws IOException {

        NIOServer server = new NIOServer();
        server.initServer(8000);
        server.selectorListener();
    }
}

class NIOClient{

    //通道管理器,监听客户端线程事件
 private Selector selector;

   //对selector初始化,注册监听事件
 public void initClient(String ip,int port) throws IOException {
        // 获得一个Socket通道
 SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
 channel.configureBlocking(false);
        // 获得一个通道管理器
 this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
 //用channel.finishConnect();才能完成连接
 channel.connect(new InetSocketAddress(ip,port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
 channel.register(selector, SelectionKey.OP_CONNECT);


    }

    /**
 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
 * @throws IOException
 */
 public void listen() throws IOException {

        System.out.println("=======client start to listen");
        // 轮询访问selector
 while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
 Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
 ite.remove();
                // 连接事件发生
 if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key
                            .channel();
                    // 如果正在连接,则完成连接
 if(channel.isConnectionPending()){
                        channel.finishConnect();

                    }
                    // 设置成非阻塞
 channel.configureBlocking(false);

                    //在这里可以给服务端发送信息哦
 channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
 channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件
 } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }
    /**
 * 处理读取服务端发来的信息 的事件
 * @param key
 * @throws IOException
 */
 public void read(SelectionKey key) throws IOException{
        //获取事件发生的连接通道
 SocketChannel channel = (SocketChannel) key.channel();
        //创建缓冲区
 ByteBuffer buffer = ByteBuffer.allocate(50);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("====client接收到的消息: "+msg);
    }


    /**
 * 启动客户端测试
 * @throws IOException
 */
 public static void main(String[] args) throws IOException {
        NIOClient client = new NIOClient();
        client.initClient("localhost",8000);
        client.listen();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值