1、缓冲区Buffer
在NIO库中,所有数据都是用缓冲区(Buffer)处理的。缓冲区实质上是一个数组,通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置(limit、position)等信息。
2、通道Channel
Channel是一个通道,可以通过它读取和写入数据。通道与流的不同之处在于通道是双向的,流只是一个方向上移动(一个流必须是InputStream或者OutputStream的子类),而且通道可以同时用于读写。
3、多路复用器Selector
多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获得就绪Channel的集合,进行后续的I/O操作。
介绍完NIO几个核心概念,下面来看看服务端与客户端的通信序列图,并用NIO实现一个EchoServer,以对整个流程有更好的理解。
package io.nio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
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 NioEchoServer {
public static void main(String[] args) {
MultiplexerEchoServer server = new MultiplexerEchoServer(8080);
new Thread(server).start();
new Thread(new Power(server)).start();
}
private static class MultiplexerEchoServer implements Runnable {
private Selector selector;
private ServerSocketChannel server;
private volatile boolean running;
public MultiplexerEchoServer(int port) {
try {
running = true;
selector = Selector.open();
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.socket().bind(new InetSocketAddress(port), 1024);
server.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server start in:" + port);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop() {
running = false;
}
@Override
public void run() {
while (running) {
try {
selector.select(1000);
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> it = readyKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handle(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handle(SelectionKey key) throws IOException {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
// 大于0:读到了字节、0:没有读到字节、-1:链路已经关闭
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, "UTF-8");
System.out.println("Server received:" + msg);
doWrite(sc, msg);
} else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
private void doWrite(SocketChannel channel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
channel.write(buffer);
}
}
}
private static class Power implements Runnable {
private MultiplexerEchoServer server;
public Power(MultiplexerEchoServer server) {
this.server = server;
}
@Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
String msg = in.readLine();
while (msg != null && !"shutdown".equalsIgnoreCase(msg)) {
msg = in.readLine();
}
server.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package io.nio;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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 NioEchoClient {
public static void main(String[] args) {
EchoClient client = new EchoClient("127.0.0.1", 8080);
new Thread(client).start();
new Thread(new Writer(client)).start();
}
private static class EchoClient implements Runnable {
private String host;
private int port;
private Selector selector;
private SocketChannel client;
private volatile boolean running;
public EchoClient(String host, int port) {
try {
this.running = true;
this.host = host == null ? "127.0.0.1" : host;
this.port = port;
this.selector = Selector.open();
this.client = SocketChannel.open();
client.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (running) {
try {
selector.select(1000);
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> it = readyKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
handle(key);
} catch (Exception e) {
e.printStackTrace();
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
System.exit(1);
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handle(SelectionKey key) throws IOException {
if (key.isValid()) {
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
} else {
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(buffer);
// 大于0:读到了字节、0:没有读到字节、-1:链路已经关闭
if (readBytes > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, "UTF-8");
System.out.println("Client received:" + msg);
} else if (readBytes < 0) {
key.cancel();
sc.close();
}
}
}
}
private void doConnect() throws IOException {
if (client.connect(new InetSocketAddress(host, port))) {
client.register(selector, SelectionKey.OP_READ);
} else {
client.register(selector, SelectionKey.OP_CONNECT);
}
}
public void doWrite(String msg) {
byte[] bytes = msg.getBytes();
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
try {
client.write(buffer);
System.out.println("Cliend send:" + msg);
} catch (IOException e) {
e.printStackTrace();
}
}
public void stop() {
running = false;
}
}
private static class Writer implements Runnable {
private EchoClient client;
public Writer(EchoClient client) {
this.client = client;
}
@Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
String msg = in.readLine();
while (msg != null && !"quit".equalsIgnoreCase(msg)) {
client.doWrite(msg);
msg = in.readLine();
}
client.stop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}