netty-lean1

IO四大模型

1、同步阻塞IO(Blocking IO)
2、同步非阻塞IO(Non-blocking IO)
3、IO多路复用模型(IO Multiplexing)
4、异步IO模型(Asynchronous IO)

BIO(blocking I/O) 同步阻塞

  • 问题分析
    1-每个请求创建独立线程,与对应的客户端进行数据Read,业务处理,数据Write
    2-当并发数较大时,需要创建大量线程来处理连接,系统资源占用较大。
    3-连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read操作上,造成线程资源浪费。
----BIO进行通信

public class BIOServer {
    
    public static void main(String[] args) throws Exception {
        // 线程池
        // 如果有客户端连狙就创建一个线程与之通信
        // 创建一个线程池
        ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
        // 创建serversocket
        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服务器启动了。。。");

        while (true){
            System.out.println("等待连接。。。");
            // 监听等待客户端连接
            final Socket socket = serverSocket.accept();
            System.out.println("----连接:------------------------------------------");
            System.out.println("连接到了一个客户端");

            //创建一个线程与之通信
            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    // 可以和客户端通讯\
                    handler(socket);
                }
            });
        }
    }

    // 编写一个handler方法 和客户端通讯
    public static void handler(Socket socket){
        try {
            System.out.println("线程信息 id="+Thread.currentThread().getId());
            System.out.println("线程信息 name="+Thread.currentThread().getName());
            System.out.println("等待读取。。。");

            byte[] bytes = new byte[1024];
            // 通过socket获取一个输入流
            InputStream inputStream = socket.getInputStream();
            // 循环读取客户端发送的数据
            while (true){
                System.out.println("----读取信息-------------------------------------");
                System.out.println("线程信息 id="+Thread.currentThread().getId());
                System.out.println("线程信息 name="+Thread.currentThread().getName());
                int read = inputStream.read(bytes);
                if (read != -1){
                    System.out.println(new String(bytes,0,read));
                }else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭cliant连接");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

NIO(non-blocking I/O) 同步非阻塞

  • 三大核心部分
    1-Selector(选择器)
    2-Channel(通道)
    3-Buffer(缓冲区)

  • Selector、Channel、Buffer的关系说明:
    1-每个channel都会对应一个Buffer
    2-Selector对应一个线程,一个线程对应多个channel
    3-程序切换哪个channel由事件决定,Event就是一个重要概念
    4-selector会根据不同事件在各个通道切换
    5-buffer就是要给内存块,底层是有一个数组
    6-数据读取写入通过Buffer
    ,NIO的Buffer是可以读也可以写,需要flip方法切换。
    ,BIO中要么是输入流,或者是输出流,不能双向。
    7-channerl是双向的,可以返回底层操作系统的情况,如linux底层的操作系统通道是双向的。

Buffer

  • 重要属性
    【capsity】容量大小
    【limit】缓冲区当前终点
    【position】位置
    【mark】标志

  • buffer方法:
    【flip()】就是把position的位置改变回0,以实现读写转换,读写实质就是position位置改变。
    【clear()】把标志位重置一下

Channel

channel是Nio中的一个接口
【SoketChannel】 类似 Soket
【ServerSoketChannel】类似ServerSoket
【FileChannel】用于文件的数据读写,
【DatagramChannel】用于UDP的数据读写,
【ServerSocketChannel和 SocketChannel】用于TCP的数据读写。

  • Channel方法:
    【.writer()】buffer内容写入channel
    【.read()】buffer读取channel内容
    【.transferFrom(oherChannel,startPosition,endPosition)】把其他channel数据复制到当前channel
    【.transferTo(oherChannel,startPosition,endPosition)】把当前channel数据复制到其他channel

  • 文件拷贝

----通过buffer进行copy
public class NIOFileChannel03copy {
    
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("D:\\Users\\xiaoaiying\\IDEAProjects\\netty\\NettyPro\\1.txt");
        FileChannel fileChannel_in = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Users\\xiaoaiying\\IDEAProjects\\netty\\NettyPro\\2.txt");
        FileChannel fileChannel_out = fileOutputStream.getChannel();

        //buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while (true){

            // 重要步骤,把buffer标志位恢复到初始状态,否则循环读写时如果一次没读完可能会发生错误
            byteBuffer.clear();

            // 循环将读取到的数据放入buffer,
            int read = fileChannel_in.read(byteBuffer);
            if (read == -1){
                break;
            }

            // 将buffer内数据写出
            // 记得先反转buffer
            byteBuffer.flip();

            fileChannel_out.write(byteBuffer);
        }
    }
}
----通过channel封装好的方法copy
public class NIOFileChannel04copy {
    
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("D:\\Users\\xiaoaiying\\IDEAProjects\\netty\\NettyPro\\1.txt");
        FileChannel fileChannel_in = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("D:\\Users\\xiaoaiying\\IDEAProjects\\netty\\NettyPro\\2.txt");
        FileChannel fileChannel_out = fileOutputStream.getChannel();


        fileChannel_in.transferTo(0, fileChannel_in.size(),fileChannel_out);
        // 或者
//        fileChannel_out.transferFrom(fileChannel_in,0,fileChannel_in.size());

        fileChannel_in.close();
        fileChannel_out.close();
        fileInputStream.close();
        fileOutputStream.close();
    }
}
  • buffer只读
    【.asReadonlyBuffer()】buffer方法,返回一个只读buffer

  • MappedByteBuffer

public class MappedByteBufferTest {
    
    public static void main(String[] args) throws Exception{
        RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\Users\\xiaoaiying\\IDEAProjects\\netty\\NettyPro\\1.txt","rw");
        FileChannel channel = randomAccessFile.getChannel();

        /**
         * MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成
         * 参数1:Filechannel.MapMode . READ_wRITE使用的读写模式
         * 参数2:可以直接修改的起始位置
         * 参数3:是映射到内存的大小,即将文件的多少个字节映射到内存
         * 可以直接修改的范围就是0-5,不包括5
         * 实际类型DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(
                FileChannel.MapMode.READ_WRITE,
                0,
                5);

        // 修改内容
        mappedByteBuffer.put(0,(byte)'H');
        mappedByteBuffer.put(3,(byte)'9');
    }
}
  • buffer的 分散和聚集
    【Scattering】[分散] 将数据写入到buffer时,可以采用buffer数组,依次写入
    【Gathering】[聚集] 从buffer读取数据时,可以采用buffer数组,依次读
public class ScatteringAndGatheringTest {

    public static void main(String[] args) throws Exception {
        // 使用
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        // 绑定端口到socket,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);

        // 创建buffer数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        // 等待客户端连接
        SocketChannel socketChannel = serverSocketChannel.accept();

        // 假定从客户端接收8字节
        int messageLenght = 8;
        // 循环读取
        while (true){
            int byteRead = 0;
            while (byteRead < messageLenght){
                long l = socketChannel.read(byteBuffers);
                byteRead += l; // 累计读取的字节数
                System.out.println("byteRead=" + byteRead);
                // 使用流打印,看看当前这个buffer的position和limit
                Arrays.asList(byteBuffers)
                        .stream()
                        .map(buffer -> "postion="+buffer.position()+",limit="+buffer.limit())
                        .forEach(System.out::println);

                // 将所有buffer进行flip
                Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());

                // 将数据读出显示到客户端
                long byteWrite = 0;
                while (byteWrite < messageLenght){
                    long wlen = socketChannel.write(byteBuffers);
                    byteWrite += wlen;
                }

                // 将所有buffer进行flip
                Arrays.asList(byteBuffers).forEach(buffer -> buffer.clear());
                System.out.println("byteRead="+byteRead +"byteWrie="+byteWrite + ",messagelenght="+messageLenght);
            }
        }
    }
}

selector

selector能够检测多个注册的通道上是否有事件发生,(多个channel以事件的方式可以注册到同一个selector)

  • selector
    selector方法:
    【open()】得到一个选择器对象
    【select()】
    【selectedKeys()】获得selectKey集合

  • selectionKey

selectionkey方法:
【selector()】得到与之关联的selector
【channel()】 得到与之关联的channel
【attachment()】 得到与之关联的buffer,即数据
public abstract SelectionKey interestOps(int ops); //设置或改变监听事件
public final boolean isAcceptable(); //是否可以accept
public final boolean isReadable();//是否可以读
public final boolean isWritable(); //是否可以写

selectionKey对应channel,channel绑定到selector

  • nio操作
----服务端
public class NIOserver {
    public static void main(String[] args) throws Exception {
        // 创建serversoketChaneel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        serverSocketChannel.socket().bind(new InetSocketAddress(6666)); // 绑定端口
        serverSocketChannel.configureBlocking(false);// 设置非阻塞

        // 得到一个selector对象 selector开始监听
        Selector selector = Selector.open();
        // 把serverSocketChannel注册到selector   关系事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环等待客户端连接
        while (true){
            // 等待1秒,如果没有事件发生
            if (selector.select(3000) == 0){
                System.out.println("服务器等待了3秒,无连接");
                continue;
            }

            // 如果返回>0 获取到selectionKey集合
            // selector.selectedKeys() 返回关注事件集合
            // 反向获取通道
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()){// 遍历有操作的selectionkey
                SelectionKey key = keyIterator.next();// 获取到selectionkey

                if (key.isAcceptable()){ // 如果事件是OP_ACCEPT  有新客户端连接
                    // 给该客户端生成一个socketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);// 将socketChannel设置为非阻塞
                    System.out.println("客户端连接成功 生成一个socketChannel:"+socketChannel.hashCode());

                    // 将当前socketChannel 注册到selector  关注事件为OP_READ  通过socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (key.isReadable()){  // 如果事件是OP_READ
                    // 通过key 反向获取channel
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    socketChannel.configureBlocking(false);
                    // 获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    socketChannel.read(buffer);
                    System.out.println("form client:"+ new String(buffer.array()));

                }
                // 手动从集合中移动当前的selectionKey 防止重复操作
                keyIterator.remove();
            }
//        System.out.println("无连接,关闭!!!");
        }
    }
}

----客户端
public class NIOclient {
    public static void main(String[] args) throws Exception {
        // 得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置非阻塞
        socketChannel.configureBlocking(false);
        // 提供服务器端的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);

        // 连接服务器
        if (!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作。。。");
            }
        }
        // 如果连接成功就放数据
        String str = "hello,小艾";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        // 发动数据,将buffer数据写入channel
        socketChannel.write(buffer);
        System.in.read();
    } 
}

----

DMA : direct memory access直接内存拷贝(不使用CPU)
0拷贝:从操作系统角度看,即没有cpu拷贝

posted @ 2021-01-17 14:31  小艾影  阅读(102)  评论(0编辑  收藏  举报