Java IO模型:BIO、NIO、AIO

Java IO模型:BIO、NIO、AIO

本来是打算直接学习网络框架Netty的,但是先补充了一下自己对Java 几种IO模型的学习和理解。分别是 BIO、NIO、AIO三种IO模型。

IO模型的基本说明

BIO模型图

缺点:

  1. 如果有很多个Client,则会产生很多个线程。压力主要是在服务器端。客户端的压力并不大。
  2. 另外建立连接之后,并不是在时时刻刻的使用。会有空间时间。
  3. 会阻塞。

NIO模型图

特点:

  1. 事件驱动
  2. 多路复用
  3. Netty底层使用的NIO模型

AIO模型

目前还未得到广泛运用。异步非阻塞。先了解就可以。

BIO、NIO、AIO使用场景分析

  1. BOI方式使用与连接数目比较小固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。JDK1.4以前的唯一选择。但是程序简单容易理解。
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务期间通讯等。编程比较复杂,JDK1.4开始支持。
  3. AIO方式适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS操作系统参与并发操作,编程比较复杂,JDK7开始支持。但是目前还未得到广泛运用。

JAVA BIO编程

JAVA BIO 基本介绍

JAVA BIO 工作机制

JAVA BIO 应用实例

package com.dawa.netty.bio;

import com.sun.org.apache.xpath.internal.operations.String;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 功能需求
 * 1. 使用BIO模型编写一个服务器,监听6666端口,当有客户连接的时候,就启动一个客户端线程与之连接
 * 2. 要求使用县城连接机制,可以连接过个客户端
 * 3. 服务器端可以接受客户端发送的数据(TeInet方法即可)
 */
public class TestBIO {
    public static void main(String[] args) throws Exception {
        //1. 创建一个线程池. 这里 借助 Executors 这个工具类
        ExecutorService pool = Executors.newCachedThreadPool();
        //2. 建立一个监听服务,用来监听客户端连接
        ServerSocket serverSocket = new ServerSocket(6666);

        while (true) {
            final Socket socket = serverSocket.accept();
            System.out.println("一个客户端连接");
            //就创建一个线程与之通信
            pool.execute(new Runnable() {
                public void run() {
                    //编写一个处理方法.
                    handler(socket);
                }
            });
        }
    }

    public static void handler(Socket socket) {
        byte[] bytes = new byte[1024];

        try (InputStream inputStream = socket.getInputStream()) {
            while (true) {
                int read = inputStream.read(bytes);
                if (read != -1) {
                    //注意这里,不能用String转换了.因为String已经不支持有参数的构造方法.
                    System.out.println(Arrays.toString(bytes));
                } else {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            System.out.println("关闭连接");
        }

    }
}

JAVA BIO 问题分析

JAVA NIO编程

NIO 基本介绍

  1. NIO的三个核心,channel相当于IO的Socket
  2. Buffer,Channel,Selector(选择器)三大核心组件。
  3. 通过Buffer实现非阻塞,
  4. 面向缓冲区,或者面向块编程(Buffer就是这样的)。
  5. NIO是事件驱动的

NIO Buffer 基本使用

这没有BooleanBuffer,另外StringBuffer继承自StringBuilder.

一个简单的Buffer子类的使用案例如下

package com.dawa.netty.bio;

import java.nio.IntBuffer;

public class TestNIO {
    public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(5);

        for (int i = 0; i < intBuffer.capacity(); i++) {
            System.out.println(intBuffer.put(i*5));
        }

        intBuffer.flip();

        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

NIO 和 BIO 的比较

NIO 三大核心原理示意图

Selector、Channel和Buffer的关系图

  1. 每个Channel都会对应一个Buffer。
  2. Selector对应一个线程,一个线程对应多个Channel(连接)。
  3. 该图反应了有三个Channel注册到该Selector
  4. 程序切换到哪个Channel是由事件决定的。Event是一个重要概念。
  5. Select会根据不同的事件,在各个通道上切换。
  6. Buffer就是一个内存块、底层是由一个数组
  7. 数据的读取写入是通过Buffer,这个和BIO、BIO中要么是输入流、或者是输出流,不能双向,但是NIO的Buffer是可以读也可以写,需要flip()方法来进行切换。
  8. Channel也是双向的,可以返回底层操作系统的情况,比如Linux,底层的操作系统通道就是双向的。

三大核心——Buffer缓冲区详解

Buffer的Doc源码(java11)

基本介绍

Buffer的子类

容器对象(函数组),如何理解?从源码中可以看到。Int,Float等,每一个子类Buffer对象,都是[]数组。

具有的四个子类

private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
  1. 缓冲区的容量是它所包含的元素数量。 缓冲区的容量从不为负,从来没有改变。
  2. 缓冲区的限制是不应读取或写入的第一个元素的索引。 缓冲区的限制是从不为负,并且永远不会比它更大的容量。
  3. 缓冲区的位置要被读出或写入的下一个元素的索引。 缓冲区的位置永远不会为负,并且永远不会比它的极限。
  4. // Invariants: mark <= position <= limit <= capacity
    

代码跟踪-详解

Buffer类及其子类中的重要的方法

如,通过设置position的值,来读取指定位置的值。也可以修改limit的值等。

ByteBuffer

ByteBuffer,是最常用的。二进制数据。

三大核心—— Channel 通道详解

Channel接口的Doc源码(java11)

基本介绍

Channel的子类

FileChannel类

Channel应用实例

Channel应用实例1——本地文件写数据

实例代码如下:

package com.dawa.netty.nio;

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class TestNIOFileChannel01 {
    public static void main(String[] args) throws Exception{

        //准备字符串
        String string = "dawa,大娃,Bigbaby";

        //准备输出流.指定输出的文件地址
        FileOutputStream fileOutputStream = new FileOutputStream("dawa.txt");

        //准备Channel管道. 对输出流进行封装,封装为一个channel管道.
        FileChannel fileChannel = fileOutputStream.getChannel();

        //准备一个byte数组, 也就是一个 Buffer数组,来缓存数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //读取数据
        byteBuffer.put(string.getBytes());

      	//这里第一次没有反转,文件里面乱码
        byteBuffer.flip();

        //完成写的操作
        fileChannel.write(byteBuffer);

        //关闭流
        fileOutputStream.close();
    }
}

注意:

  1. 是FileOutPutStream 包含 NIO FileChannel
  2. FileChannel的具体的实现类是:FileChannelImpl

Channel应用实例2——本地文件读数据

代码案例如下

package com.dawa.netty.nio;

import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//本地 读文件
public class TestNIOFileChannel02 {
    public static void main(String[] args) throws Exception {

        //读到文件
        File file = new File("dawa.txt");
        FileInputStream fileInputStream = new FileInputStream(file);

        //fileInputStream 包装为 Channel
        FileChannel fileChannel = fileInputStream.getChannel();

        //借助Buffer byte[]缓冲数
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());

        //将Channel的数据读入到byteBuffer
        fileChannel.read(byteBuffer);

        System.out.println(new String(byteBuffer.array()));

        fileInputStream.close();

    }
}

Channel应用案例3——使用Buffer完成文件的读写

类似于拷贝的操作,使用文件Channel+Buffer完成

代码案例如下

package com.dawa.netty.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//使用一个Channel完成文件的读写
public class TestNIOFileChannel03 {
    public static void main(String[] args)  throws  Exception{

        FileInputStream fileInputStream = new FileInputStream("dawa.txt");
        FileChannel channel01 = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("erwa.txt");
        FileChannel channel02 = fileOutputStream.getChannel();

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

        while (true) {
            //这里注意使用Clear操作,不然会进入死循环
            /**
             * public Buffer clear() {
             *         position = 0;
             *         limit = capacity;
             *         mark = -1;
             *         return this;
             *     }
             */
            byteBuffer.clear();

            int read = channel01.read(byteBuffer);
            if (read == -1) {
                break;
            }
            //反转,切换流
            byteBuffer.flip();
            channel02.write(byteBuffer);
        }
						fileInputStream.close();
            fileOutputStream.close();
    }
}

这里需要注意的是使用clear操作,重置缓冲区基本参数

public Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

Channel应用案例4——Transferform拷贝

transferFrom方法

    public long transferFrom(ReadableByteChannel src,
                             long position, long count)
        throws IOException
    {
        ensureOpen();
        if (!src.isOpen())
            throw new ClosedChannelException();
        if (!writable)
            throw new NonWritableChannelException();
        if ((position < 0) || (count < 0))
            throw new IllegalArgumentException();
        if (position > size())
            return 0;
        if (src instanceof FileChannelImpl)
           return transferFromFileChannel((FileChannelImpl)src,
                                          position, count);

        return transferFromArbitraryChannel(src, position, count);
    }

案例如下

package com.dawa.netty.nio;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

//使用一个Channel完成文件的读写
public class TestNIOFileChannel03 {
    public static void main(String[] args)  throws  Exception{

        FileInputStream fileInputStream = new FileInputStream("dawa.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("erwa.txt");

        FileChannel sourceCH = fileInputStream.getChannel();
        FileChannel destCH = fileOutputStream.getChannel();

        //直接通过通道,完成拷贝
        destCH.transferFrom(sourceCH, 0, sourceCH.size());

        fileInputStream.close();
        fileOutputStream.close();

    }
}

关于Buffer和Channel的注意事项和注意细节

  1. 存取类型需要保持一致(存取顺序一致)

  2. Buffer可以转为只读Buffer
    byteBuffer.asReadOnlyBuffer();
    
   ![image-20200325061059067](https://tva1.sinaimg.cn/large/00831rSTly1gd5rck2zicj31ja0tyh7e.jpg)

3. **MappedBuffer 可以让文件直接在内存(堆外内存)修改,操作系统不需要拷贝一次**

   ![image-20200325061421909](https://tva1.sinaimg.cn/large/00831rSTly1gd5rge5zuaj30oi0b4tad.jpg)

   MappedByteBuffer是抽象类,实际能够操作的类型是 DirectByteBuffer

   > 代码案例如下:

   ```java
   package com.dawa.netty.nio;

   import java.io.RandomAccessFile;
   import java.nio.MappedByteBuffer;
   import java.nio.channels.FileChannel;

   //使用 MappedBuffer 直接完成文件在内存中的数据修改
   public class MappedBuffer01 {
       public static void main(String[] args) throws Exception {

           //获取一个读取文件流
           RandomAccessFile randomAccessFile = new RandomAccessFile("dawa.txt","rw");

           //获取指定的Channel
           FileChannel channel = randomAccessFile.getChannel();

           //读取模式. 0 代表从0开始, 5代表读取5个字节,也同时意味着只能在内存中操作这5个字节
           MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

           //对指定位置进行操作
           mappedByteBuffer.put(0, (byte) ‘A‘);
           mappedByteBuffer.put(2, (byte) 9);

           randomAccessFile.close();
           channel.close();
       }
   }
  1. Scattering&Gathering的使用

    Scattering:将数据写入到Buffer时,可以采用Buffer数组,依次写入【分散】

    Gathering:从Buffer读取数据时,可以采用Buffer数组,依次读【聚合】

    解决的问题:当一个数组不够用的时候,可以用数组组,来完成类似的操作

    代码案例如下:使用 数组,来完成客户端 - 服务器端 读取操作

    package com.dawa.netty.nio;
    
    import java.net.InetSocketAddress;
    import java.nio.ByteBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Arrays;
    
    public class ScatteringGatheringGetPut {
        public static void main(String[] args) throws Exception {
    
            //创建服务器端的
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    
            //监听端口号
            InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 7000);
    
            //绑定端口号到服务器端的Channel
            serverSocketChannel.socket().bind(inetSocketAddress);
    
            //创建Buffer数组
            ByteBuffer[] byteBuffers = new ByteBuffer[2];
            byteBuffers[0] = ByteBuffer.allocate(5);
            byteBuffers[1] = ByteBuffer.allocate(3);
    
            // 等待连接,获取连接, 并生成客户端的 Channel
            SocketChannel socketChannel = serverSocketChannel.accept();
    
            //假设从 客户端读取 8个字节
            int messageLength = 8;
    
            while (true) {
                //1. 将客户端的数据, 读取
                int byteRead = 0;
                while (byteRead < messageLength) {
                    long l = socketChannel.read(byteBuffers);
                    System.out.println("byteRead = " + byteRead);
                    byteRead += 1;//累积读取的字节数
    
                    //使用流打印,看看当前Buffer里面的position和limit
                    Arrays.asList(byteBuffers).stream()
                            .map(byteBuffer -> "position=" + byteBuffer.position() + ", limit=" + byteBuffer.limit()).forEach(System.out::println);
                }
    
                //将所有的 Buffer反转
                Arrays.asList(byteBuffers).forEach(ByteBuffer::flip);
    
                //2. 将读取到的数据,写回客户端
                int byteWrite = 0;
                while (byteWrite < messageLength) {
                    socketChannel.write(byteBuffers);
                    byteWrite += 1;
                }
    
                //将所有的Buffer进行Clear操作
                Arrays.asList(byteBuffers).forEach(ByteBuffer::clear);
    
                //读完之后,打印出来看看读写文件的长度
                System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWrite + ", messageLength" + messageLength);
    
            }
    
        }
    }
    

三大核心—— Selector 选择器详解

Selector的Doc源码(java11)

基本介绍

  1. 示意图:

Select示意图和特点说明

Selector的子类

Selector类的相关方法

PS:一个线程,对应一个Selector,每个Selector通过调用select()方法,获取不同的能够代表Channel的SelectionKey,得到一个能够被选择的Channel集合。

注意事项

NIO非阻塞网络编程原理分析图

NIO非阻塞网络相关的(Selector、SelectionKey、ServerSocketChannel和SocketChannel)关系图梳理。

对上图的说明

  1. 当客户端生成时,会通过ServerSocketChannel得到SocketChannel。
  2. Selector开始监听...Selector进行监听select方法,返回有事件发生的通道的个数。
  3. 将SocketChannel注册到Selector上.register(Selector sel,int ops).一个Selector上可以注册多个SocketChannel。

    SocketChannel的父类里面有注册方法

    SelectableChannel里面还有一个注册方法,这个用的比较多

  4. 注册后,返回一个SelectionKey,会和该Selector关联(集合)
  5. 进一步得到各个SelectionKey(有事件发生)
  6. 再通过SelectionKey 反向获取SocketChannel。

    SelectionKey类中的channel()方法

  7. 通过得到的Channel,完成业务处理

NIO非阻塞网络编程快速入门

创建服务器端。
  1. 创建ServerSocketChannel ,并设置非阻塞
  2. 得到一个Selector对象
  3. 绑定一个端口6666.在服务器端监听
  4. 把servrSocketChannel 注册到 selector  关心事件为 SelectionKey.OP_ACCEPT
  5. 循环等待客户端连接
  	//这里我们等待一秒,如果没有事件发生,返回
  	1. if(selector.selecct(1000)==0){//没有事件发生
      sout("服务器等待了一秒");
      continue;
    }

		//如果返回的值>0,就获取到相关的selectionKey集合
		// 1. 表示已经获取到关注的事件。
		// 2. 通过selectionKeys()返回关注的集合。
		// 3. 通过selectionKeys
		seletor.selectedKeys().var;

		//遍历得到的selectionKeys.
			//1. 获取SelectionKey
			//2. 根据key 对应的通道发生的事件做处理
			//3. 如果是 OP_ACCEPT,有新的客户端连接
					//1. 给该客户端生成一个SocketChannel
					//2. 将SocketChannel 注册到select,关注事件为OP_READ,并关联一个Buffer
			//4. 如果是 OP_READ,读取数据
					//1. 通过key,反向获取对应的channel
					//2. 获取到该channel关联的buffer
			//5. 手动从集合中移动单签的SelectionKey,防止重复操作。

创建客户端。
  1. 得到一个网络通道SocketChannel.并设置非阻塞
  2. 提供服务区的IP和端口,连接服务器

服务器端代码

package com.dawa.netty.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

// NIO 服务端
public class TestNIOServer {
    public static void main(String[] args) throws Exception {

        //  1. 创建ServerSocketChannel ,并设置非阻塞
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        //  2. 得到一个Selector对象
        Selector selector = Selector.open();
        //  3. 绑定一个端口6666.在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //  4. 把serverSocketChannel 注册到 selector  关心事件为 SelectionKey.OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环,等待客户端连接
        while (true) {
            if (selector.select(1000) == 0) {// 没有事件发生
                System.out.println("服务器端等待1秒,没有客户端连接");
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();//得到所有被选中的Key
            //循环遍历每一个key,每一个key代表一个事件
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //根据key对应的事件,做响应的处理
                if (selectionKey.isAcceptable()) {//如果是 Accept事件, 连接事件,则生成对应的客户端Channel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    //将SocketChannel 注册到select,关注事件为OP_READ,并关联一个Buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {//如果是读事件
                    //1. 通过key,反向生成Channel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    //设置非同步NIO
                    socketChannel.configureBlocking(false);
                    //2. 获取到该channel关联的buffer
                    ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                    socketChannel.read(buffer);
                    //打印出获取到的Buffer
                    System.out.println("from 客户端:" + new String(buffer.array()));
                }
                //这里一定要记得把处理过的key给移除掉,自己遇到了死循环.
                iterator.remove();
            }
        }
    }
}

客户端代码

package com.dawa.netty.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

//NIO 客户端
public class TestNIOClient {
    public static void main(String[] args) throws Exception {

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        //服务器端的IP和端口
        InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 6666);

        if (!socketChannel.connect(socketAddress)) {
            System.out.println("连接失败,但是可以干其他事情,非阻塞");
            while (!socketChannel.finishConnect()) {
                System.out.println("在连接完成之前,我一直干其他的事情");
            }
        }

        String string = "hello,dawa";
        socketChannel.write(ByteBuffer.wrap(string.getBytes()));
        System.in.read();
    }
}

SelectionKey

Selector.keys() 是 列出所有的key。

Selector.selectedKeys()是列出所有被选中的key。

这两个是不一样的。

ServerSocketChannel

SocketChannel

NIO网络编程应用实例——群聊系统

功能示意图

  1. 先写服务器端

    1. 服务器端启动并监听6667
    2. 服务器端接收客户端消息,并实现转发[处理上线和离线]
  2. 编写客户端
    1. 连接服务器
    2. 发送消息
    3. 接受服务器消息

服务器端代码

  1. 构造器初始化

  1. 监听方法

    Listen()

    里面循环的写法:

里面读数据的方法:

try catch完成离线处理

里面转发给其他客户端的方法

客户端代码

  1. 构造器初始化

  2. 向服务器发消息

  3. 读取从服务器端回复的消息

启动客户端和服务器端

  1. 启动客户端的方法

  2. 启动服务器端的方法

自己编码:实现群发

客户端代码

package com.dawa.netty.nio.group;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;

//客户端
public class GroupCharClient {

    private SocketChannel socketChannel;
    private static final int PORT = 6667;
    private static final String HOST = "127.0.0.1";
    private Selector selector;
    private String userName;

    public GroupCharClient() throws IOException {
        selector = Selector.open();
        //连接服务器
        socketChannel = SocketChannel.open(new InetSocketAddress(HOST, PORT));
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        userName = socketChannel.getRemoteAddress().toString().substring(1);
    }

    //发送消息
    public void sendMessage(String message){
        message = userName + "说" + message;
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
        try {
            socketChannel.write(buffer);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读取服务器端发来的消息
    public void readMessage() {
        try {
            int readChannels = selector.select();
            if (readChannels > 0) {//有可用的通道
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    if (key.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        socketChannel.read(buffer);

                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
            } else {
                //没有可用的通道
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        GroupCharClient groupCharClient = new GroupCharClient();

        new Thread(() -> {
            while (true) {
                groupCharClient.readMessage();
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            String message = scanner.nextLine();
            groupCharClient.sendMessage(message);
        }
    }

}

服务器端代码

package com.dawa.netty.nio.group;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 服务器端代码
 */
public class GroupChatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private static final int PORT = 6666;

    public GroupChatServer() {
        try {
            //得到选择器
            selector = Selector.open();
            //绑定端口
            serverSocketChannel = ServerSocketChannel.open().bind(new InetSocketAddress(PORT));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //服务器端监听的方法
    public void listen() {
        try {
            //循环监听
            while (true) {
                int count = selector.select();
                if (count > 0) {
                    Iterator<SelectionKey> selectionKeyIterator = selector.selectedKeys().iterator();
                    while (selectionKeyIterator.hasNext()) {
                        //取出Key
                        SelectionKey key = selectionKeyIterator.next();

                        //判断事件
                        if (key.isAcceptable()) {//监听访问
                            //key 转Channel
                            SocketChannel channel = serverSocketChannel.accept();
                            SocketAddress remoteAddress = channel.getRemoteAddress();
                            System.out.println(remoteAddress + ":上线了");
                        }
                        if (key.isReadable()) {//读取事件
                            //处理读
                            readData(key);
                        }
                        //移除已经处理的key
                        selectionKeyIterator.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读数据
    public void readData(SelectionKey key) {
        SocketChannel channel = null;
        try {
            //根据key,取得Channel
            channel = (SocketChannel) key.channel();
            channel.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int read = channel.read(buffer);
            if (read > 0) {
                String message = new String(buffer.array());
                System.out.println("from: 客户端" + message);

                // 向其他用户,转发消息
                sendMessageToOtherCLient(message,key);
            }

        } catch (IOException e) {
            try {
                System.out.println(channel.getRemoteAddress() + " :离线了");
                key.cancel();
                channel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }

    //向其他用户转发消息
    public void sendMessageToOtherCLient(String message,SelectionKey self){
        System.out.println("服务器转发消息ing");

        selector.keys().forEach(key -> {
            //根据Key,取出对应的SocketChannel.或者是ServerSocketChannel
            Channel targetChannel = key.channel();
            //排除自己
            if (targetChannel instanceof SocketChannel && targetChannel != self) {
                //转型
                SocketChannel dest = (SocketChannel) targetChannel;
                //Buffer
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                try {
                    dest.write(buffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    public static void main(String[] args) {
        GroupChatServer groupChatServer = new GroupChatServer();
        groupChatServer.listen();
    }
}

NIO与零拷贝

什么是零拷贝?

零拷贝,指的是没有CPU拷贝,而不是不拷贝。从操作系统角度看的。

基本介绍

传统IO数据读写

传统IO模型图,状态切换:用户态和内核态的切换:4次拷贝,3次切换

MMAP优化

Mmap优化:3次拷贝,3次切换

DMA拷贝:direct memory accect:直接内存访问

sendFile优化

sendFile优化:三次拷贝,两次切换

零拷贝

零拷贝,指的是没有CPU拷贝,而不是不拷贝。从操作系统角度看的。

这里其实还有一次CPU拷贝的:kernel buffer->socket buffer但是,拷贝的信息很少,比如length,offset,消耗低,可以忽略

零拷贝:2次拷贝,2次切换。

零拷贝是我们在进行网络传输的重要优化手段。

mmap和sendFile的区别:

NIO零拷贝案例

传统IO流案例

传统IO耗费时间:60毫秒

零拷贝案例。(NIO)

transferTo 底层用的就是零拷贝

NIO零拷贝耗时时间:20毫秒

Java AIO编程

Java AIO基本介绍

这里暂时不深入扩展。

BIO、NIO、AIO对比表

原文地址:https://www.cnblogs.com/bigbaby/p/12596350.html

时间: 2024-11-05 11:00:54

Java IO模型:BIO、NIO、AIO的相关文章

初理解Java中的BIO,NIO,AIO

初识: java 中的 BIO.NIO和 AIO 理解为是 Java 语言对操作系统的各种 IO 模型的封装.程序员在使用这些 API 的时候,不需要关心操作系统层面的知识,也不需要根据不同操作系统编写不同的代码.只需要使用Java的API就可以了. 在讲 BIO,NIO,AIO 之前先来回顾一下这样几个概念:同步与异步,阻塞与非阻塞. 同步与异步: 同步: 同步就是发起一个调用后,被调用者未处理完请求之前,调用不返回. 异步: 异步就是发起一个调用后,立刻得到被调用者的回应表示已接收到请求,但

JAVA中的BIO,NIO,AIO

在了解BIO,NIO,AIO之前先了解一下IO的几个概念: 1.同步 用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪, 例如自己亲自出马持银行卡到银行取钱 2.异步 用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程,例如让小弟去银行帮你取钱,你可以干别的事 3.阻塞 当试图进读写文件的时候,发现不可读取或没东西读,则进入等待状态知道可读,ATM排队取钱 4.非阻塞 用户进程访问数据时,会马上返回一个状态值(可读不可读),比如在银行柜台办理业务,先取个号,然后坐在椅子

JAVA-IO模型(BIO,NIO,AIO)

基本概念 阻塞和非阻塞 阻塞是进行读写时, 如果当时没有东西可读,或者暂时不可写, 程序就进入等待, 直到有东西可读或者可写为止 非阻塞是如果没有东西可读, 或者不可写, 读写函数马上返回, 而不会等待 同步和异步 同步是指的是用户操作后等待或者轮询的去查看操作是否就绪 异步是指用户某动作操作后便开始做其他动作,而当这个动作操作完成的时候用户会得到这个动作完成的通知 IO模型 同步阻塞IO(JAVA BIO): 在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成

java并发之bio nio aio

最近在进行tomcat优化,发现tomcat connector并发支持bio nio apr,发现想要理解tomcat并发离不开java io的理解.所有本文先探讨java对io的支持.java的io需要操作系统的支持,本文描述linux系统对io的支持,windows系统因为java生成环境使用少不再论述. 一.linux操作系统io的支持 1.同步阻塞 I/O(bio) 2.同步非阻塞I/O(nio) 3.异步非阻塞 I/O(aio) 二.java 包对io的支持 原文地址:https:/

JAVA 004 网络编程 BIO NIO AIO

目录(图片来自于网络) 多路复用Linux环境下底层机制 多路复用模式Reacotor和Proactor BIO,NIO,AIO的简单介绍 多路复用Linux环境下底层机制 多路复用模式Reacotor和Proactor Reactor和Proactor模式的主要区别就是真正的读取和写入操作是由谁来完成的 Reactor中需要应用程序自己读取或者写入数据 Proactor模式,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO的设备

JAVA 中BIO,NIO,AIO的理解

JAVA 中BIO,NIO,AIO的理解 博客分类: 网络编程 [转自]http://qindongliang.iteye.com/blog/2018539 在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步非阻塞? 7 什么是异步阻塞? 8 什么是异步非阻塞? 散仙不才,在查了一部分资料后,愿试着以通俗易懂的方式解释下这几个名词.如有不足之处,还

也谈BIO | NIO | AIO (Java版--转)

关于BIO | NIO | AIO的讨论一直存在,有时候也很容易让人混淆,就我的理解,给出一个解释: BIO | NIO | AIO,本身的描述都是在Java语言的基础上的.而描述IO,我们需要从两个层面: 编程语言 实现原理 底层基础 从编程语言层面 BIO | NIO | AIO 以Java的角度,理解,linux c里也有AIO的概念(库),这些概念不知道什么原因被炒火起来,这里只从Java角度入手. BIO,同步阻塞式IO,简单理解:一个连接一个线程 NIO,同步非阻塞IO,简单理解:一

一站式学习Java网络编程 全面理解BIO/NIO/AIO

第1章 [开宗明义]网络编程三剑客BIO.NIO.AIO网络编程是RPC的奠基,RPC编程贯穿了程序员生涯的始终.本章首先分析为什么要学网络编,本课为谁设计,然后介绍课程内容主线脉络,让大家清晰知道本课程并非光说不练的假把式,而是处处有实战,实战项目步步优化,最后通过综合项目巩固所学.... 第2章 网络层的解析与协议本章首先对网络中涉及的网络链路层的解析进行讲解,进一步引出网络基本协议知识.使学员了解分层思想,对三种协议的定位及作用有所了解. 第3章 解读java.io专业术语也可以变得生动精

3. 彤哥说netty系列之Java BIO NIO AIO进化史.md

你好,我是彤哥,本篇是netty系列的第三篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 先说两个事 (1)上周五的那篇文章发重复了,是定时任务设置错误导致,给大家带来干扰,这里说声抱歉. (2)之前的问卷调查结果出来了,认为先讲案例的票数较多,所以后面的文章都是先讲案例,再以案例展开讲解组件. 简介 上一章我们介绍了IO的五种模型,实际上Java只支持其中的三种,即BIO/NIO/AIO. 本文将介绍Java中这三种IO的进化史,并从使用的角度剖析它们背后的故事. Java BI