NIO/Concurrent包/zookeeper/Avro-NIO

NIO

  1. Java中的IO介绍:
    1. BIO:BlockingIO,同步组阻塞式IO,即传统的IO,是Java最早期的流。
    2. NIO:Non-BlockingIO,又称NewIO,同步式非阻塞式IO,sinceJDK1.4的stream
    3. AIO:AsynchronousIO,异步式非阻塞式IO,可以认为式NIO的二代版本,sinceJDK1.8提供的stream
    4. NIO组件:Buffer(缓存),Channel,Selector
  2. BIO的缺点
    1. 阻塞:导致效率整体降低,单向流
    2. 一对一的连接:客户端每过来一个请求,那么在服务器端就需要有一个线程去处理这个请求。如果客户端产生大量请求,回导致服务器端也产生大量的线程去处理请求,服务器端的线程数量一旦过多,会导致服务器卡顿甚至崩溃。
    3. 如果客户端连接之后不产生任何操作,依然会占用服务器端的线程,则会导致服务器资源浪费。
  3. Buffer-缓冲区
    1. 作用:存储数据
    2. 容器:数组,针对基本类型提供了其中对应的Buffer-ByteBuffer(字符缓冲区,继承了Buffer类,底层依靠字节数组来存储数据,本身是个抽象类,需要利用其子类创建对象或者是利用提供的allocate(初始容量,固定)或者wrap方法来创建ByteBuffer对象),ShortBuffer, IntBuffer, LongBuffer, FloatBuffer,DoubleBuffer,CharBuffer
    3. 重要位置:capacity>=limit>=position>=mark
      1. capacity:容量位。用域标记缓冲区的大小,指定以后就不可变。
      2. limit:限制位。用域限制操作位所能达到的最大位置。缓冲区刚创建的时候,limit和capacity重合
      3. position:操作位。类似于数组的下标,用域执行要读写的位置。默认0
      4. mark:标记位。往往是确定数据没有问题的前提下进行标记。后续操作如果出错则从标记位重新开始即可。默认情况下。mark=-1。默认不启用。
    4. 重要操作
      1. flip:翻转缓冲区:将limit挪到position上,然后将position归零。mark重置为-1
        public final Buffer filp(){
        limit=position;
        position=0;
        mark=-1;
        return this;
        }

         

      2.  clear:清空缓冲区,回归缓冲区最开始的状态

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

         

      3.  reset:重置缓冲区,将position挪到mark上

        public final Buffer reset(){
        int m=mark;
        if(m<0)
        throw new InvalidMarkException();
        position=m;
        return this;}

         

      4.  rewind:重绕缓冲区,将position归零,将mark重置为1

        ​
        public final Buffer rewind(){
        position=0;
        mark=-1;
        return this;
        }
        
        ​

         

      5. ByteBuffer重要方法

        1. 方法

          作用

          allocate(int capacity)

          创建缓冲区的时候指定缓冲区容量的大小,实际上是指定缓冲区底层的字节数组的大小

          wrap(byte[] array)

          利用传入的字节数组来构建缓冲区

          array()

          获取缓冲区底层的字节数组

          get()

          获取缓冲区中position位置上的字节

          get(byte[] dst)

          将缓冲区中的数据写到传入的字节数组中

          get(int index)

          获取指定下标上的字节

          put(byte b)

          向position位置上放入指定的字节

          put(byte[] src)

          向position位置上放入指定的字节数组

          put(byte[] src, int offset, int length)

          向position位置上放入指定的字节数组的部分元素

          put(ByteBuffer src)

          将字节缓冲区放入

          put(int index, byte b)

          向指定位置插入指定的字节

          capacity()

          获取容量位

          clear()

          清空缓冲区:position = 0; limit = capacity; mark = -1;

          flip()

          反转缓冲区:limit = position; position = 0; mark = -1;

          hasRemaing()

          判断position和limit之间是否还有空余

          limit()

          获取限制位

          limit(int newLimit)

          设置限制位

          mark()

          设置标记位

          position()

          获取操作位

          position(int newPosition)

          设置操作位

          remaining()

          获取position和limit之间剩余的元素个数

          reset()

          重置缓冲区:position = mark

          rewind()

          重绕缓冲区:position = 0; mark = -1

           

           

 4.Channel-管道

  1. 作用:传输数据
  2. 分类:
    1. 文件-FileChannel
    2. UDP-DatagramChannel
    3. TCP-SocketChannel,ServerSocketChannel
  3. 通道默认式阻塞的,可以手动设置为非阻塞
  4. 通道可以进行双向传输
  5. 操作的时候是面向缓冲区进行的,在NIO中用域完成数据的传输
  6. FileChannel
    1. 概述:面向文件的通道
    2. 可以利用FileChannel万册灰姑娘对文件的读写操作
    3. 利用FileChannel读取文件的时候,是将文件中的内容映射到虚拟内存中,然后再读取到程序的缓冲区
    4. FileChannel不能直接创建,可以利用FileInputStream,FileOutStream,RandomAccessFile对象中的个体Channel()方法获取
    5. 如果是通过FileInputStream获取FileChannel,那么只能进行读取操作
    6. 如果是通过FileOutputStream获取FileChannel,那么只能进行写入操作

    7. 如果是通过RandomAccessFile获取FileChannel,那么可以进行读写操作
  7. 示例:
    读取过程
    @Test
    public void readFile() throws Exception {
     
        // 创建RandomAccessFile对象。指定模式为读写模式
        RandomAccessFile raf = new RandomAccessFile("F:\\a.txt", "rw");
        // 获取FileChannel对象
        FileChannel fc = raf.getChannel();
        // 创建缓冲区用于存储数据
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 记录读取的字节个数
        int len;
        // 读取数据
        while ((len = fc.read(buffer)) != -1) {
            System.out.println(new String(buffer.array(), 0, len));
            buffer.flip();
        }
        // 关流
        raf.close();
     
    }
    
    写入过程
    @Test
    public void writeFile() throws Exception {
     
        // 创建RandomAccessFile对象。指定模式为读写模式
        RandomAccessFile raf = new RandomAccessFile("F:\\test.txt", "rw");
        // 获取FileChannel对象
        FileChannel fc = raf.getChannel();
        // 创建缓冲区,并且将数据放入缓冲区
        ByteBuffer src = ByteBuffer.wrap("hello".getBytes());
        // 利用通道写出数据
        fc.write(src);
        // 关流
        raf.close();
     
    }
    
    复制文件
    @Test
    public void copyFile() throws Exception {
     
        // 创建流对象指向对应的文件
        FileInputStream in = new FileInputStream("F:\\a.txt");
        FileOutputStream out = new FileOutputStream("E:\\a.txt");
     
        // 获取FileChannel对象
        FileChannel src = in.getChannel();
        FileChannel dest = out.getChannel();
     
        // 准备缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 读取数据,将读取到的数据写出
        while (src.read(buffer) != -1) {
            buffer.flip();
            dest.write(buffer);
            buffer.clear();
        }
        // 关流
        in.close();
        out.close();
     
    }
    

     

  8. UDP

    1. 概述:

      1. 用域进行UDP收发的通道

      2. 是无连接的网络协议,只能进行发送和接受的操作

      3. 基本类是DatagramChannel,是一个抽象

      4. 示例:

        发送端
        @Test
        public void send() throws IOException {
        	// 开启通道
        	DatagramChannel dc = DatagramChannel.open();
        	// 准备数据
        	ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes());
        	// 发送数据
        	dc.send(buffer, new InetSocketAddress("localhost", 8090));
        	// 关闭通道
        	dc.close();
        }
        
        接收端
        @Test
        public void recieve() throws IOException {
         
            // 开启通道
            DatagramChannel dc = DatagramChannel.open();
            // 绑定连接地址和端口号
            dc.socket().bind(new InetSocketAddress(8090));
         
            // 准备缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 接收数据
            dc.receive(buffer);
            System.out.println(new String(buffer.array(), 0, buffer.position()));
         
            // 关闭通道
            dc.close();
         
        }
        

         

  9. TCP

    1. 概述

      1. 用域进行TCP通信的通道

      2. 需要进行链接的网络协议

      3. 提供了链接,接受,读取,写入操作

      4. 客户端通道时SocketChannel,服务器端通道时ServerSocketChannel

    2. 示例:

      客户端
      @Test
      public void client() throws IOException {
       
          // 开启客户端通道
          SocketChannel sc = SocketChannel.open();
          // 可以手动设置为非阻塞模式
          sc.configureBlocking(false);
          // 发起连接
          sc.connect(new InetSocketAddress("localhost", 8090));
          // 由于是非阻塞的,所以连接不一定建立了,所以要判断连接是否建立
          while (!sc.isConnected())
              // 如果连接没有建立,则试图重新建立连接
              sc.finishConnect();
          // 写出数据
          sc.write(ByteBuffer.wrap("hello".getBytes()));
          // 关闭通道
          sc.close();
      }
      
      服务器端
      @Test
      public void server() throws IOException {
       
          // 开启服务器端通道
          ServerSocketChannel ssc = ServerSocketChannel.open();
          // 绑定要监听的端口
          ssc.socket().bind(new InetSocketAddress(8090));
          // 手动设置为非阻塞
          ssc.configureBlocking(false);
          // 获取连接过来的通道
          SocketChannel sc = ssc.accept();
          // 判断连接是否真正建立
          while (sc == null)
              // 如果没有建立则重新获取建立
              sc = ssc.accept();
          // 准备缓冲区用于存储数据
          ByteBuffer buffer = ByteBuffer.allocate(1024);
          // 读取数据
          sc.read(buffer);
          System.out.println(new String(buffer.array(), 0, buffer.position()));
          // 关闭通道
          ssc.close();
       
      }
      

       

5.Selector-多路复用选择器

  1. 对通道进行选择,需要级域时间进行驱动
  2. 针对了四类时间:connect,accept,read,write,四类时间定义在Selectionkey中
  3. 可以实现利用一个或者少量线程的大量请求
  4. 适用于大量的短任务场景,不适用于长任务场景
  5. Selector针对的必须是非阻塞的通道
  6. 示例:
    @Test
    public void server() throws IOException {
     
        // 开启服务器端的通道
        ServerSocketChannel ssc = ServerSocketChannel.open();
        // 设置为非阻塞
        ssc.configureBlocking(false);
        // 绑定要监听的端口
        ssc.socket().bind(new InetSocketAddress(8090));
        // 开启选择器
        Selector selector = Selector.open();
        // 将当前通道注册到选择器上,并且为通道注册事件
        ssc.register(selector, SelectionKey.OP_ACCEPT);
     
        while (true) {
            // select()是Selector提供的用于查询是否有事件触发的机制
            // 如果没有事件触发,则会一直阻塞在这儿,如果有事件触发,则阻塞放开
            // 引入Selector的好处是:线程不必每时每刻都去工作、去查询客户端是否有新事件,没有事件就一直阻塞
            // 有事件发生,Selector则放开阻塞。这样一来,可以避免了线程无意义的空转,节省cpu资源,同时也不影响工作
            selector.select();
            // 能走到下面的代码,说明有事件发生,需要进行处理
            // 在处理的时候需要根据事件类型来做相应的处理
            // 对于服务端来说,事件分为:
            // SelectionKey.OP_ACCPET 新客户端接入
            // SelectionKey.OP_WRITE 已接入的客户端给服务端传数据
            // SelectionKey.OP_READ 服务端给已接入的客户端传数据
            Set<SelectionKey> set = selector.selectedKeys();
            // 迭代遍历进行处理
            Iterator<SelectionKey> it = set.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                // 判断是否是一个可接受事件
                if (key.isAcceptable()) {
                    // 从事件中获取到通道
                    ServerSocketChannel ss = (ServerSocketChannel) key.channel();
                    // 接受事件
                    SocketChannel sc = ss.accept();
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ);
     
                }
                // 判断是否是一个可读事件
                if (key.isReadable()) {
                    SocketChannel s = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    s.read(buffer);
                    // 处理完read事件后,需要把read事件从当前的SelectionKey键集里删除,避免重复处理
                    System.out.println("服务端接收到信息:" + new String(buffer.array()));
                    s.register(selector, key.interestOps() ^ SelectionKey.OP_READ);
     
                }
                // 判断是否是一个可写事件
                if (key.isWritable()) {
                    SocketChannel s = (SocketChannel) key.channel();
                    s.write(ByteBuffer.wrap("hello client".getBytes()));
                    s.register(selector, key.interestOps() ^ SelectionKey.OP_WRITE);
                }
                // 防止已处理完毕的SelectionKey再次被处理
                it.remove();
            }
     
        }
     
    }
    

     

6.常见通信框架:

	1. Mina
		a. 主导作者:Trustin Lee(韩国)
		b. Mina(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架
		c. 当前发行的 Mina 版本2.04支持基于 Java NIO 技术的 TCP/UDP 应用程序开发、串口通讯程序
		d. 目前,正在使用Mina的应用包括:Apache Directory Project、AsyncWeb、AMQP(Advanced Message Queuing Protocol)、RED5 Server(Macromedia Flash Media RTMP)、ObjectRADIUS、 Openfire等等
	2. Netty
		a. 主导作者:Trustin Lee(韩国)
		b. Netty是一款异步的事件驱动的网络应用框架和工具,用于快速开发可维护的高性能、高扩展性协议服务器和客户端
		c. Netty是一个NIO客户端/服务器框架,支持快速、简单地开发网络应用,如协议服务器和客户端
		d. 它极大简化了网络编程,如TCP和UDP套接字服务器
	3. Grizzly
		a. Grizzly是一种应用程序框架,专门解决编写成千上万用户访问服务器时候产生的各种问题
		b. Grizzly使用JAVA NIO作为基础,并隐藏其编程的复杂性,提供了容易使用的高性能的API
		c. Grizzly利用了非阻塞socket到协议处理层,使用了高性能的缓冲和缓冲管理使用高性能的线程池

三、Netty简介
	1. Netty是目前最流行的由JBOSS提供的一个Java开源框架NIO框架
	2. Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序
	3.  相比JDK原生NIO,Netty提供了相对十分简单易用的API,非常适合网络编程
	4. Netty是完全基于NIO实现的
	5. 作为一个异步NIO框架,Netty的所有IO操作都是异步非阻塞的,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果
	6. Netty的优点:
		a. API使用简单,开发门槛低
		b. 功能强大,预置了多种编解码功能,支持多种主流协议
		c. 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
		d. 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
		e. 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
		f. 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入
		g. 经历了大规模的商业应用考验
	7. 与Mina相的比较:
		a. 都是Trustin Lee的作品,Netty更晚
		b. Mina将内核和一些特性的联系过于紧密,使得用户在不需要这些特性的时候无法脱离,相比下性能会有所下降,Netty解决了这个设计问题
		c. Netty的文档更清晰,很多Mina的特性在Netty里都有
		d. Netty更新周期更短,新版本的发布比较快
		e. 它们的架构差别不大,Mina靠apache生存,而Netty靠jboss,和jboss的结合度非常高,Netty有对google protocal buf的支持,有更完整的ioc容器支持(spring,guice,jbossmc和osgi)
		f. Netty比Mina使用起来更简单,Netty中可以自定义的处理upstream events或者downstream events,可以使用decoder和encoder来解码和编码发送内容
		g. Netty和Mina在处理UDP时有一些不同,Netty将UDP无连接的特性暴露出来;而Mina对UDP进行了高级层次的抽象,可以把UDP当成"面向连接"的协议,而要Netty做到这一点比较困难
		h. 从任务调度粒度上看,Mina会将有IO任务的session写入队列中,当循环执行任务时,则会轮询所有的session,并依次把session中的所有任务取出来运行。这样粗粒度的调度是不公平调度,会导致某些请求的延迟很高

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宰祖宣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值