NIO是当前Java中最流行的IO方式,大名鼎鼎的网络框架Netty就是基于NIO的。本文将仔细介绍NIO的工作方式。
NIO简介
NIO的底层原理就是对IO进行多路复用,对IO多路复用不太了解的可以看我之前写的文章Linux中网络IO模型详解
通过多路复用可以在一个线程中监听多个连接,节省了线程资源。
NIO详解
NIO中有三个核心: 1、Buffer简介 Buffer就是缓冲池。Buffer和Channel配合使用。 1、将Channel中的数据读取到Buffer中。 2、将Buffer中的数据写入到Channel中。
Java NIO 有以下Buffer类型
ByteBuffer CharBuffer DoubleBuffer FloatBuffer IntBuffer LongBuffer ShortBuffer MappedByteBuffer
Buffer详解 Buffer就是一个数组缓冲池。Buffer有两种模式,往Buffer中写数据,或者从Buffer中读取数据。读写模式通过flip()函数切换。
Buffer默认为写模式。
Buffer主要通过三个变量和一个切换函数flip()来维护。 1、position 当前第一个可以读或者写的下标 2、limit 当前可读或者可写的最后一位下标+1 3、capacity Buffer的容量,也就是数组长度。 4、flip() flip()用来切换读写模式。 当从写切换读之后,position和limit之间的数据是之前写的数据,可以进行读取。 当从读切换写之后,postion和limit之间才可以被写数据。
public final Buffer flip() {
limit = position;
position = 0;
return this;
}
主要方法 get(): 读取一个byte,将position++ get(byte[] bytes,int offeset,int length): 将buffer中的[offset,offset+length)复制到bytes中,position+=length put(byte[] bytes): 往buffer中写入bytes,position += bytes.length clear(): 将buffer清除,position=0,limit=capacity compact(): 假设还有n个字节未被读取,就将这n个字节搬运到数组头部,从数组的第n位开始写。 remaining(): 返回limit - position,也就是当前还是多少字节未读取或者未写
2、Channel Channel是基于流的改进, Channel是面向缓冲区的,并且需要与另一个Buffer配和使用,我的猜测是将数据弄了一个缓冲区,然后通过Buffer将数据拷贝到缓冲区或者从缓冲区里将数据拷贝到Buffer中。
与流有以下区别
| Channel | 流 |
---|
是否支持异步 | 支持异步 | 不支持异步 | 是否支持双向储传输数据 | 双向 | 单向 | 原理 | 缓冲区 | 流 | 性能 | 高 | 低 | 是否集合Buffer使用 | 结合Buffer使用 | 不用 |
网络IO中Channel主要有以下两大类: 1、ServerSocketChannel 用于服务器的Channel,负责接收客户端SocketChannel的连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
}
}
2、SocketChannel 用于接收客户端SocketChannel的数据传输
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
String newData = "New String to write to file..." +
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
3、Selector
NIO的优势就是利用单线程来处理多个Socket。其原理就是依靠Selector。具体的原理可以查看我之前写的文章Linux中网络IO模型详解中的IO复用模型。
Selector就是IO复用中的监视器,一个Selector可以监视多个Socket,Socket需要将其感兴趣的事件注册给Selector。当对应的Socket事件发生后,会将其加入到Selector对应的队列中,然后会将Selector唤醒,执行其业务代码。
Socket共有四个事件: 1、CONNECT 2、ACCEPT 3、READ 4、WRITE
通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“CONNECT”。一个server socket channel准备好接收新进入的连接称为“ACCEPT”。一个有数据可读的通道可以说是“READ”。等待写数据的通道可以说是“WRITE”。
具体的步骤就看下面的代码吧。
代码
服务器端
public class ServerConnect
{
private static final int BUF_SIZE=1024;
private static final int PORT = 8080;
private static final int TIMEOUT = 3000;
public static void main(String[] args)
{
selector();
}
public static void handleAccept(SelectionKey key) throws IOException{
ServerSocketChannel ssChannel = (ServerSocketChannel)key.channel();
SocketChannel sc = ssChannel.accept();
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ);
}
public static void handleRead(SelectionKey key) throws IOException{
SocketChannel sc = (SocketChannel)key.channel();
ByteBuffer buf = ByteBuffer.allocateDirect(BUF_SIZE);
long bytesRead = sc.read(buf);
while(bytesRead>0){
buf.flip();
while(buf.hasRemaining()){
System.out.print((char)buf.get());
}
System.out.println();
buf.clear();
bytesRead = sc.read(buf);
}
if(bytesRead == -1){
sc.close();
}
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
key.attach(buffer);
}
public static void handleWrite(SelectionKey key) throws IOException{
ByteBuffer buffer = (ByteBuffer) key.attachment();
SocketChannel channel = (SocketChannel) key.channel();
if (buffer.hasRemaining()) {
channel.write(buffer)
} else {
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
}
}
public static void selector() {
Selector selector = null;
ServerSocketChannel ssc = null;
try{
selector = Selector.open();
ssc= ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(PORT));
ssc.configureBlocking(false);
ssc.register(selector, SelectionKey.OP_ACCEPT);
while(true){
if(selector.select(TIMEOUT) == 0){
System.out.println("==");
continue;
}
Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
while(iter.hasNext()){
SelectionKey key = iter.next();
if(key.isAcceptable()){
handleAccept(key);
}
if(key.isReadable()){
handleRead(key);
}
if(key.isWritable() && key.isValid()){
handleWrite(key);
}
if(key.isConnectable()){
System.out.println("isConnectable = true");
}
iter.remove();
}
}
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(selector!=null){
selector.close();
}
if(ssc!=null){
ssc.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
}
客户端代码
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));
while (true) {
ByteBuffer buffer = ByteBuffer.allocate(500 * 1024 * 1024);
buffer.clear();
socketChannel.read(buffer);
}
|