Netty之 BIO与NIO

BIO

BIO 会导致线程逐渐增多,消耗cpu

BIO模型

通过线程池机制编写简单demon

@Slf4j
public class Server {

public static void main(String[] args) throws Exception {
    //创建一个缓存线程池
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
    //serverSocket
    ServerSocket serverSocket = new ServerSocket(6666);

    while (true){
        final  Socket socket = serverSocket.accept();//等待客户端进来
        log.info("连接到一个线程");
        // 加载到线程池里
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    handler(socket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

public static void handler(Socket socket) throws IOException {
    // 接收数据
    byte[] bytes = new byte[1024];
    // 通过socket 获取输入流
    InputStream inputStream = null;
    int read = 0;
    try {
        inputStream = socket.getInputStream();
        read = inputStream.read(bytes);
        while (true){
            if(read != -1 ){
                log.info("获取到客户端数据的数据"+new String(bytes,0,read) );
            }else{
                break; // 退出
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        inputStream.close();
        socket.close();
    }
}

}

客户端如何证明 访问的时候是一个单独的一个线程?

通过输出线程的id 和名字

System.out.println("线程id"+Thread.currentThread().getId()+"名字"+Thread.currentThread().getName() );

当没有进入数据发送的时候 会默认产生阻塞效果 。 因此nio模型是事件驱动的。

bio jdk1.4之前独有。

客户端cmd 连接命令 telnet 127.0.0.1 6666

NIO同步非阻塞模型

同步非阻塞,服务器端是实现了一个线程进行多个处理,客户端发送的连接都会路由到多路复用上,多路复用在通过轮询对连接的请求进行io的读写操作。

什么是堆外内存和堆内内存?

堆内内存是被jvm管理的,堆外则不是。堆外是由操作系统管理的。

服务端产生一个selector 轮询器,把客户端的请求进行分发和处理,当客户端有事件发生的时候选择器会进行轮询获取。

nio 是jdk1.7以后,而且nio没有广泛应用,netty就是应用了NIO。

简单了解javaAIO就是NIO.2:异步非阻塞,对请求进行一个判断,连接使用较长的应用。 主要学习NIO

NIO 在1.4以后是同步非阻塞的,nio的类在java.nio.的包下

NIO3大核心

Channel(通道),Buffer(缓冲区),Selector(选择器)

在NIO里 selector 与 channel交互,channel与buffer交互,channel 是一个通道可以创建读写,buffer与客户端进行交互的一个场景。 通过buffer就可以实现非阻塞。一个selector 可以管理多个通道。

NIO面向缓冲或者面向块编程,数据读取到缓冲区,需要时在缓冲区去读取,从而实现同步非阻塞。

javaNio是非阻塞,如果没有数据可用的时候,就什么也不会获取,而不是保持阻塞的状态。

selector 当这个通道里没有消息执行的时候,就会去执行其他通道的消息,当其他通道也没有消息的时候,那么他就去执行其他任务。因此线程就是不会阻塞。

http2.0之后支持一个服务器去服务多个浏览器,做到一个连接 处理多个并发请求的情况。

selector 对应多个channel ,一个selector对应一个线程,一个channel 对应一个缓存buffer。

selector的切换根据channel通道的事件进行切换,(Event) ,selector 根据不同的事件在各个通道上切换。

buffer就是一个内存块, 底层是有一个数组的,在nio里数据的读取,数据的读写通过buffer和bio的io输入 输出是有去别的。但nio的buffer 是支持双向流动可以读也可以写的,需要bufffer.flip()方法来切换。其中channel 也是双向的非阻塞的。 channel 可以反应底层操作系统。其中linux的底层操作系统就是双向的。

buffer与channel是双向的,那NIO的是只能读或者只能写。 而且读写的时候必须经过buffer。

buffer 代码实现

public static void main(String[] args) {
// 创建一个缓存 支持存放3个 int
IntBuffer bufffer = IntBuffer.allocate(3);
//allocate.capacity() 容量
for (int i = 0; i < bufffer.capacity(); i++) {
bufffer.put(i*2);
}
// 读取数据 读写切换 当读或者写的时候 要用这个切换一下
bufffer.flip();
while (bufffer.hasRemaining()){
int i = bufffer.get();
System.out.println(i);
}
}

缓冲区基本介绍

缓冲区本质上是一个读写内存的块,可以理解成一个数组容器,能够对数据进行操控 。

buffer底层

ctrl+h 显示子类和方法

ctrl+n 查找所有

buffer 是一个抽象类,他的子类有IntBuffer,stringBuffer ,Charbuffer,LongBuffer,Bytebuffer

等,网络传输都是用byteBuffer,底层都有hb数组

buffer 定义的4下面四个属性是提供相关的数据元素的信息。

Capacity 一个容量 在缓冲区创建后 不可以改变

limit 是一个可以改变不能对缓冲区的极限进行读写

Postiion 就是当前的位置,下一个要被读或者写要发生变化的数据 ,为其做准备。

Mark 是一个标记

Channel 通道

channel是一个接口,然后channel是可以对数据进行读写的,而bio只能单向的,其中channel的主要实现类有

FileChannel ,DatagramChannel,ServersocketChannel,SocketChannel 。

FileChannel :用于文件的数据读写

DatagramChannel :用于UDP

ServersocketChannel、SocketChannel :做网络的TCP,ServersocketChannel 类似ServerSocket ,SocketChannel 类似于Socket

当连接进入的时候 先去寻找主线程,ServerSocketChannel 会生成一个SocketChannel


FileChannel demon

这里面 在缓冲区写入管道后要有一个缓存buffer.flip() 进行反转;

 public static void main(String[] args) throws Exception{
        // 一个汉字3个字节
        String str ="jhha";
        // 创建输出流管道
        FileOutputStream fileOutputStream = new FileOutputStream("D://111.txt");
        // 得到 Channel
        FileChannel channel = fileOutputStream.getChannel();


        //创建 缓存buffer
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        // 将数据加入缓存中
        allocate.put(str.getBytes());
        // 反转
        allocate.flip();
        // 写入管道
        channel.write(allocate);
        fileOutputStream.close();
 }

读取文件流

   public static void main(String[] args) throws IOException {
    File  file = new File("D://111.txt");

    FileInputStream fileInputStream = new FileInputStream(file);

    FileChannel channel = fileInputStream.getChannel();
	
    ByteBuffer byteBuffer =ByteBuffer.allocate((int) file.length());

    int read = channel.read(byteBuffer);

    // 把byte buffer 字节转换数组
    System.out.println(new String(byteBuffer.array()));

    fileInputStream.close();
}

BIO拷贝文件

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

FileInputStream fileInputStream = new FileInputStream("1.txt");
FileChannel channel = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
FileChannel channel1 = fileOutputStream.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true){
    //先复位
    buffer.clear();
    int read = channel.read(buffer);
    if (read == -1) break;
    buffer.flip();
    channel1.write(buffer);
}
channel.close();
channel1.close();

}

transferFrom实现拷贝功能
public static void main(String[] args)throws Exception {
// c创建流
FileInputStream in = new FileInputStream("D://1.JPG");

    FileOutputStream out = new FileOutputStream("D://A2.JPG");

    FileChannel inChannel = in.getChannel();

    FileChannel outChannel = out.getChannel();

    // 使用transferFrom
    outChannel.transferFrom(inChannel,0,inChannel.size());

    // 关闭相关通道和流
    inChannel.close();
    outChannel.close();
    in.close();
    out.close();
}
buffer与channel

1 buffer 中 ByteBuffer 放入什么类型,那么取出的时候就要取出什么类型 。 如果不按顺序取出 会发生异常。

2 可以将buffer 转换成一个只读buffer buffer.asReadOnlyBuffer 类型是HeapReadBuffer 是只读的意思

ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();

3 nio提供了MappedByteBuffer,可以让文件在内存中进行修改。

4 nio 支持多个buffer 进行Scattering和Gathering 。

channel 与channel 如何进行通信

在FileInputStream通过 outChannel.transferFrom(inChannel,0,inChannel.size());

NIO 之Selector

selector 基本介绍

selector 由一个线程创建,实现同步非阻塞的模型,可以管理多个客户端的链接 。

1 selector 可以管理通道 ,并且检测是否有事件发生 。 也就是多个Channel 发生事件的时候 selector是可以监听到的。

2 只有在连接通道真正有事件发生啥的时候 才会进行读写,这样提高了性能的使用。

3 避免了频繁的多线程切换。

selector 特点

1 selector 可以处理成千上百的链接

2 空闲的时候可以做别的事情,哪个io有事件就去处理哪个。

3 读写操作本身是带有缓冲的操作。,避免了io频繁访问,导致挂起。

selector类和 方法

Selector 是一个抽象类

public abstract class Selector implements Closeable {                          }

常用方法有

1 open()得到一个选择器 创建一个选择器

2 select () 这个是调用的阻塞方法,当通道没有事件发生的时候 一直处于阻塞状态,当有一个以上的事件发生则将 selectKey 加入到内部集合队列中,之后在通过内部集合队列去把对应发生的channel管道对应数据取出来。

3 select(Long time) long time 是加入阻塞的时间,例如传入1秒那么则堵塞一秒钟

4 selectNow()直接获取当前堵塞的事件

5 selectedKeys() 查看所有管道的注册的seletorkey

6 wakeup() 唤醒在阻塞的。

NIO非阻塞网络编程

1 selector 注册之后 serverSocketChannel分配一个socketChannel

2 创建一个管道后同时在selector选择器内注册 交由selector管理

3 当管道有读写发生的时候selector 会监听到,得到一个selectKey

4 selector通过selectKey去得到对应管道的数据

BIO NIO AIO 场景 ?

bio是一个传统连接数较小,比较固定,在jdk1.4之前都是bio。易于了解

NIO连接数比较多,弹幕,聊天系统等,连接时间较短。

aio 连接时间较长,数目较多。

clear() : 情况buffer 进行复位,如果不复位的话 读取到最后 position 与limit 就想等,那么相等则就无数据 导致read 是 0 ,所以没有机会read =-1 就会导致死循环 。

代码实现 业务逻辑梳理服务端

服务端

1 先创建一个ServerSocketChannel

2 设置为 非阻塞状态

3 创建一个selector 选择器

4注册 serverSocketeChannel 加入到selector 选择器

5 创建端口号

6 轮询监听

7 监听事件 selector阻塞时间判断

8 创建socket通道

9注册

10 设置非阻塞

客户端

1 serverSocket 通道

2 添加链接端口

3 发送数据

服务端代码
@Slf4j
public class NioServer {

public static void main(String[] args) throws Exception {
    // 创建 ServerSocketChannel
    ServerSocketChannel  serverSocketChannel = 	        ServerSocketChannel.open();

    // 创建 一个selector
    Selector selector = Selector.open();
    // 设置非阻塞
    serverSocketChannel.configureBlocking(false);
    // 注册
    serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
    // 绑定端口号
    serverSocketChannel.socket().bind(new InetSocketAddress(6666));

    while (true){

        if(selector.select(1000) == 0){
            log.info("服务器等待了一秒,无连接");
            continue;
        }
        // 如果返回大于0 ,那么获取对应管道的key
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> keys = selectionKeys.iterator();
        while (keys.hasNext()){
            SelectionKey key = keys.next();
            // 根据key 对应的通道事件

            // 1 有新的客户端链接 产生新的通道 ,分配给客户端
            if( key.isAcceptable()){
                // 生成一个socketChannel
                SocketChannel channel = serverSocketChannel.accept();
                channel.configureBlocking(false);
                // 关注读事件
                channel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
            }

            if(key.isReadable()){
                 // 得到通道
                SocketChannel channel =(SocketChannel)key.channel();
                // 获取当前的buffer
                ByteBuffer byteBuffer =(ByteBuffer) key.attachment();
                // 读入 buffer
                channel.read(byteBuffer);
                log.info("客户端读入数据"+new String(byteBuffer.array()));
            }
            //移除当前key
            keys.remove();
        }

    }
  }
}
客户端代码
public class NIOClient {

public static void main(String[] args) throws Exception {
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);

    if(!socketChannel.connect(inetSocketAddress)){
        while (!socketChannel.finishConnect()){
            System.out.println("等待链接中");
        }
    }

    String str = "您好!";
    ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());

    int write = socketChannel.write(wrap);
    //让代码停在这里
    System.in.read();
}
}
原文地址:https://www.cnblogs.com/chianw877466657/p/12733823.html