NIO基础知识

NIO基础

一、三大组件

1. Channel

读写数据的双向通道

常见Channel:

  • FileChannel
  • DatagramChannel:TCP UDP需要用到
  • SocketChannel:客服端、服务端都可以用
  • ServerSocketChannel:服务端可用

2. Buffer

内存缓存区,暂存数据,用来独写数据

常用Buffer:

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • 。。。。。。

2.1 ByteBuffer结构

重要属性:

  • capacity:容量
  • position:
    • 写模式:代表写入数据的指针位置
    • 读模式:将位置转换为读取位置
  • limit:
    • 写模式:写入限制
    • 读模式:读取的限制

在这里插入图片描述

3. Selector

选择器

3.1 多线程版设计

在这里插入图片描述

3.2 缺点

  • 内存占用高
  • 线程上下文切换成本高
  • 只适合连接少数的场景

3.3 线程池版本

在这里插入图片描述

3.4 缺点

  • 阻塞模式下,线程仅能处理一个socket连接
  • 仅适合短连接场景

3.5 Selector版本

配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下。调用selector的select() 会阻塞知道channel发生了独写就绪事件,这些事件方法就会返回这些事件交给trhead来处理。适合流量比较低的场景
在这里插入图片描述

二、粘包\半包

网络上有多条数据发送给服务端,数据之间使用 \n 进行分割,但是由于某种原因这些数据在接收时被进行了重新组合。

public static void main(String[] args) throws IOException {
        //test1();
        //writer();
        ByteBuffer encode = StandardCharsets.UTF_8.encode("hello,world\ni am zhangsan\nwe are family");
        split(encode);
        encode = StandardCharsets.UTF_8.encode(" together . \n 55555");
        split(encode);
        encode = StandardCharsets.UTF_8.encode(" nonononono . \n");
        split(encode);
        encode = StandardCharsets.UTF_8.encode(" 222222 . \n");
        split(encode);
        encode = StandardCharsets.UTF_8.encode(" 33333333333 . \n");
        split(encode);
        encode = StandardCharsets.UTF_8.encode(" 44444444444444 . \n123456\n");
        split(encode);
    }
    
 /**
     * 粘包/半包,通过使用 /n分割 模拟网络发送的数据包
     * 1、查询ThreadLocal中还有没有上一次没有读取完的数据
     *      1.1 将上一次未获取完数据跟当前数据进行合并
     *      1.2 开启读模式
     *      1.3 验证 position的位置 跟limit的位置是否相同,如果不同证明 数据没有取完
     * 2、读取数据     
     */
    private static void split(ByteBuffer byteBuffer){
        //切换读模式
        Data lastData = dataThreadLocal.get();
        if (Objects.nonNull(lastData)) {
            ByteBuffer lastDataBuffer = lastData.getData();
            ByteBuffer newByteBuffer = ByteBuffer.allocate(byteBuffer.limit() + (lastDataBuffer.limit() - lastDataBuffer.position()));
            while (lastDataBuffer.hasRemaining()){
                newByteBuffer.put(lastDataBuffer.get());
            }
            newByteBuffer.put(byteBuffer);
            newByteBuffer.flip();
            //读取新传入的,将上一次的数据进行合并
            for (int i = 0; i < newByteBuffer.limit(); i++) {
                byte b = newByteBuffer.get(i);
                if ('\n' == b) {
                    int length = i + 1 - newByteBuffer.position();
                    ByteBuffer allocate = ByteBuffer.allocate(length);
                    for (int i1 = 0; i1 < allocate.limit(); i1++) {
                        allocate.put(newByteBuffer.get());
                    }
                    allocate.flip();
                    System.out.println("后续包数据:"+StandardCharsets.UTF_8.decode(allocate));
                }
            }
            if (newByteBuffer.position() != newByteBuffer.limit()) {
                //没有截取完的数据,将后面的数据放入
                Data data = new Data(Thread.currentThread().getName(), newByteBuffer);
                dataThreadLocal.set(data);
            }
        }else {
            //将数据按照转义符进行分割
            for (int i = 0; i < byteBuffer.limit() ; i++) {
                //截取数据
                byte b = byteBuffer.get(i);
                if ('\n' == b) {
                    int length = i + 1 - byteBuffer.position();
                    ByteBuffer allocate = ByteBuffer.allocate(length);
                    for (int i1 = 0; i1 < allocate.limit(); i1++) {
                        allocate.put(byteBuffer.get());
                    }
                    allocate.flip();
                    System.out.println("打印数据:"+StandardCharsets.UTF_8.decode(allocate));
                }
            }
            if (byteBuffer.position() != byteBuffer.limit()) {
                //没有截取完的数据,将后面的数据放入
                Data data = new Data(Thread.currentThread().getName(), byteBuffer);
                dataThreadLocal.set(data);
            }
        }
    }

三、实战

时间服务器

public class TimeServer {


    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                //
            }
        }
        new Thread(new MultiplexerTimeServer(port)).start();
        Thread.currentThread().join();
    }
    
    
    
    public static class MultiplexerTimeServer implements Runnable {
	//创建多路复用器
    private Selector selector;

    //简历服务器管道
    private ServerSocketChannel serverSocketChannel;
        
	//用来控制服务器是否停止
    private volatile boolean stop;


    public MultiplexerTimeServer(int port) {
        try {
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            //设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //绑定套接字
            serverSocketChannel.socket().bind(new InetSocketAddress(port), 1024);
            //注册一个接收的监听位事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("The time server is start int port :" + port);
        } catch (IOException e) {
            System.exit(1);
        }
    }

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


    @Override
    public void run() {
        try {
            while (!stop) {
                //轮询注册在selector上面准备就绪的channel
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                SelectionKey key = null;
                while (keyIterator.hasNext()) {
                    key = keyIterator.next();
                    keyIterator.remove();
                    this.handleInput(key);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 处理事件
     * @param selectionKey
     * @throws IOException
     */
    private void handleInput(SelectionKey selectionKey) throws IOException {
        if (selectionKey.isValid()) {
            //如果是接收事件先处理接收的事件,然后再在管道上面注册一个读的操作位
            if (selectionKey.isAcceptable()) {
                System.out.println("接收到连接事件。。。。");
                ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                SocketChannel sc = channel.accept();
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
            }

            //如果是读事件
            if (selectionKey.isReadable()) {
                System.out.println("接收到读事件。。。。");
                SocketChannel channel = (SocketChannel) selectionKey.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int read = channel.read(byteBuffer);
                byteBuffer.flip();
                if (read > 0) {
                    byte[] bytes = new byte[byteBuffer.remaining()];
                    byteBuffer.get(bytes);
                    String body = new String(bytes, StandardCharsets.UTF_8);
                    System.out.println("The time server receive order :" + body);
                    //返回客户端时间
                    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
                    doWrite(channel, currentTime);
                } else if (read < 0) {
                    selectionKey.cancel();
                    channel.close();
                } else {
                    ; //读取 0 字节 忽略
                }
            }
        }

    }

    private void doWrite(SocketChannel socketChannel, String response) throws IOException {
        if (StringUtils.hasText(response)) {
            byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
            ByteBuffer wrap = ByteBuffer.allocate(bytes.length);
            wrap.put(bytes);
            wrap.flip();
            socketChannel.write(wrap);
        }
    }
}
    

}

时间客户端

public class TimeClient {

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if (args != null && args.length > 0) {
            port = Integer.parseInt(args[0]);
        }
        new Thread(new TimeClientHandle("127.0.0.1", port)).start();
        Thread.currentThread().join();
    }
    
    
    
    public static class TimeClientHandle implements Runnable {

    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;

    public TimeClientHandle(String host, int port) {
        this.host = host == null ? "127.0.0.1" : host;
        this.port = port;

        try {
            selector = Selector.open();
            socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            doConnect();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }

        while (!stop) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
                SelectionKey key = null;
                while (selectionKeyIterator.hasNext()) {
                    key = selectionKeyIterator.next();
                    selectionKeyIterator.remove();
                    try {
                        handleInput(key);
                    } catch (IOException e) {
                        e.printStackTrace();
                        key.cancel();
                        if (key.channel() != null) {
                            key.channel().close();
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }

    /**
     * 创建连接
     * 1.如果直接连接成功,直接注册读事件到多路复用器上
     * 2.没有连接成功的话,注册连接事件到多路复用器上
     * @throws IOException
     */
    private void doConnect() throws IOException {
        if (socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
            doWrite(socketChannel);
        } else {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
    }

    /**
     * 发送请求数据到管道中
     *
     *
     * @param socketChannel
     * @throws IOException
     */
    private void doWrite(SocketChannel socketChannel) throws IOException {
        byte[] bytes = "QUERY TIME ORDER".getBytes(StandardCharsets.UTF_8);
        ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
        byteBuffer.put(bytes);
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        if (!byteBuffer.hasRemaining()) {
            System.out.println("Send order 2 server succeed .");
        }
    }

    /**
     * 先判断key 是否是连接事件,如果连接成功,注册一个读时间发送查询请求
     *
     * @param selectionKey
     * @throws IOException
     */
    private void handleInput(SelectionKey selectionKey) throws IOException {
        if (selectionKey.isValid()) {
            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
            //判断是否连接成功
            if (selectionKey.isConnectable()) {
                System.out.println("接收到服务器连接事件。。。。");
                //完成连接后,注册一个读事件
                if (socketChannel.finishConnect()) {
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    doWrite(socketChannel);
                }
            }

            //读事件准备就绪
            if (selectionKey.isReadable()) {
                System.out.println("接收到服务器读事件。。。。");
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int read = socketChannel.read(byteBuffer);
                if (read > 0) {
                    byteBuffer.flip();
                    byte[] bytes = new byte[read];
                    byteBuffer.get(bytes);
                    String body = new String(bytes, StandardCharsets.UTF_8);
                    System.out.println("Now is :" + body);
                    this.stop = true;
                } else if (read < 0) {
                    selectionKey.cancel();
                    socketChannel.close();
                } else {
                    ; // 读取 0 字节
                }
            }
        }
    }
}


}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值