Java NIO
IO模型
IO模型: http://blog.youkuaiyun.com/lilongjiu/article/details/78147070
IO
传统的IO一般是采用多线程 + 阻塞 IO 达到类似 I/O 复用模型效果。但是这种模式需要为每个客户端Socket创建一个新的Thread,每个线程都占用一定的资源,分配过多的线程会导致资源的浪费。再有,过多的线程会导致更频繁的上下文切换。
public class IOTest {
public static void main(String[] args) throws Exception {
//创建一个服务端Socket,绑定到8899端口号
ServerSocket serverSocket = new ServerSocket(8899);
//创建一个线程池
ExecutorService executorService = Executors.newCachedThreadPool();
Socket clientSoket = null;
while (true) {
//接收客户端连接
clientSoket = serverSocket.accept();
//处理客户端请求
executorService.submit(new Handler(clientSoket));
}
}
static class Handler implements Runnable {
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
process(socket);
} catch (Exception e) {
e.printStackTrace();
}
}
private void process(Socket socket) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter writer = new PrintWriter(socket.getOutputStream());
String line = null;
while (null != (line = reader.readLine())) {
System.out.println("receive message from: " + socket.getRemoteSocketAddress() + ", message is: " + line);
writer.println("response message from: " + socket.getLocalAddress() + ", message is: " + line);
writer.flush();
}
}
}
}
IO 与 NIO 区别
IO
- Java IO 核心概念是Stream
- 面向流编程
- 一个流要么是输入流,要么是输出流,不可能同时既是输入流又是输出流
NIO
- Java NIO 核心概念是Buffer,Channel,Selector,SelectionKeys
- 面向缓冲编程
- Channel既可以读也可以写
Buffer
Buffer是一个存储原生数据类型的容器,一个Buffer是线性的、有限的特定原生数据类型元素序列,它是非线程安全的。
Java NIO - Buffer: http://blog.youkuaiyun.com/lilongjiu/article/details/78145862
Channel
Channel 是 I/O 操作的连接,表示与实体的开放连接,如:硬件设备,文件,网络套接字,或者应用程序组件,这些实体可以执行一个或多个 I/O 读写操作。一个Channel要么处于打开状态,要么出与关闭状态。一个Channel一旦创建就处于打开状态,一旦关闭就会一直处于关闭状态。
下面是一个FileChannel的例子,Channel把数据写到Buffer
public class FileChannelTest {
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream("channel.txt");
//根据FileInputStream 创建文件通道
FileChannel channel = fis.getChannel();
//allocate 静态方法创建buffer
ByteBuffer buffer = ByteBuffer.allocate(1024);
//Channel把数据写到Buffer
int readSize = channel.read(buffer);
while (readSize != -1) {
//一定要调用flip方法
buffer.flip();
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
readSize = channel.read(buffer);
}
}
}
这个例子Channel从Buffer读数据
public class FileChannelTest2 {
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream("channel.txt");
FileChannel channel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("hello file channel".getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
//Channel从Buffer读数据
channel.write(buffer);
}
channel.close();
}
}
从上面的两个例子可以看出,Channel是面向Buffer的,读写数据都是与Buffer交互。
Selector
Selector是一个SelectableChannel对象的多路复用器,SelectableChannel可以注册到Selector上,通过SelectionKey来表示注册到Selector上的Channel。一个Selector维护了三个SelectionKey集合:
- all-keys:当前所有向Selector注册的Channel对应的SelectionKey的集合
- selected-keys :被Selector检测到发生I/O事件的Channel对应的SelectionKey的集合,是all-keys的子集
- cancelled-keys: 已经被取消的SelectionKey的集合,是all-keys的子集
阻塞IO模型中,一般是一个客户端Socket对应一个Thread,如图:
NIO使用Channel,Channel需要注册到Selector上,Selector可以监听到注册其上的Channel的所有I/O事件,然后使用一个线程去处理
Selector上可以注册多个Channel,并监听Channel上的事件
SelectionKey 用来表示Selector上发生事件的Channel
下面是一个比较典型的例子,一个线程就可以处理多个连接:
public class SelectorTest {
public static void main(String[] args) throws IOException {
//通过open方法获取Selector
Selector selector = Selector.open();
//通过open获取ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(8899));
//一定要配置为非阻塞
serverSocketChannel.configureBlocking(false);
//将Channel注册到Selector上,监听连接请求
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//这个方法是阻塞的,当注册到Selector上的Channel有I/O事件发生,则返回
selector.select();
//所有发生I/O事件的Channel对应的SelectionKey,通过SelectionKey可以获取发生I/O事件的Channel
Set<SelectionKey> selectionKeySet = selector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeySet.iterator();
SelectionKey selectionKey = null;
while (iter.hasNext()) {
selectionKey = iter.next();
ServerSocketChannel server = null;
SocketChannel client = null;
//客户端连接请求
if (selectionKey.isAcceptable()) {
//通过SelectionKey获取与之关联的Channel,我们只将ServerSocketChannel到Selector监听连接事件,所以这里一定是ServerSocketChannel
server = (ServerSocketChannel) selectionKey.channel();
client = server.accept();
//这里同样要设置成非阻塞
client.configureBlocking(false);
//客户端的SocketChannel同样需要注册到Selector上,并监听读事件
client.register(selector,SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
//因为我们只将SocketChannel注册到Selector监听读事件,所以这里一定是SocketChannel
client = (SocketChannel) selectionKey.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
//将客户端的数据读取到ByteBuffer,简单起见,这里最多只能读1024字节
int readCount = client.read(readBuffer);
if(readCount < 0){
return;
}
//读写转换,一定要调用flip方法
readBuffer.flip();
//从客户端读取的数据
Charset charset = Charset.forName("UTF-8");
String receiveMessage = String.valueOf(charset.decode(readBuffer).array());
ByteBuffer writeBuffer = ByteBuffer.allocate(2048);
writeBuffer.put(("From server: " + receiveMessage).getBytes());
//读写转换,一定要调用flip方法
writeBuffer.flip();
//向客户端写数据
client.write(writeBuffer);
}
//一定要将本次的selectionKey清除
iter.remove();
}
}
}
}
https://docs.oracle.com/javase/8/docs/api/
http://tutorials.jenkov.com/java-nio/index.html
Netty实战 https://item.jd.com/12070975.html