java必备Netty技术(1)

  • 涉及的一些基础
    涉及的设计模式:观察者模式、命令模式、责任链模式
    涉及的数据结构:链表(管道的底层使用了链表)

一.netty介绍

是一个java开源项目
是一个异步的基于事件驱动的网络应用框架,用以开发高性能、高可用的网络io程序

异步是相对于同步而言的
同步:在同步中,当浏览器向服务器发送了一个请求1,需要等待服务器向浏览器返回了响应2后,浏览器才能进行操作3。
在

异步:在异步中,浏览器向服务器发起一个请求,但是它并不会因为响应没有到达,而发生阻塞,而是可以进行操作3,不需要等待响应2,提供了性能
在这里插入图片描述

高性能、高可用其实就是对java中的io进行了重写
主要针对于在TCP协议下,面向Clients的高并发应用,或者是peer-to-peer > 场景下的大量数据持续传输的应用
在这里插入图片描述

Netty本质是一个NIO框架,适用于服务通讯的多种应用场景,所以学好NIO。

二.Netty应用的场景

  1. 作为基础的通信组件被RPC框架使用,例如dubbo
  2. 游戏行业
  3. 大数据领域:AVRO实现数据文件的共享

三.IO模型

三种IO模型:用什么样的通道进行数据的发送和接受,很大程度上决定了程序通道的性能

BIO:同步并阻塞(传统阻塞),服务器实现模式为一个链接一个线程,即客户端有链接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情就会造成不必要的线程开销
在这里插入图片描述
当一个服务器被多个客户端请求时,就会对服务器造成压力,求每个线程都会有一定的开销。

BIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(链接),即客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理。
在这里插入图片描述
一个线程维护一个选择器,选择器可以对客户端的通道进行轮询操作,实现了可以处理更多的并发

AIO:异步非阻塞,AIO引入异步通道的概念,采用Procactor模式,简化了程序编写,有效的请求才启动线程,它的特点时由操作系统完成后才通知服务端程序开启线程去处理,一般适用于连接数较多且连接时间较长的应用(目前还不常用)

IO适用场景:

  1. BIO方式适用于连接数目较小且固定的框架,但是方式对服务器资源要求比较高,并发局限于应用中,jdk1.4以前的唯一选择,但是程序简单
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕系统,服务器间通讯、
  3. AIO方式适用于连接数目比较多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,编程较为复杂,jdk7后开始支持

四.BIO讲解

同步阻塞,线程的开销大,可以通过线程池改善开销
编程流程:

  1. 服务器端创建一个serverSocket
  2. 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户建立一个线程与通信
  3. 客户端发送请求后,先咨询服务器是否由线程响应,如果1没有则会等待,或者被拒绝
  4. 如果有响应,客户端线程会等待请求结束后,在继续执行

BIO相关案例:使用BIO模拟客户端向服务端发送数据

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

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

        //创建一个线程池
        ExecutorService newCachedThreadPool= Executors.newCachedThreadPool();

//        创建一个ServerScocket
        ServerSocket serverSocket=new ServerSocket(6666);

        System.out.println("服务启动了");
        while(true){
            //主线程
            System.out.println("线程信息 id:"+Thread.currentThread().getId()+" 名字为:"+Thread.currentThread().getName());
            final Socket socket=serverSocket.accept(); //可能会进行阻塞
            System.out.println("有客户端进行连接了");

            newCachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {

                    //与客户端进行通讯
                    handler(socket);
                }
            });

        }
    }

    //编写一个Handler方法,与客户端进行通讯
    public static void handler(Socket socket){
        try{
            byte[] bytes=new byte[1024];
            InputStream inputStream = socket.getInputStream(); //可能会进行阻塞
            //循环读取客户端发送的数据
            while(true){
                System.out.println("等待连接");
                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("关闭连接");
            try {
                socket.close();
            } catch (IOException e) {

                e.printStackTrace();
            }
        }
    }
}

在cmd中使用

telnet 127.0.0.1 6666    

ctrl+] 进入命令行界面
后使用send命令进行发送

send helloWorld

在这里插入图片描述
在这里插入图片描述

五.Java NIO基本介绍

  1. 全程java non-blocking IO ,是一系列改进的输入/输出的新特性,被统称为NIO,是同步非阻塞的

  2. NIO相关类都被放在java.nio 包和其子包下,并且对原java.io包中很多类进行了改写

  3. 三大核心部分:Channel(管道) ,Buffer(缓冲区),Selector(选择器)

  4. NIO是面向缓冲区,或者面向块编程的,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中进行前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络。
    在这里插入图片描述

  5. java NIO 的非阻塞模式,是一个线程从某通道发送的请求或者读取数据,但是它仅能得到且目前可用的数据,如果目前没有数据可用时,就什么都不会获得,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情,而非阻塞也是如此,一个线程请求写入一些数据到某通道,但是不需要等待它完全写入,这个线程同时可以去做别的事情。

  6. 通俗理解:NIO是可以做到用一个线程来处理多个操作的,假设有10000个请求过来,根据实际情况,可以分配50个或100个,不会像阻塞IO那样,非得分配10000个

  7. HTTP 2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP 1.1大了好几个数量级

NIO的Buffer小案例:

文件1

import java.nio.IntBuffer;

public class BasicBuffer {
    public static void main(String[] args) {

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

//        向Bufer中存放数据
        intBuffer.put(10);
        intBuffer.put(11);
        intBuffer.put(12);
        intBuffer.put(13);
        intBuffer.put(14);

//        如何向Buffer中读取数据
//        将bufer读写切换
        intBuffer.flip();

        while(intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());//取得后,索引后移
        }
    }
}

NIO与BIO的比较

  1. BIO以流的方式处数据,NIO以块的方式处理数据,块I/O的效率比流I/O高很多
  2. BIO是阻塞的,NIO则是非阻塞的
  3. BIO基于字节流和字符流进行操作,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取数据到缓冲区,或者从缓冲区写入到通道中,Selector(选择器)用于监听多个通道的事件(比如:连接请求、数据到达等),因此使用单个线程就可以监听多个客户端通道。

NIO三大核心

在这里插入图片描述

  1. 每个channel都对应一个Buffer
  2. Selector对应一个线程
  3. 一个线程可以对应多个channel,一个channel相当于一个连接
  4. 该图反应了有3个channel注册到了Selector
  5. 程序切换到哪个channel是由事件决定的,Event是一个很重要的事件
  6. Seletor会根据不同的事件在各个通道进行切换
  7. Buffer相当于就是一个内存块,底层是一个数组
  8. 数据的读取是通过Buffer,这个和BIO是完全不同的,BIO要么是输入流或者是输出流,不是双向的;但是NIO的Buffer是可以读也可以写,但是需要有flip进行切换
  9. channel是双向的,可以返回底层操作系统的情况,如linux底层的操作系统通道就是双向的

缓冲区Buffer

缓冲区本质上是一个可以读写数据的内存块,可以理解是一个容器对象(含数组),该对象提供了一组方法,可以更加轻松地使用内存块,缓冲区对像内置了一些机制,能够跟踪和记录缓冲区地状态变化情况,channel提供文件、网络读取数据地渠道,但是读取或写入地数据必须是经过Buffer,如图:
在这里插入图片描述
Buffer即其子类:
在这里插入图片描述
先看看Buffer源码地几个重要地成员属性:
在这里插入图片描述

属性概述
Capacity容量,即可以容纳地最大数据量;在缓冲区创建时被设定并且不能改变
Limit表示缓冲区地当前终点,不能对缓冲区超过极限地位置进行读写操作。且极限是可以修改的
Position位置,下一个要被读或写的元素的索引,每次读写缓冲区数据时都会改变值,为下一次读写操作准备
Mark标记

可以对 文件1 进行代码debug调式:
在这里插入图片描述
当position到达5时,就无法进行写的操作了
接下来我们使用filp进行反转,我们先看看这个方法中的源代码:
在这里插入图片描述

将限制limit定位到当前position,表示读操作时必须在limit范围内,接着将position置为0,进行新的读操作。
Buffer中常用的方法:
在这里插入图片描述

ByteBuffer是Buffer的子类,也是我们进行网络传输最常用的一个类

在这里插入图片描述

通道Channel

基本介绍:

1.BIO中的Stream是单向的,例如FileInputStream对象只能进行读取文件数据的操作,而NIO中的通道channel是双向的,可以读操作,也可以写操作。
2.Channel在NIO中是一个接口,常用的Channel类有:FileChannel、DatagramChannel、ServerSocketChannel、SocketChannel
3.FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据的读写,ServerSocketChannel和SocketChannel用于TCP的数据读写

FileChannel是其中一个重要的类,常见的方法有:
int read(ByteBuffer dst) :从通道读取数据并放到缓冲区中
int write(ByteBuffer src) :把缓冲区的数据写到通道中
long transferFrom(ReadableByteChannel src,long position,long count): 从目标通道中复制数据到当前通道
long transferTo(long position,long count,WritiableByteChannel target) : 把数据从当前通道复制给目标通道
在这里插入图片描述

channel、buffer小案例

建议以下案例自行进行debug,观察buffer中那4个关键字段的变化

1.使用前面学习的ByteBuffer和FileChannel,将一段文字写入到file01.txt中
public class ChannelTest1 {

    public static void main(String[] args) throws Exception{
        String s="hello world";
        //创建一个文件输出流

        FileOutputStream fileOutputStream=new FileOutputStream("d://myNIO//file01.txt");
//        注意:虽然在NIO中我们没有怎么提到输出流,但是Channel实际是输出流的一个包装,所以我们可以通过输出流获得一个Channel
//        fileChannel实际是fileChannelmpl
        FileChannel channel = fileOutputStream.getChannel();

        //创建一个缓冲区,用于内存数据到channel的一个缓冲区
        //这个buffer是被写的
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //将s放入到buffer中
        buffer.put(s.getBytes());

//        现在我们需要将buffer中的数据读到channel中,需要对buffer进行反转
        buffer.flip();

        //将bytebuffer写入到channel
        channel.write(buffer);
        //关闭FileoutputStream即可关闭所有流



    }
}

案例2:

使用channel和buffer读取文件file01.txt

/**
 * 使用channel和buffer读取文件file01.txt
 */
public class ChannelTest2 {
    public static void main(String[] args) throws Exception {
        File file = new File("d://myNIO//file01.txt");
        //创建文件输入流
        FileInputStream fileInputStream = new FileInputStream("d://myNIO//file01.txt");
        //获得通道
        FileChannel channel = fileInputStream.getChannel();
//        创建缓冲区,因为我们文件已知长度,所以这里直接将缓冲区大小设置为和文件大小一样
        ByteBuffer buffer = ByteBuffer.allocate((int)file.length());
        //将通道的数据写入到缓冲区
        int read = channel.read(buffer);
        //从buffer中获取数据并将字节转化为字符串
        //这里直接获取buffer底层的整个数组,与position无关,所以可以不用filp反转了
        System.out.println(new String(buffer.array()));
        fileInputStream.close();



    }
}

案例3

使用一个buffer完成文件的读取
使用channel和read、write方法,完成文本文件的拷贝

public class ChannelTest3 {
    public static void main(String[] args) throws Exception{
        File file = new File("d://myNIO//file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileOutputStream fileOutputStream=new FileOutputStream("d://myNIO//file02.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();

        //获得一个buffer
        ByteBuffer buffer = ByteBuffer.allocate(524);

        while(true){
            buffer.clear();  //重要操作,不然会无限循环
            int read = fileInputStreamChannel.read(buffer);
            if(read==-1){
                break;
            }
            buffer.flip();
            fileOutputStreamChannel.write(buffer);



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



    }
}

案例4:
使用FileChannel和transerFrom方法,完成对文件的拷贝

public class ChannelTest4  {
    public static void main(String[] args) throws  Exception{
//        创建输入流
        FileInputStream fileInputStream = new FileInputStream("d://myNIO//p1.jpg");
        //创建输出流
        FileOutputStream fileOutputStream=new FileOutputStream("d://myNIO//p2.jpg");

        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        //将目标通道的内容复制到该通道
        fileOutputStreamChannel.transferFrom(fileInputStreamChannel,0,fileInputStreamChannel.size());
        fileInputStreamChannel.close();
        fileOutputStreamChannel.close();


    }
}

在这里插入图片描述

通过以上的案例,这里做一个总结

buffer和channel的注意事项:

  1. ByteBuffer支持类型化的put和get,put放入的是什么数据类型,get就应该使用相应的数据类型取出来,否则可能会报错BufferUnderflowException
ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.putInt(3);
        buffer.putChar('e');
        
        buffer.flip();
        
        buffer.getInt();
        buffer.getChar();
  1. 可以将一个普通的Buffer转化成为一个只读的Buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
//readonlyBuffer只可以读,buffer还是可读可写的
  1. NIO提供了MappedByteBuffer,可以让文件直接在内存(堆外的内存)中进行修改,而如何同步到文件由NIO来完成。
    最重要的就是FileChannel中的一个方法可以获得这个直接内存修改的通道对象:
    MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
/**
 * MappedByteBuffer 可以让文件直接在内存(对外内存)进行修改,操作系统不需要进行一次拷贝操作,减少了开销
 */
public class MappedByteBufferTest {
    public static void main(String[] args)throws Exception{
        RandomAccessFile randomAccessFile=new RandomAccessFile("d://myNIO//file01.txt","rw");
        FileChannel channel = randomAccessFile.getChannel();
        //参数一:FileChannel的模式选择,这里为读写模式
        //参数二:  0 :可以直接修改文件的起始文件
        //参数三:  5: 映射到内存的大小,即 1.txt从0开始的多少个字节可以进行直接修改,这里直接修改的范围是1~4
//        mappedByteBuffer是一个抽象类,这里实际类型是directBytebuffer
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
        mappedByteBuffer.put(0,(byte)'E');
//        mappedByteBuffer.put(5,(byte)'L');//会报错
        randomAccessFile.close();

    }
}
  1. 前面的操作我们可以看出基本都是通过一个Buffer进行操作的,NIO还支持通过多个BUffer(即Buffer数组来完成读写操作),即ScatteringGathering,即分散和聚集
  • Scattering:将数据写到buffer时,可以采用Buffer数组,依次写入,即当一个buffer满后,可以使用下一个buffer
  • Gathering:从buffer读取数据时,可以采用buffer数组,一次读取多个buffer
package com.atguigu.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;

/**
 * Scattering:将数据写入到 buffer 时,可以采用 buffer 数组,依次写入 [分散]
 * Gathering:从 buffer 读取数据时,可以采用 buffer 数组,依次读
 */
public class ScatteringAndGatheringTest {

    public static void main(String[] args) throws Exception {
        
        //使用 ServerSocketChannel 和 SocketChannel 网络
        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);

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

        int messageLength = 8; //假定从客户端接收 8 个字节

        //循环的读取
        while (true) {
            int byteRead = 0;

            while (byteRead < messageLength) {
                long l = socketChannel.read(byteBuffers);
                byteRead += l; //累计读取的字节数
                System.out.println("byteRead = " + byteRead);
                //使用流打印,看看当前的这个 buffer 的 position 和 limit
                Arrays.asList(byteBuffers).stream().map(buffer -> "position = " + buffer.position() + ", limit = " + buffer.limit()).forEach(System.out::println);
            }

            //将所有的 buffer 进行 flip
            Arrays.asList(byteBuffers).forEach(buffer -> buffer.flip());
            //将数据读出显示到客户端
            long byteWirte = 0;
            while (byteWirte < messageLength) {
                long l = socketChannel.write(byteBuffers);//
                byteWirte += l;
            }
            
            //将所有的buffer进行clear
            Arrays.asList(byteBuffers).forEach(buffer -> {
                buffer.clear();
            });
            
            System.out.println("byteRead = " + byteRead + ", byteWrite = " + byteWirte + ", messagelength = " + messageLength);
        }
    }
}

Selector(选择器)

  1. java的NIO,用非阻塞的IO方式,可以用一个线程,处理多个客户端的连接,就会使用到Selector
  2. Selector能够检测到多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector)
  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值