НИО, модель программирования BIO и нулевой копии

Модель Java IO

  • Java поддерживает в общей сложности три вида сетевой модели программирования / режим ввода-вывода: BIO, НИО, А.И.

BIO

  • Синхронные и блокировки (блокировка традиционного типа), модель реализации сервера для резьбового соединения, что клиент имеет сервер, который нужен запустить поток для обработки, когда запрос на установление соединения, если соединение не делает ничего, чтобы вызвать ненужные накладные нити

  • Это относится к относительно небольшого количества соединений и фиксированной инфраструктуры, ресурсов сервера таким образом на высокий спрос, ограниченные параллельные приложения, JDK1.4 ранее единственным вариантом, но программа проста и легка для понимания.

  • Проблемы:

    • Каждый запрос должен будет создать отдельный поток, Считывает данные с соответствующим клиентом, обработки бизнес, данные записи
    • Когда большое количество одновременных необходимости создавать большое количество потоков для обработки соединений, потребление системных ресурсов
    • После того, как соединение не будет установлено, если текущий поток временно нет данных для чтения, поток будет блокировать на операции чтения, в результате чего отходов нити ресурсов
      Here Вставка рисунка Описание
  • клиент

    public class BIOClient {
    	public static void main(String[] args) {
    		// 通过构造函数创建Socket,并且连接指定地址和端口的服务端
    		try {
    			Socket socket = new Socket(localhost, 6666);
    			//开启一个线程接收消息
    			new ReadMsg(socket).start();
    			
    			System.out.println("请输入信息");
    			PrintWriter pw = null;
    			// 写数据到服务端
    			while (true) {
    				pw = new PrintWriter(socket.getOutputStream());
    				pw.println(new Scanner(System.in).next());
    				pw.flush();
    			}
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    
    	}
    
    	public static class ReadMsg extends Thread {
    		Socket socket;
    
    		public ReadMsg(Socket socket) {
    			this.socket = socket;
    		}
    
    		@Override
    		public void run() {
    			try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
    				String line = null;
    				// 通过输入流读取服务端传输的数据
    				while ((line = br.readLine()) != null) {
    					System.out.printf("%s\n", line);
    				}
    
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
  • сервер

    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("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
                //监听,等待客户端连接
                System.out.println("等待连接....");
                final Socket socket = serverSocket.accept();
                System.out.println("连接到一个客户端");
    
                //就创建一个线程,与之通讯(单独写一个方法)
                newCachedThreadPool.execute(new Runnable() {
                    public void run() { //我们重写
                        //可以和客户端通讯
                        handler(socket);
                    }
                });
    
            }
        }
    
        //编写一个handler方法,和客户端通讯
        public static void handler(Socket socket) {
    
            try {
                System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
                byte[] bytes = new byte[1024];
                //通过socket 获取输入流
                InputStream inputStream = socket.getInputStream();
    
                //循环的读取客户端发送的数据
                while (true) {
    
                    System.out.println("线程信息 id =" + Thread.currentThread().getId() + " 名字=" + Thread.currentThread().getName());
    
                    System.out.println("read....");
                   int read =  inputStream.read(bytes);
                   if(read != -1) {
                       System.out.println(new String(bytes, 0, read
                       )); //输出客户端发送的数据
                   } else {
                       break;
                   }
                }
    
    
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                System.out.println("关闭和client的连接");
                try {
                    socket.close();
                }catch (Exception e) {
                    e.printStackTrace();
                }
    
            }
        }
    }
    

NIO

  • Синхронный неблокирующей, режим сервера реализован поток обработки множества запросов (соединение), то есть запрос на соединение посылается клиентом, зарегистрированных на мультиплексор, мультиплексор соединен с опросом запроса ввода / вывода обработка

    • NIO ориентированного буфера, или блок-ориентированное программирование. Считывание данных буфера с его последующей обработкой, подвижной вперед и назад в буфере по мере необходимости, что повышает гибкость процесса, он может быть использован, чтобы обеспечить высокую масштабируемость неблокируемой сети
    • HTTP2.0 с использованием мультиплексирования техники, таким образом, чтобы использовать одно соединение одновременно обработки множества запросов, а также количество одновременных запросов на несколько порядков больше, чем HTTP1.1
  • Относится к числу соединений велико и относительно короткое соединение (светло-режим) архитектуры, такие, как чат-сервера, система заградительного, межсерверных связи. Программирование является более сложным, JDK1.4 начал поддержку.

    Here Вставка рисунка Описание

  • NIOClient

    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, 尚硅谷~";
            //Wraps a byte array into a buffer
            ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
            //发送数据,将 buffer 数据写入 channel
            socketChannel.write(buffer);
            System.in.read();
    
        }
    }
    
  • NIOServer

    public class NIOServer {
        public static void main(String[] args) throws Exception{
    
            //创建ServerSocketChannel -> ServerSocket
    
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
            //得到一个Selecor对象
            Selector selector = Selector.open();
    
            //绑定一个端口6666, 在服务器端监听
            serverSocketChannel.socket().bind(new InetSocketAddress(6666));
            //设置为非阻塞
            serverSocketChannel.configureBlocking(false);
    
            //把 serverSocketChannel 注册到  selector 关心 事件为 OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    
            System.out.println("注册后的selectionkey 数量=" + selector.keys().size()); // 1
    
    
    
            //循环等待客户端连接
            while (true) {
    
                //这里我们等待1秒,如果没有事件发生, 返回
                if(selector.select(1000) == 0) { //没有事件发生
                    System.out.println("服务器等待了1秒,无连接");
                    continue;
                }
    
                //如果返回的>0, 就获取到相关的 selectionKey集合
                //1.如果返回的>0, 表示已经获取到关注的事件
                //2. selector.selectedKeys() 返回关注事件的集合
                //   通过 selectionKeys 反向获取通道
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                System.out.println("selectionKeys 数量 = " + selectionKeys.size());
    
                //遍历 Set<SelectionKey>, 使用迭代器遍历
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
    
                while (keyIterator.hasNext()) {
                    //获取到SelectionKey
                    SelectionKey key = keyIterator.next();
                    //根据key 对应的通道发生的事件做相应处理
                    if(key.isAcceptable()) { //如果是 OP_ACCEPT, 有新的客户端连接
                        //该该客户端生成一个 SocketChannel
                        SocketChannel socketChannel = serverSocketChannel.accept();
                        System.out.println("客户端连接成功 生成了一个 socketChannel " + socketChannel.hashCode());
                        //将  SocketChannel 设置为非阻塞
                        socketChannel.configureBlocking(false);
                        //将socketChannel 注册到selector, 关注事件为 OP_READ, 同时给socketChannel
                        //关联一个Buffer
                        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
    
                        System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size()); //2,3,4..
    
    
                    }
                    if(key.isReadable()) {  //发生 OP_READ
    
                        //通过key 反向获取到对应channel
                        SocketChannel channel = (SocketChannel)key.channel();
    
                        //获取到该channel关联的buffer
                        ByteBuffer buffer = (ByteBuffer)key.attachment();
                        channel.read(buffer);
                        System.out.println("form 客户端 " + new String(buffer.array()));
    
                    }
    
                    //手动从集合中移动当前的selectionKey, 防止重复操作
                    keyIterator.remove();
    	
                }
    
            }
    
        }
    }
    

NIO на основе нулевой копии

  • объяснение
    • Есть четыре копии традиционного ввода-вывода
      Here Вставка рисунка Описание
    • DMP оптимизирован (через отображение памяти для отображения файла в буфер ядра, в то время как пользователь может делить пространство данных в пространстве ядра. Таким образом, во время передачи по сети, можно уменьшить число раз ядро ​​пространства в пространстве пользователя копии)
      Here Вставка рисунка Описание
    • оптимизация SendFile функции (нет данных после пользовательского режима, буфер ядра, чтобы перейти непосредственно из SocketBuffer, в то же время, так как в пользовательском режиме, и не имеет ничего общего, переключение контекста уменьшается)
      Here Вставка рисунка Описание
    • NIO TranseferTo метод канала, режим нижней SendFile нулевой копии, производительность лучше, чем DMP основе MappedByteBuffer.
  • NewIOClient
    public class ZeroIOClient {
        public static void main(String[] args) throws Exception {
    
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.connect(new InetSocketAddress("localhost", 7001));
            String filename = "protoc-3.6.1-win32.zip";
    
            //得到一个文件channel
            FileChannel fileChannel = new FileInputStream(filename).getChannel();
    
            //准备发送
            long startTime = System.currentTimeMillis();
    
            //在linux下一个transferTo 方法就可以完成传输
            //在windows 下 一次调用 transferTo 只能发送8m , 就需要分段传输文件
            //transferTo 底层使用到零拷贝
            long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
    
            System.out.println("发送的总的字节数 =" + transferCount + " 耗时:" + (System.currentTimeMillis() - startTime));
    
            //关闭
            fileChannel.close();
    
        }
    }
    
  • NewIOServer
    public class ZeroIOServer {
        public static void main(String[] args) throws Exception {
    
            InetSocketAddress address = new InetSocketAddress(7001);
    
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
            ServerSocket serverSocket = serverSocketChannel.socket();
    
            serverSocket.bind(address);
    
            //创建buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
    
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
    
                int readcount = 0;
                while (-1 != readcount) {
                    try {	
                        readcount = socketChannel.read(byteBuffer);	
                    }catch (Exception ex) {
                       // ex.printStackTrace();
                        break;
                    }
                    byteBuffer.rewind(); //倒带 position = 0 mark 作废
                }
            }
        }
    }
    
  • Обратите внимание
    • В Linux метод передачи о переходе может быть завершена
    • О переходе может послать только один 8м вызов в окнах, необходимо сегментировать для передачи файлов
Опубликовано 109 оригинальных статей · вона похвала 47 · просмотров 30000 +

рекомендация

отblog.csdn.net/weixin_43934607/article/details/104417876
рекомендация