高并发编程系列之IO模型(二)--------NIO教程

2 篇文章 0 订阅

相关系列文章

1. 高并发编程系列之IO模型(一)-------- BIO教程
2. 高并发编程系列之IO模型(二)-------- NIO教程

NIO和NIO的区别

高并发编程系列之IO模型(一)--------BIO教程

BIONIO
面向的流的操作面向缓冲区
阻塞IO非阻塞IO(能立即返回)
选择器

NIO的三个重要核心

1、Channel(通道)

Channel通道,可以使用它实现数据的读取和写入,通道是双向的可以双向同时读写(流是单项的,BIO中要输入流、输出流),这充分的利用了操作系统的全双工的支持,从而实现更高效率的传输数据。

Channel的实现

类名作用
FileChannel文件读取通道(阻塞模式的通道)
DatagramChannelUDP网络通道
SocketChannelTCP网络通道
ServerSocketChannel监听TCP连接(服务端)

2、Buffer (缓冲区)

缓冲区实际上是一个数组,数据的读取和写入都要经过它,它的内存读写是一块一块的采用映射的方式,速度非常快
CharBuffer,ShortBuffer,IntBuffer,LongBuffer,FloatBuffer,DoubleBuffer等实现


3、Selector(选择器)
一个Selector可以实现监听上千个Channel,Selector只能管理非阻塞的通道,通过轮训监听的关键字(SelectorKey)实现对通道的状态的监听


NIO的实现原理

服务端只有开启一个专门的IO线程,负责事件的分发
NIO中通过套接字通道SocketChannel和ServerSocketChannel,完成同步阻塞、同步非阻塞的连接


Selector监听的事件

事件名事件值
服务端接受客户端事件SelectionKey.OP_ACCEPT
客户端的连接事件SelectionKey.OP_CONNECT
读事件SelectionKey.OP_READ
写事件SelectionKey.OP_WRITE

项目代码

服务端代码:

客户端端代码:


public class NIOClient {

    public static void main(String[] args) {
        new Thread(new NIOClientHandler(null , 8080)).start();
    }
}

客户端处理器

/**
 * @author : GONG ZHI QIANG
 * @data : 2019-09-06 , 15:07
 * @user : SnaChat
 * @project: Netty
 * @description :
 */
public class NIOClientHandler implements Runnable {

    private String host;

    private int port;

    private Selector selector;

    private SocketChannel socketChannel;

    private volatile boolean stop;

    public NIOClientHandler(String host, int port) {
        this.host = host == null ? "127.0.0.1" : host;
        this.port = port;
        try {
            // 创建选择器
            selector = Selector.open();
            // SocketChannel 绑定客户端的本地地址
            socketChannel = SocketChannel.open();
            // 客户端设置为非阻塞模式
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                SelectionKey key = null;
                while (iterator.hasNext()) {
                    key = iterator.next();
                    iterator.remove();
                    try {
                        // 将将要通信的key
                        handleInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     *  真正处理网络请求
     * @param key
     * @throws IOException
     */
    private void handleInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            System.out.println("客户端请求建立的连接成功了吗? " + socketChannel.finishConnect());
            //判断是否连接成功
            if (key.isConnectable()) {

                if (socketChannel.finishConnect()) {
                    System.out.println("客户端连接成功");
                    //连接成功,将key注册到 Selector上 ,SelectionKey.OP_READ:监听网络中的读操作
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    // 将信息发送给 服务端
                    doWrite(socketChannel);
                }
            }
            //当期的key 是可读的 ,那么读取服务端的消息(异步的读取)
            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                int readBytes = socketChannel.read(readBuffer);

                if (readBytes > 0) {
                    readBuffer.flip();
                    byte[] bytes = new byte[readBuffer.remaining()];
                    //将数据读从缓冲区 读取到 数组中
                    readBuffer.get(bytes);
                    String readResult = new String(bytes, "UTF-8");
                    System.out.println("服务端返回的信息:" + readResult);
                    this.stop = true;

                } else {  //读取不到数据
                    key.cancel();
                    socketChannel.close();
                }
            }
        }
    }

    /**
     * 向服务端 发送消息
     * @param socketChannel
     * @throws IOException
     */
    private void doWrite(SocketChannel socketChannel) throws IOException {
        /***  将String类型的字符串 转化 成 byte数组 */
        byte[] clientMessage = ("你好!我是客户端 , Time = " + new Date(System.currentTimeMillis()).toString()).getBytes() ;
        //申请缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(clientMessage.length);
        //将数组的数据写入缓冲区中
        buffer.put(clientMessage);
        //调整指针的位置
        buffer.flip();

        //将数据映射到 Channel中
        socketChannel.write(buffer);
        if (!buffer.hasRemaining()) {
            System.out.println("全部信息发送成功");
        }
    }

    private void doConnect() throws IOException {

        //根据IP和端口号做连接,并将socketChannel绑定到 selector上
        if (socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
            doWrite(socketChannel);
        } else {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }
}

服务端代码:

/**
 * @author : GONG ZHI QIANG
 * @data : 2019-09-06 , 14:31
 * @user : SnaChat
 * @project: Netty
 * @description :
 */
public class NIOServer {

    public static void main(String[] args) throws IOException {
        final int PORT = 8080;

        NIOServerHandler server = new NIOServerHandler(PORT);

        new Thread(server , "NIO-Server--0001").start();
    }
}

服务端处理器

public class NIOServerHandler implements Runnable {

    /**
     *  选择器,
     */
    private Selector selector;

    /**
     * 通道对象ServerSocketChannel
     */
    private ServerSocketChannel serverSocketChannel;

    /**
     *  volatile: 这里使用 volatile,保证多线程中的线程的可见性,用于后面线程的停止
     */
    private volatile boolean stopFlag;

    public NIOServerHandler(int port) throws IOException {

        // 创建选择器
        selector = Selector.open();
        // 监听客户端的连接
        serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 绑定监听端口 , 并且将backlog的值设置为1024
        serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
        // 将Channel注册到 Selectord多路复用器上  , 并且监听SelectionKey.OP_ACCEPT操作
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端已经启动:" + port);
    }

    public void stop() {
        this.stopFlag = true;
    }

    public void run() {
        /**
         * 多线程中 实现轮训 找到准备就绪的 Key
         */
        System.out.println("服务端监测客户端的连接");

        while (!stopFlag) {
            try {
                //设定休眠的时间
                selector.select(1000);
                // 筛选出准备就绪的 Channel的Key,然后可以进行一步的读写操作
                Set<SelectionKey> selectionKeys = selector.selectedKeys();

                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                SelectionKey key = null;
                while (iterator.hasNext()) {
                    key = iterator.next();
                    iterator.remove();
                    try {
                        handlerInput(key);
                    } catch (Exception e) {
                        if (key != null) {
                            key.cancel();
                            if (key.channel() != null) {
                                key.channel().close();
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  这个方法中处理客户端的具体的请求
     * @param key
     * @throws IOException
     */
    private void handlerInput(SelectionKey key) throws IOException {
        if (key.isValid()) {
            // 根据key的类型判断网络处理事件的类型
            if (key.isAcceptable()) {
                //处理新接入的请求消息
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();

                // 此处处理新的连接(来自客户端)
                SocketChannel socketChannel = serverSocketChannel.accept();

                //设置是阻塞模式还是非阻塞模式
                socketChannel.configureBlocking(false);

                //将新的连接 添加到 selector
                socketChannel.register(selector, SelectionKey.OP_READ);
            }
            if (key.isReadable()) {


                SocketChannel socketChannel = (SocketChannel) key.channel();

                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                //读取数据
                int redBytes = socketChannel.read(readBuffer);

                if (redBytes > 0) {
                    //调整缓冲区的指针
                    readBuffer.flip();

                    byte[] bytes = new byte[readBuffer.remaining()];

                    readBuffer.get(bytes);

                    //将读取的字符数组 封装成字符串
                    String message = new String(bytes, "UTF-8");

                    System.out.println("服务端接受到的请求信息 : " + message);

                    String returnMessage = "NIO--服务端已经收到你的请求 , Time = " + new Date(System.currentTimeMillis()).toString();

                    /*** 这里再向客户端返回一条数据*/
                    doReturn(socketChannel, returnMessage + "  , 这是服务端返回数据");
                }
            }
        }
    }

    /**
     * 返回给客户端的信息
     *
     * @param socketChannel
     * @param responseMessage
     * @throws IOException
     */
    private void doReturn(SocketChannel socketChannel, String responseMessage) throws IOException {
        if (responseMessage != null && responseMessage.trim().length() > 0) {
            //将String转换成为 byte[]
            byte[] bytes = responseMessage.getBytes();

            ByteBuffer writerBuffer = ByteBuffer.allocate(bytes.length);

            writerBuffer.put(bytes);

            writerBuffer.flip();

            //将数据写入 Channel中
            socketChannel.write(writerBuffer);
        }
    }
}

这张图挺好的

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值