1. Java NIO三大部件概述

Netty是什么?
百度百科:

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

Java NIO包括三大部件
从哪哪哪读(写)数据,数据读(写)到哪里,看看这里或者那里能不能读(写)。

  • Channel (通道)
  • Buffer(缓冲器)
  • Selector(选择器)
    在Java NIO里的所有IO操作都从Channel开始。数据可以从 Channel 读取到 Buffer 中,也可以从 Buffer 写到 Channel 中。如下图所示
    在这里插入图片描述
    Channel 必须配合 Buffer 使用,总是先读取到一个 Buffer 中,又或者是向一个 Buffer 写入。也就是说,我们无法绕过 Buffer ,直接向 Channel 写入数据。

1. Channel的实现

Channel在Java中作为一个接口,java.nio.channels.Channel 定义IO操作的联通与关闭

package java.nio.channels;

import java.io.IOException;
import java.io.Closeable;

public interface Channel extends Closeable {

    public boolean isOpen();
//判断通道是否开启
    public void close() throws IOException;
//关闭通道
}

Channel实现类众多,最重要的四个Channel实现类如下:

  • SocketChannel :一个客户端用来发起 TCP 的 Channel 。
  • ServerSocketChannel :一个服务端用来监听新进来的连接的 TCP 的 Channel 。对于每一个新进来的连接,都会创建一个对应的 SocketChannel 。
  • DatagramChannel :通过 UDP 读写数据。
  • FileChannel :从文件中,读写数据。

2. 关于Buffer

Buffer在java.nio包中实现,被定义成抽象类
Buffer有四个重要属性 capacity、limit、 position 、 mark

public abstract class Buffer {

    // Invariants: mark <= position <= limit <= capacity 不变量
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

	    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
    //省略具体方法的代码
}

一个 Buffer ,本质上是内存中的一块,是不是一下子就想通四个属性存在必要啦
capacity 在Buffer创建时被赋值,永远不能被更改

Buffer分为读、写模式两种。两种模式下
position 和 limit 属性分别代表不同的含义。


    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
    
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
  • position属性 意为位置,初值为0
    (position像是真的数据存入所占的内存所占的相对位置)
  1. 在写模式中,往Buffer中写入一个值,position 就自动加 1 ,代表下一次的写入位置。
  2. 在读模式中,从 Buffer 中读取一个值,position 就自动加 1 ,代表下一次的读取位置。( 和写模式类似 )
  • limit属性 意为上限。
  1. 写模式下,代表最大能写入的数据上限位置,这个时候 limit 等于 capacity 。
  2. 读模式:在Buffer完成数据写入后,通过调用 #flip() 方法,切换到读模式。此时,limit 等于 Buffer 中实际的数据大小(position)。因为 Buffer 不一定被写满,所以不能使用 capacity 作为实际的数据大小。
  • mark 属性,标记,通过 #mark() 方法,记录当前 position ;通过 reset() 方法,恢复 position 为标记。
  1. 写模式下,标记上一次写位置。
  2. 读模式下,标记上一次读位置。

四个属性的大小关系遵循:

mark <= position <= limit <= capacity

3. 关于Selector

Selector , 一般称为选择器。它是 Java NIO 核心组件中的一个,用于轮询一个或多个 NIO Channel 的状态是否处于可读、可写。如此,一个线程就可以管理多个 Channel ,也就说可以管理多个网络连接。也因此,Selector 也被称为多路复用器。

  1. 将Channel注册到Selector中,Selector就知道他需要管理哪些Channel。
  2. Selector 会不断地轮询注册在其上的 Channel 。如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。
    在这里插入图片描述

① 优点

使用一个线程能够处理多个 Channel 的优点是,只需要更少的线程来处理 Channel 。事实上,可以使用一个线程处理所有的 Channel 。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源( 例如 CPU、内存 )。因此,使用的线程越少越好。

② 缺点

因为在一个线程中使用了多个 Channel ,因此会造成每个 Channel 处理效率的降低。


在Netty中,对Buffer的操作少了#filp()的操作, 他的基本属性设计为:通过readerIndex和writerIndex两个属性操作。

0 <= readerIndex <= writerIndex <= capacity

通过n个线程处理Channel,解决Channel处理效率低的问题。

发布了26 篇原创文章 · 获赞 4 · 访问量 2390

猜你喜欢

转载自blog.csdn.net/weixin_43257196/article/details/103463002