Netty入门,NIO,BIO,三大组件

Netty

Netty是什么

Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。 [1]

面向高并发,网络IO程序

事件驱动

用户一个行为触发一个事件,驱动函数或方法进行处理

应用场景

  1. 分布式系统各个组件需要远程服务调用,netty作为基础通信组件被RPC框架使用(比如Dubbo,实现各进程节点之间的内部通信)
  2. 游戏行业,高性能定制网络通信程序
  3. 大数据 avro实现数据文件共享

IO模型

BIO

同步并阻塞型,客户端有链接请求时客户端需要启动一个线程进行处理。不处理完请求不会继续往下执行。BIO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。

![](https://img-blog.csdnimg.cn/img_convert/16912148a0f7aef2d99bcdeff1340037.png#align=left&display=inline&height=531&margin=[object Object]&originHeight=531&originWidth=500&size=0&status=done&style=none&width=500)

NIO

NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

![](https://img-blog.csdnimg.cn/img_convert/56ea21aab4c8c8ac71a14b9957b8cca5.png#align=left&display=inline&height=254&margin=[object Object]&originHeight=254&originWidth=450&size=0&status=done&style=none&width=450)

适用于连接数目多和连接比较短的架构,聊天服务器,弹幕系统,服务期间通讯

AIO模型

异步非阻塞模型,引入一个异步通道,有效的请求才启动线程,先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用,比如相册服务器

同步和异步

同步和异步IO的概念:同步是用户线程发起I/O请求后需要等待或者轮询内核I/O操作完成后才能继续执行 异步是用户线程发起I/O请求后仍需要继续执行,当内核I/O操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

阻塞和非阻塞

阻塞是指I/O操作需要彻底完成后才能返回用户空间 非阻塞是指I/O操作被调用后立即返回一个状态值,无需等I/O操作彻底完成。

BIO

高并发的状态下,BIO对服务端的压力非常大

适用于连接数目比较小的项目

可以通过线程池机制改善,实现多个客户连接服务器

编程的简单流程

  1. 服务器端启动一个ServerSocket
  2. 客户端启动Socket对服务器进行通信,服务端需要对每一个客户建立一个线程与之通信。
  3. 客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待或遭服务器拒绝连接
  4. 如果有相应,客户端线程会等待请求结束后再继续执行(阻塞)

实例

1.手动创建线程池

new ThreadPoolExecutor(2, 5, 1L,
                TimeUnit.HOURS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
/* 七个参数
1、corePoolSize线程池的核心线程数
2、maximumPoolSize能容纳的最大线程数
3、keepAliveTime空闲线程存活时间
4、unit 存活的时间单位
5、workQueue 存放提交但未执行任务的队列
6、threadFactory 创建线程的工厂类
7、handler 等待队列满后的拒绝策略
*/
//1.创建线程池
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 1L,
            TimeUnit.HOURS, new LinkedBlockingDeque<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
    //2.监听客户端请求
	//创建服务端ServerSocket
    ServerSocket serverSocket = new ServerSocket(6666);
    System.out.println("服务器启动了");
    while (true) {
        //监听
        Socket socket = serverSocket.accept();
        System.out.println("连接到一个客户端,他的IP是:"+socket.getInetAddress()+"  端口号为:"+socket.getPort());
        //启动一个线程去处理
        threadPoolExecutor.execute(() -> {
            handler(socket);
        });
    }
}
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) {
                System.out.println(new String(bytes, 0, read));
            } else {
                break;
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        System.out.println("关闭和客户端的连接");
        try {
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

NIO

模式

三大核心部分:Channel(通道)类似BIO的socket,Buffer(缓冲区),Selector(选择器)

![](https://img-blog.csdnimg.cn/img_convert/dafbeefff5aafdb9c35cd08957122679.png#align=left&display=inline&height=1080&margin=[object Object]&originHeight=1080&originWidth=1593&size=0&status=done&style=none&width=1593)

用buffer作为缓冲,可以降低socket阻塞度,NIO是面向缓冲区或面向块编程,数据读取到一个它稍后处理的缓冲区

如果buffer区没有数据可用,就什么都不会获取,不会保持线程阻塞,这个线程(选择器)可以同时去做不同的东西

案例

//buffer的使用
//创建一个buffer
//大小为5,可以存放5个int
IntBuffer allocate = IntBuffer.allocate(5);

//向buffer存放数据
//allocate.capacity() buffer的容量
for (int i = 0; i < allocate.capacity(); i++) {
    allocate.put(i * 2+2);
}
//从buffer中读取数据
//将缓冲区反转,读写切换,将position切成0
allocate.flip();
//如果后面还有元素的话
//调用get 会将索引向后移
while (allocate.hasRemaining()) {
    System.out.println(allocate.get());
}

NIO以块的方式处理数据,效率比bio以流的方式效率高很多

单个线程可以监听多个客户端通道

三大核心组件

Selector,channel,buffer

三者关系

  1. 每个channel对应一个buffer
  2. 每个selector对应一个线程,一个线程对应多个channel(连接)
  3. 程序切换到哪个channel,是由事件决定的
  4. selector会根据不同的事件,在各个通道上切换
  5. buffer本质上是一个内存块,底层一个数组
  6. 采用发布订阅模式设计
  7. BIO是单向流处理,NIO是双向操作,但需要filp方法切换
  8. channel也是双向的,可以返回底层操作系统的情况

Buffer

本质上是一个可以读写数据的内存块,可以理解为容器对象,缓冲区内置对象可以跟踪和记录缓冲区的状态变化情况

private int mark = -1;  //标记
private int position = 0; //当前位置索引
private int limit;  //限制读入\写入多少个
private int capacity; //容量,缓冲区创建后不能改变

 final int[] hb;         //真正存放数据的数组

buffer是一个顶层的父类,它是一个抽象类,拥有很多子类

![](https://img-blog.csdnimg.cn/img_convert/2933ca98c580b931832d16d2bd3818fc.png#align=left&display=inline&height=341&margin=[object Object]&originHeight=341&originWidth=569&size=0&status=done&style=none&width=569)

可以设置position控制自己想读的位置

类型化

  1. put放入什么数据类型,get就应该使用相应的数据类型来取出,putint,getint

  2. 可以将一个普通buffer转换成只读buffer

转为 class java.nio.HeapByteBufferR 对象

allocate.asReadOnlyBuffer();
  1. mappedByteBuffer,可以让文件直接在内存(堆外的内存)进行修改

  2. NIO可同时操作多个buffer

只读buffer

 public static void main(String[] args) {
        //1.创建一个buffer
        ByteBuffer allocate = ByteBuffer.allocate(1024);


        //2.循环读入数据

        for (int i = 0; i < 64; i++) {
            allocate.put((byte)i);
        }
        //3.转换为只读
        ByteBuffer byteBuffer = allocate.asReadOnlyBuffer();
        byteBuffer.flip();

        //4.读出数据
        for (int i = 0; i < 64; i++) {
            System.out.println(byteBuffer.get((byte)i));
        }

MappedByteBuffer

public abstract class MappedByteBuffer
    extends ByteBuffer
    //继承了ByteBuffer 类
    

    /**
     * 说明
     *  MappedByteBuffer 可让文件直接在内存(堆外)修改,操作系统不需要拷贝一次
     */
    public static void main(String[] args) throws IOException {

        RandomAccessFile rw = new RandomAccessFile("D://1.txt", "rw");

        //获取对应的通道
        FileChannel channel = rw.getChannel();
        //调用map方法  读写模式,读写开始位置,读写位置的大小
        //可以修改的范围为0到5;
        MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        map.put(0,(byte)'H');
        map.put(3,(byte)'9');

        rw.close();
    }

Scattering 和 Gathering

/**
 * 写入数据可以采用buffer数组依次写入
 * gathering: 将数据读出到buffer时: 可以采用buffer数组 依次读入
 */
public class Scattering {


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


        //使用SeverSocketChannel 和SocketChannel;

        //1.创建
        ServerSocketChannel open = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

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

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

        //4.监听客户端连接
        SocketChannel accept = open.accept();
        int messageLength = 8;
        //5.循环读取

        while (true) {
            int byteRead = 0;
            while (byteRead < messageLength) {
                long read = accept.read(byteBuffers);
                byteRead += read;
                System.out.println("读取到" + byteRead);
                Arrays.asList(byteBuffers).stream().map(buffer -> "position=" + buffer.position() + ",limit=" + buffer.limit()).forEach(System.out::println);
            }
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
            long byteWrite = 0;
            while (byteWrite < messageLength) {
                long write = accept.write(byteBuffers);
                byteWrite += write;

            }

            //复位
            Arrays.asList(byteBuffers).forEach(buffer->{
                buffer.clear();
            });
            System.out.println("byteRead:="+byteRead+"byteWrite"+byteWrite);
        }


    }
}

channel

Nio的通道,类似于流,但双向作用于buffer

![](https://img-blog.csdnimg.cn/img_convert/7fa7be1b556f66a72b78fbe261ba3325.png#align=left&display=inline&height=277&margin=[object Object]&originHeight=277&originWidth=543&size=0&status=done&style=none&width=543)

DatagramChannel用于udp的数据读写

FileChannel用于文件的数据读写

SocketChannel,ServerSocketChannel用于TCP的数据读写

FileChannel

public abstract int read(ByteBuffer dst) throws IOException; //从通道读取数据并放到缓冲区中
public abstract int write(ByteBuffer src) throws IOException; //把缓冲区的数据写到通道中
public abstract long transferFrom(ReadableByteChannel src,
                                      long position, long count)
        throws IOException;  //从目标通道中复制数据到当前通道
public abstract long transferTo(long position, long count,
                                    WritableByteChannel target)
        throws IOException;  //把数据从当前通道复制到目标通道

读入读出按内存来理解

案例

/**
 * 将缓存的字节存入文件中
 * 文件不存在就创建
 */
public static void main(String[] args) throws IOException {
    String hello = "hello world";

    //创建一个输出流,并包装到channel
    FileOutputStream fileOutputStream = new FileOutputStream("d:\\1.txt");
    //通过输出流获取文件channel
    FileChannel f1=fileOutputStream.getChannel();
    //创建缓冲区 并write进channel
    f1.write(ByteBuffer.wrap(hello.getBytes()));
   
        FileInputStream fileInputStream = new FileInputStream("d:\\1.txt");
        FileChannel f2=fileInputStream.getChannel();
        f2.read(allocate);
        System.out.println(new String(allocate.array()));
    //读写出来
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值