1. 参考
建议按顺序阅读以下三篇文章
为什么NIO被称为同步非阻塞?
Java IO 与 NIO:高效的输入输出操作探究
【Java.NIO】Selector,及SelectionKey
2. 实战
我们将模拟一个简单的HTTP服务器,它将响应客户端请求并返回一个固定的响应(”Hello, World!”)。我们将使用IO和NIO两种不同的方式实现此服务器。
2.1 传统阻塞IO
import java.io.*;
public class TraditionalIOExample {
public static void main(String[] args) {
try {
// 打开文件
InputStream input = new FileInputStream("example.txt");
OutputStream output = new FileOutputStream("output.txt");
// 读取和写入数据
int data;
while ((data = input.read()) != -1) {
output.write(data);
}
// 关闭文件
input.close();
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
2.2 非阻塞NIO
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioHttpServer {
public static void main(String[] args) {
try {
// 创建服务端通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 绑定访问端口
serverChannel.socket().bind(new InetSocketAddress(8080));
// 通道设置为非阻塞
serverChannel.configureBlocking(false);
// 通过open方法创建一个Selector
Selector selector = Selector.open();
/**
必须将channel注册到selector上,并订阅OP_ACCEPT事件
SelectionKey.OP_CONNECT channel成功连接到另一个服务器称为”连接就绪“
SelectionKey.OP_ACCEPT server socket channel准备好接收新进入的连接称为”接收就绪“
SelectionKey.OP_READ 有数据可读的通道可以说是”读就绪“
SelectionKey.OP_WRITE 有数据可写的通道可以说是”读就绪“
*/
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
// 返回你所感兴趣的事件(连接,接受,读或写)已经准备就绪的那些通道
int readyChannels = selector.select();
if (readyChannels == 0){
continue;
}
// 访问”已选择键集“中的就绪通道
Set<SelectionKey> selectedKeys = selector.selectedKeys();
// 可以遍历这个已选择的集合来访问就绪的通道
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
// 注意每次迭代末尾的remove()调用,Selector不会自己从已选择集中移除SelectioKey实例,必须在处理完通道时自己移除。
keyIterator.remove();
// 一个server socket channel准备号接收新进入的连接称为”接收就绪“
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 客户端socker注册进来
SocketChannel clientChannel = server.accept();
clientChannel.configureBlocking(false);
clientChannel.register(selector, SelectionKey.OP_READ);
// 客户端通道是否有数据流进来
} else if (key.isReadable()) {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannel.read(buffer);
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String request = new String(bytes);
String response = "HTTP/1.1 200 OK\r\n\r\nHello, World!\r\n";
ByteBuffer responseBuffer = ByteBuffer.wrap(response.getBytes());
clientChannel.write(responseBuffer);
clientChannel.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. 模型
上述代码结合该模型,第二次阅读代码,会有更深的理解
4. 原理
多路复用才是NIO不阻塞的原因