简介
NIO是非阻塞的I/O。java NIO基于linux的多路复用I/O模型,epoll机制。
I/0复用模型:Linux提供select/poll,进程通过将一个或多个fd传递给 select 或poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的f数量有限,因此它的使用受到了一些制约。Linux还提供了一个epoll系统调用,epoll使用基于事件驱动方式代替顺序扫描,因此性能更高。当有fd就绪时,立即回调函数rollback。 -------此段文字和下图摘自《《Netty权威指南》》
NIO相关类库和概念介绍
Buffer
Buffer是缓冲区,缓冲区是特定基本类型的元素的线性有限序列。除此之外,缓冲区的基本属性还包括其容量、限制和位置(capacity、limit、position)。
capacity
capacity是Buffer的容量。缓冲区的容量永远不会为负数,也永远不会改变。
limit
缓冲区的限制,是一个不应读取或写入的元素的索引。缓冲区的限制永远不会为负数,并且永远不会大于其容量。 读模式的时候,limit是限制读取的位置,写模式的时候,limint是Buffer的最大容量等于capacity
position
缓冲区的位置,是要读取或写入的下一个元素的索引。缓冲区的位置永远不会为负数,也永远不会大于其限制。 读模式的时候position是,读取的位置,写模式的时候,position是写入数据的位置指针。
关于Buffer的其他api(如clear(),flip(),rewind()等)本文不做详细介绍,具体参考java api文档。
Channel
Channel是一个通道,I/O操作的枢纽,通道表示与实体(如硬件设备、文件、网络套接字或程序组件)的开放连接,这些实体能够执行一个或多个不同的 IO 操作 (例如,读取或写入)。Channel是双全工的,读取和写入可通过Channel。
Selector
Selector是多路复用器。Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/0操作。
代码事例
与Bio Socket类和 ServerSocket类相对应,NIO也提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现.
NIOServer
public static void main(String[] args) throws IOException {
NIOServerHandler server = new NIOServerHandler();
Thread t = new Thread(server);
t.start();
}
NIOServerHandler
public class NIOServerHandler implements Runnable {
private ServerSocketChannel sers;
private Selector selector;
private volatile boolean running;
public NIOServerHandler() throws ClosedChannelException {
try {
//打开服务套接字通道
this.sers = ServerSocketChannel.open();
//设置为非阻塞
sers.configureBlocking(false);
//获取ServerSocket
ServerSocket socket = sers.socket();
//绑定端口
socket.bind(new InetSocketAddress(18080));
this.selector = Selector.open();
//注册连接事件
sers.register(this.selector, SelectionKey.OP_ACCEPT);
running = true;
} catch (IOException e) {
System.exit(1);
}
}
@Override
public void run() {
while (running) {
try {
//
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isValid()) {
if (key.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.register(this.selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = socketChannel.read(buffer);
if (read > 0) {
//读取客服端消息
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, "UTF-8");
System.out.println("服务器接收到了客户端消息:" + msg);
//给客户端响应消息
String msgToClient = "hell client----";
ByteBuffer bufferToClient = ByteBuffer.wrap(msgToClient.getBytes());
socketChannel.write(bufferToClient);
} else if (read < 0) {
key.cancel();
socketChannel.close();
} else {
//读取0字节,忽略
}
}
}
} catch (IOException e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
NIOClient
public static void main(String[] args) {
NIOClientHandler client = new NIOClientHandler();
Thread t = new Thread(client);
t.start();
}
NIOClientHandler
public class NIOClientHandler implements Runnable {
private SocketChannel sc;
private Selector selector;
private volatile boolean running;
public NIOClientHandler() {
try {
selector = Selector.open();
sc = SocketChannel.open();
sc.configureBlocking(false);
running = true;
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
boolean connect = sc.connect(new InetSocketAddress(18080));
if (connect) {
sc.register(selector, SelectionKey.OP_READ);
String msg = "hell server-----";
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
sc.write(buffer);
} else {
sc.register(selector, SelectionKey.OP_CONNECT);
}
} catch (IOException e) {
System.exit(1);
}
while (true) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isValid()) {
SocketChannel channel = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (channel.finishConnect()) {
channel.register(selector, SelectionKey.OP_READ);
String msg = "hell server-----";
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
sc.write(buffer);
} else {
System.exit(1);
}
}
if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if (read > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String msg = new String(bytes, "UTF-8");
System.out.println("客户端接收到了服务器的消息:" + msg);
} else if (read < 0) {
channel.close();
} else {
//dnt
}
}
}
} catch (IOException e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (IOException e) {
if (selector != null) {
try {
selector.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
}
}
}