程序员:Java NIO 三大部件你都知道吗?

Netty是什么?

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

java NIO包括三大部件

从哪哪哪读(写)数据,数据读(写)到哪里,看看这里或者那里能不能读(写)。

  1. Channel (通道)
  2. Buffer(缓冲器)
  3. Selector(选择器)

在Java NIO里的所有IO操作都从Channel开始。数据可以从 Channel 读取到 Buffer 中,也可以从 Buffer 写到 Channel 中。如下图所示

程序员:Java NIO 三大部件你都知道吗?

Channel 必须配合 Buffer 使用,总是先读取到一个 Buffer 中,又或者是向一个 Buffer 写入。也就是说,我们无法绕过 Buffer ,直接向 Channel 写入数据

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 :从文件中,读写数据。

关于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像是真的数据存入所占的内存所占的相对位置)

在写模式中,往Buffer中写入一个值,position 就自动加 1 ,代表下一次的写入位置。

在读模式中,从 Buffer 中读取一个值,position 就自动加 1 ,代表下一次的读取位置。( 和写模式类似 )

limit属性 意为上限。

写模式下,代表最大能写入的数据上限位置,这个时候 limit 等于 capacity 。

读模式:在Buffer完成数据写入后,通过调用 #flip() 方法,切换到读模式。此时,limit 等于 Buffer 中实际的数据大小(position)。因为 Buffer 不一定被写满,所以不能使用 capacity 作为实际的数据大小。

mark 属性,标记,通过 #mark() 方法,记录当前 position ;通过 reset() 方法,恢复 position 为标记。

写模式下,标记上一次写位置。

读模式下,标记上一次读位置。

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

mark <= position <= limit <= capacity

关于Selector

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

将Channel注册到Selector中,Selector就知道他需要管理哪些Channel。

Selector 会不断地轮询注册在其上的 Channel 。如果某个 Channel 上面发生了读或者写事件,这个 Channel 就处于就绪状态,会被 Selector 轮询出来,然后通过 SelectionKey 可以获取就绪 Channel 的集合,进行后续的 I/O 操作。

程序员:Java NIO 三大部件你都知道吗?

① 优点

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

② 缺点

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

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

0 <= readerIndex <= writerIndex <= capacity

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

相关推荐