最通俗易懂的NIO讲解
2018-02-23 12:33
561 查看
一、NIO是什么?
NIO的全称是New I/O,与之相对应的是Java中传统的I/O,这里都指的是Java的API包。传统的IO包提供的是同步阻塞IO,即当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU),当数据就绪后,内核会将数据复制到用户线程,并把结果返回给用户线程,同时接触用户线程的阻塞,同步体现在用户线程需要等待数据就绪后才能向后执行(后面的执行依赖于前面的结果)。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,线程数量也会受到。
而NIO包提供的IO是同步非阻塞IO,非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。而同步体现在用户线程需要主动去轮询直到发现数据就绪,再主动将数据从内核拷贝到用户线程。服务器实现模式为多个连接一个线程(IO多路复用),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
传统IO模型 | NIO | |
客户端个数IO线程 | 1:1 | M:1(1个IO线程处理多个客户端连接) |
IO类型(阻塞) | 阻塞IO | 非阻塞IO |
IO类型(同步) | 同步IO | 同步IO(IO多路复用) |
API使用难度 | 简单 | 非常复杂 |
调式难度 | 简单 | 复杂 |
可靠性 | 非常差 | 高 |
吞吐量 | 低 | 高 |
二、NIO包API的用法。
(1)Channel:Java NIO中的所有I/O操作都基于Channel对象,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。FileChannel:文件通道,从输入流输出流中获取实例,常见的使用场景就是从一个文件拷贝其内容到另一个文件。FileInputStream fis = new FileInputStream("E://zfb.txt");
FileChannel ifc = fis.getChannel();
FileOutputStream os = new FileOutputStream("E://zfb2.txt");
FileChannel ofc = os.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (ifc.read(buffer) != -1){
System.out.println(1);
buffer.flip();
ofc.write(buffer);
buffer.clear();
}SocketChannel/ServerSocketChannel:套接字通道,通过静态方法获取实例,使用场景在最后会给出demo。SocketChannel socketChannel = SocketChannel.open();(2)Buffer:NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据。与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是capacity (总容量)、position (指针当前位置)、limit (读/写边界位置)。
接下来看三个方法flip、rewind、clearpublic final Buffer flip() {//用于写模式到读模式的转换
limit = position;//设置上届为当前位置
position = 0;//当前位置设置为0
mark = -1;//重置写标记
return this;
}
public final Buffer rewind() {//设置当前位置为0 position = 0; mark = -1; return this; }
public final Buffer clear() {//通过指针移动的方式清空缓冲区 position = 0;//设置当前位置为0 limit = capacity;//上限为容量 mark = -1; return this; }
(3)Selector:Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置关心的事件,然后就可以通过调用select()方法,监听关注事件的发生。通道有4个事件可供我们监听:Accept、Connect、Read、Write。
selector.select()方法是阻塞的方法!!!若注册的通道没有事件到达,则不会向下执行。但是NIO的IO机制是非阻塞的!!!
三、NIO的编程模型——基于Reactor模式的事件驱动。
NIO是同步非阻塞的,但是提供了基于Selector的异步网络IO。这句话的意思是NIO的IO机制是同步非阻塞的,而基于Selector这个组件的编程模型(Reactor)是事件驱动的,是异步的。下面来看一下一般采用NIO的编程模型:
demo服务端代码:public class NIOServer {
private final static int BUFFER_SIZE = 1024;
private final static int PORT = 52621;
private ServerSocketChannel serverSocketChannel;
private ByteBuffer byteBuffer;
private Selector selector;
public NIOServer(){
try {
init();
} catch (IOException e) {
e.printStackTrace();
}
}
public void init() throws IOException {//初始化
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(PORT));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
}
private void listen() throws IOException {
while (true){
if (selector.select() == 0){
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if (key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
replyClient(socketChannel);
}
if (key.isReadable()){
readDataFromClient(key);
}
iterator.remove();
}
}
}
private void readDataFromClient(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
byteBuffer.clear();
int n;
while ((n = socketChannel.read(byteBuffer)) > 0){
byteBuffer.flip();
while (byteBuffer.hasRemaining()){
socketChannel.write(byteBuffer);
}
byteBuffer.clear();
}
if (n < 0){
socketChannel.close();
}
}
private void replyClient(SocketChannel channel) throws IOException {
byteBuffer.clear();
byteBuffer.put("Hello,I am server!".getBytes());
byteBuffer.flip();
channel.write(byteBuffer);
}
public static void main(String[] args) {
try {
new NIOServer().listen();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:public class NIOClient {
private final static int BUFFER_SIZE = 1024;
private final static int PORT = 52621;
private SocketChannel socketChannel;
private ByteBuffer byteBuffer;
public void connect() throws IOException {
socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", PORT));
byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
receive();
}
private void receive() throws IOException {
while (true){
int n;
byteBuffer.clear();
while ((n = socketChannel.read(byteBuffer)) > 0){
byteBuffer.flip();
System.out.println("从服务器收到消息: " + new String(byteBuffer.array()));
//socketChannel.write(byteBuffer);//发消息
byteBuffer.clear();
}
}
}
public static void main(String[] args) {
try {
new NIOClient().connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
相关文章推荐
- C#2.0全能数据库组件 (通俗易懂)
- ASP.NET 2.0 中改进的缓存功能 - 很通俗易懂
- 关于网关的精典描述通俗易懂
- C#中的委托和事件(通俗易懂)
- 软件架构之美在于简单、好用、稳定、功能定位明确、代码简洁、通俗易懂
- 通俗易懂讲解委托的使用妙文(C#)
- [转]通俗易懂Tomcat中Servlet的生命周期,讲的非常详细
- 还是转载了,通俗易懂
- java 接口用法(通俗易懂)
- Spring AOP概念理解 (通俗易懂)
- 数据挖掘的数据分析方法有哪些(通俗易懂)
- 通俗易懂解释java反射机制(一)
- 通俗易懂的字符串匹配的KMP算法讲解
- Lambda表达式介绍 (通俗易懂)
- A星算法详解(个人认为最详细,最通俗易懂的一个版本)
- 数字签名是什么—讲解数字签名最通俗易懂的文章
- 通俗易懂的 JSon解析处理
- SQL Server Profiler使用教程,通俗易懂才是王道
- 数据库范式——通俗易懂【转】
- 三极管的工作原理(详细、通俗易懂、图文并茂)