Java NIO理解与使用

Netty的使用或许我们看着官网user guide还是很容易入门的。因为java nio使用非常的繁琐,netty对java nio进行了大量的封装。对于Netty的理解,我们首先需要了解NIO的原理和使用。所以,我也特别渴望去了解NIO这种通信模式。

官方的定义是:nio 是non-blocking的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。是不是很抽象?

在阅读《NIO入门》这篇技术文档之后,收获了很多。包括对Java NIO的理解和使用,所以也特别的感谢作者。

首先,还是来回顾以下从这篇文档中学到的要点。

为什么要使用 NIO?
NIO 的创建目的是为了让 Java 程序员可以实现高速 I/O 而无需编写自定义的本机代码。NIO 将最耗时的 I/O 操作(即填充和提取缓冲区)转移回操作系统,因而可以极大地提高速度。

NIO最重要的组成部分

通道 Channels
缓冲区 Buffers
选择器 Selectors


Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。

在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。
缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

Channel是一个对象,可以通过它读取和写入数据

看完下面这个例子,基本上就理解buffer和channel的作用了

package yyf.java.nio.ibm;

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class CopyFile {
	static public void main(String args[]) throws Exception {

		String infile = "c://test/nio_copy.txt";
		String outfile = "c://test/result.txt";

		FileInputStream fin = new FileInputStream(infile);
		FileOutputStream fout = new FileOutputStream(outfile);
		// 获取读的通道
		FileChannel fcin = fin.getChannel();
		// 获取写的通道
		FileChannel fcout = fout.getChannel();
		// 定义缓冲区,并指定大小
		ByteBuffer buffer = ByteBuffer.allocate(1024);

		while (true) {
			// 清空缓冲区
			buffer.clear();
			//从通道读取一个数据到缓冲区
			int r = fcin.read(buffer);
			//判断是否有从通道读到数据
			if (r == -1) {
				break;
			}
			//将buffer指针指向头部
			buffer.flip();
			//把缓冲区数据写入通道
			fcout.write(buffer);
		}
	}
}

缓冲区主要是三个变量

position
limit
capacity
这三个变量一起可以跟踪缓冲区的状态和它所包含的数据。我们将在下面的小节中详细分析每一个变量,还要介绍它们如何适应典型的读/写(输入/输出)进程。在这个例子中,我们假定要将数据从一个输入通道拷贝到一个输出通道。
Position
您可以回想一下,缓冲区实际上就是美化了的数组。在从通道读取时,您将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。因此,如果您从通道中读三个字节到缓冲区中,那么缓冲区的 position 将会设置为3,指向数组中第四个元素。
同样,在写入通道时,您是从缓冲区中获取数据。 position 值跟踪从缓冲区中获取了多少数据。更准确地说,它指定下一个字节来自数组的哪一个元素。因此如果从缓冲区写了5个字节到通道中,那么缓冲区的 position 将被设置为5,指向数组的第六个元素。
Limit
limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
position 总是小于或者等于 limit。
Capacity
缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。
limit 决不能大于 capacity。

缓冲区作为一个数组,这三个变量就是其中数据的标记,也很好理解。


Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

接下来来看看具体的使用把,我创建了一个直接收消息的服务器(一边接收一边写数据可能对于新手不好理解)

服务端:

package yyf.java.nio.test;

import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioReceiver {
	@SuppressWarnings("null")
	public static void main(String[] args) throws Exception {
		ByteBuffer echoBuffer = ByteBuffer.allocate(8);
		ServerSocketChannel ssc = ServerSocketChannel.open();
		Selector selector = Selector.open();
		ssc.configureBlocking(false);
		ServerSocket ss = ssc.socket();
		InetSocketAddress address = new InetSocketAddress(8080);
		ss.bind(address);
		SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
		System.out.println("开始监听……");
		while (true) {
			int num = selector.select();
			Set selectedKeys = selector.selectedKeys();
			Iterator it = selectedKeys.iterator();
			while (it.hasNext()) {
				SelectionKey sKey = (SelectionKey) it.next();
				SocketChannel channel = null;
				if (sKey.isAcceptable()) {
					ServerSocketChannel sc = (ServerSocketChannel) key.channel();
					channel = sc.accept();// 接受连接请求
					channel.configureBlocking(false);
					channel.register(selector, SelectionKey.OP_READ);
					it.remove();
				} else if (sKey.isReadable()) {
					channel = (SocketChannel) sKey.channel();
					while (true) {
						echoBuffer.clear();
						int r = channel.read(echoBuffer);
						if (r <= 0) {
							channel.close();
							System.out.println("接收完毕,断开连接");
							break;
						}
						System.out.println("##" + r + " " + new String(echoBuffer.array(), 0, echoBuffer.position()));
						echoBuffer.flip();
					}
					it.remove();
				} else {
					channel.close();
				}
			}
		}

	}

}

客户端(NIO):

package yyf.java.nio.test;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NioTest {
	public static void main(String[] args) throws Exception {
		ByteBuffer echoBuffer = ByteBuffer.allocate(1024);
		SocketChannel channel = null;
		Selector selector = null;
		channel = SocketChannel.open();
		channel.configureBlocking(false);
		// 请求连接
		channel.connect(new InetSocketAddress("localhost", 8080));
		selector = Selector.open();
		channel.register(selector, SelectionKey.OP_CONNECT);
		int num = selector.select();
		Set selectedKeys = selector.selectedKeys();
		Iterator it = selectedKeys.iterator();
		while (it.hasNext()) {
			SelectionKey key = (SelectionKey) it.next();
			it.remove();
			if (key.isConnectable()) {
				if (channel.isConnectionPending()) {
					if (channel.finishConnect()) {
						// 只有当连接成功后才能注册OP_READ事件
						key.interestOps(SelectionKey.OP_READ);
						echoBuffer.put("123456789abcdefghijklmnopq".getBytes());
						echoBuffer.flip();
						System.out.println("##" + new String(echoBuffer.array()));
						channel.write(echoBuffer);
						System.out.println("写入完毕");
					} else {
						key.cancel();
					}
				}
			}
		}

	}
}

运行结果:

开始监听……
##8 12345678
##8 9abcdefg
##8 hijklmno
##2 pq
接收完毕,断开连接



当然,BIO的客户端也可以,开启10个BIO客户端线程

package yyf.java.nio.test;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Random;

import yyf.java.test.Main;

public class BioClientTest {
	public static void main(String[] args) throws Exception {
		BioClient n = new BioClient();
		for (int i = 0; i < 10; i++) {
			Thread t1 = new Thread(n);
			t1.start();
		}
	}
}

class BioClient implements Runnable {
	@Override
	public void run() {

		try {
			Socket socket = new Socket("127.0.0.1", 8080);
			OutputStream os = socket.getOutputStream();
			InputStream is = socket.getInputStream();
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			String str = Thread.currentThread().getName() + "...........sadsadasJava";
			os.write(str.getBytes());
			StringBuffer sb = new StringBuffer();
			byte[] b = new byte[1024];
			int len;
			while ((len = is.read(b)) != -1) {
				bos.write(b, 0, len);
			}
			is.close();
			os.close();
			socket.close();
			System.out.println(Thread.currentThread().getName() + " 写入完毕 " + new String(bos.toByteArray()));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
运行结果:

##8 Thread-4
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-3
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-9
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-7
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-0
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-5
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-2
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-8
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-1
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接
##8 Thread-6
##8 ........
##8 ...sadsa
##7 dasJava
接收完毕,断开连接

当然,这只是一个测试,对于一个服务器,是有读取,也有写出的,这是文档给的一个服务端例子

package yyf.java.nio.ibm;

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class MultiPortEcho {
	private int ports[];
	private ByteBuffer echoBuffer = ByteBuffer.allocate(5);

	public MultiPortEcho(int ports[]) throws IOException {
		this.ports = ports;
		go();
	}

	private void go() throws IOException {
		Selector selector = Selector.open();
		for (int i = 0; i < ports.length; ++i) {
			ServerSocketChannel ssc = ServerSocketChannel.open();
			ssc.configureBlocking(false);
			ServerSocket ss = ssc.socket();
			InetSocketAddress address = new InetSocketAddress(ports[i]);
			ss.bind(address);
			SelectionKey key = ssc.register(selector, SelectionKey.OP_ACCEPT);
			System.out.println("Going to listen on " + ports[i]);
		}

		while (true) {
			int num = selector.select();
			Set selectedKeys = selector.selectedKeys();
			Iterator it = selectedKeys.iterator();
			while (it.hasNext()) {
				SelectionKey key = (SelectionKey) it.next();
				if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
					ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
					SocketChannel sc = ssc.accept();
					sc.configureBlocking(false);
					SelectionKey newKey = sc.register(selector, SelectionKey.OP_READ);
					it.remove();
					System.out.println("Got connection from " + sc);
				} else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
					SocketChannel sc = (SocketChannel) key.channel();
					int bytesEchoed = 0;
					while (true) {
						echoBuffer.clear();
						int r = sc.read(echoBuffer);
						if (r <= 0) {
							sc.close();
							break;
						}
						echoBuffer.flip();
						sc.write(echoBuffer);
						bytesEchoed += r;
					}
					System.out.println("Echoed " + bytesEchoed + " from " + sc);
					it.remove();
				}

			}
			// System.out.println( "going to clear" );
			// selectedKeys.clear();
			// System.out.println( "cleared" );
		}
	}

	static public void main(String args[]) throws Exception {
		int ports[] = new int[] { 8080 };
		for (int i = 0; i < args.length; ++i) {
			ports[i] = Integer.parseInt(args[i]);
		}
		new MultiPortEcho(ports);
	}
}

现在,我们就写个客户端去跟服务器通信,把发过去的返回来:

package yyf.java.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import javax.swing.ButtonGroup;

public class NioClient {
	public static void main(String[] args) throws IOException {
		SocketChannel socketChannel = SocketChannel.open();
		SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8080);
		socketChannel.connect(socketAddress);
		String str = "你好a";
		ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
		socketChannel.write(buffer);
		socketChannel.socket().shutdownOutput();
	
		buffer.clear();
		byte[] bytes;
		int count = 0;
		while ((count = socketChannel.read(buffer)) > 0) {
			buffer.flip();
			bytes = new byte[count];
			buffer.get(bytes);
			System.out.println(new String(buffer.array()));
			buffer.clear();
		}
		socketChannel.socket().shutdownInput();
		socketChannel.socket().close();
		socketChannel.close();
	}
}


运行结果

server:

Going to listen on 8080
Got connection from java.nio.channels.SocketChannel[connected local=/127.0.0.1:8080 remote=/127.0.0.1:63584]
Echoed 7 from java.nio.channels.SocketChannel[closed]

client:

你好a

对于NIO的入门就先到这里。







你可能感兴趣的:(Java)