最近看了几集各种IO的视频,对于各种IO稍微有了一些感觉。
概念篇
对于了解各种IO,首先需要了解阻塞和非阻塞的概念,以及同步和异步的概念。
所谓阻塞和非阻塞,就是
在访问数据的时候,对于数据是否准备就绪的一种处理方式。
当数据没有准备的时候,
阻塞:需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待。
非阻塞:会直接返回,不需要等待;数据若准备好了,也直接返回。
一个会卡在那,一个则不会;
所谓同步和非同步,就是
基于应用程序和操作系统处理IO事件锁的方式。
同步:应用程序需要直接参与IO读写操作;
处理IO事件时,阻塞在某个方法上等待我们的IO事件完成(阻塞或者轮训IO事件)
异步:交给操作系统操作;非阻塞,不需要等待;操作系统完成IO之后,通知应用程序,即可。
一个需要自己动手,丰衣足食;一个则让别人干,自己干别的去;
而其中,对于同步还可以分成两种,以为
1)阻塞到IO事件
阻塞到read 或者 write,完全不能做自己的事;将读写方法加入到线程里面,然后阻塞线程来实现。对线程性能开销比较大;
2)IO事件轮询 多路复用技术(select模式)
读写事件交给一个单独的线程来处理,这个线程完成IO事件的注册功能,以及不断轮询我们的读写缓冲区,看数据是否准备好。然后通知相应的读写线程。读写线程可以做其他事情,但是select这个线程会被阻塞。
Client select 管家 BOSS
客人来的时候,找管家,说我来了,找BOSS,管家得到这个注册信息之后,告诉Boss,我这里有客人,Boss说你去给A这个东西,给B这个东西。此时,客人可以做点自己的事情,比如看看视频,看看书。当管家知道boss给他任务,他去找对应的客人,告诉他boss给他的东西。(根据注册信息)
这俩的主要区别就是在于一个是现象(卡在那等数据和不卡在那等通知),一个是本质(因为啥都要自己干,所以只能自己等,卡在那;因为有一个中间人,他会找人给我干,干完给我通知,所以不用卡在那;因为找到最厉害的人(操作系统)干,干完给个回复就行,所以完全不用担心,不用卡在那)。
明白上面两个概念之后,BIO(block IO),NIO(non-block IO)和AIO(asynchronous,IO),依次递增;BIO是同步阻塞的,NIO是,同步非阻塞的;AIO则是,异步非阻塞的;从名字上也能看出来。
首先要明白,异步肯定非阻塞;其次对于同步,分为阻塞和非阻塞;也就是到底是完全卡死呢,还是还有点活路呢?
刚刚说BIO和NIO以及AIO是依次递增,是出于对Java IO事件演进的角度来说的:
JAVA IO事件
BIO:JDK 1.4之前,用的都是BIO,阻塞IO阻塞到读写方法-->阻塞到线程来提供性能
NIO:JDK1.4,学习了linux的多路复用技术,select模式,实现IO事件的轮询方法; 同步-非阻塞的模式,主流的网络通信模式。
Mina,netty,Mina2.0 netty5.0 ---网络通信的框架,简化我们自己写NIO的复杂度,并且代码可读性高。
AIO:JDK1.7(NIO2)才是真正的异步,学习Linux的epoll模式。
概念介绍完了,介绍一下原理吧!
NIO的网络通信原理篇
首先,对于网络通信而言,NIO和AIO,他们并没有改变网络通信的基本步骤,只是在原来的基础(serversocket,socket)上做了改进。
原本socket和serversocket建立连接,需要进行三次握手协议;但是三次握手的方式,建立稳定的连接,性能开销较大,解决方式(思路):减少连接的次数。
对读写通信管道进行一个抽象。
上图的理解,对于读和写采用抽象的管道的概念:Channel(管道),是一个TCP的抽象概念,一个TCP连接可以对应多个Channel,而不是以前的方式,只有一个通信信道,减少了TCP连接的次数;读写分离。
大概的意思就是,socket和serversocket通过三次握手,建立一次TCP连接,TCP连接不断开,他们通过他们建立的多个管道进行访问,并且管道还有读写的区分,可以达到读写分离。所以,他们的实现主要靠管道。
NIO和AIO的网络通信架构
通过selector(选择器),相当于管家,管理所有的IO事件,例如客户端的connection,服务端的accept,客户端和服务端的读写,这些IO事件;
Selector(选择器)如何进行管理?
当IO事件注册到选择器的时候,选择器会给他们分配一个Key值(可简单理解成一个时间标签);当IO事件完成之后,通过Key来找到相应的管道,然后通过管道发送数据和接收数据操作.
数据缓冲区:通过bytebuffer,提供很多读写的方法,put(),get();
服务端:serversocketchannel
客户端:socketchannel
选择器:selector selector=Select.open(); 打开选择器
Key:selectionkey,可以通过它来判断IO事件是否已经就绪
Key.isAcceptable,是否可以接受客户端的连接
Key.isconnectionable,是否可以连接服务器,
Key.isreadable(),缓冲区是否可以读;
Key.iswriteable(),缓冲区是否可写;
如何获得事件的keys?
SelectionKey key = Selector.selectedkeys();
如何注册?
以下分别是在将IO(读,写,客户端的连接,服务端的接受)事件注册到选择器上。
Channel.regist(selector,selectionkey.OP_Write);
Channel.regist(selector,selectionkey.OP_Read);
Channel.regist(selector,selectionkey.OP_Connect);
Channel.regist(selector,selectionkey.OP_Accept);
AIO通信架构
API主要有:
服务端:AsynchronousServerSocketChannel
客户端:AsynchronousSocketChannel
用户处理器:CompletionHandler接口,这个接口实现应用程序向操作系统发起IO请求,当完成后处理具体逻辑,否则做自己该做的事. completed和failed方法。
客户端发起连接请求,会由用户处理器(CompletionHanlder)来接收注册。而服务器会监听连接,处理器直接通知服务器的内核,内核完成IO操作,会通知用户处理器,而处理器根据注册事件来通知客户端。
实践篇
对于上面的NIO和AIO架构有了简单的了解之后。可以写点简单的实例;
对于NIO,首先是服务端;
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
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 NIOServer {
private int flag = 1;
//块大小
private int blockSize = 4096;
//发送数据的缓冲区
private ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);
//接收数据缓冲区
private ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);
//选择器
private Selector selector;
//构造方法初始化
public NIOServer(int port) throws IOException {
//打开管道,并设置管道为非阻塞的
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
//管道连接到服务端的socket,绑定ip和端口
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(new InetSocketAddress(port));
//打开选择器,并将管道注册到选择器上
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//服务器启动完成;
System.out.println("Server start->"+port);
}
//监听
public void listen() throws IOException {
while(true) {
//轮询选择器,获得事件列表
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()) {
SelectionKey key = iterator.next();
//事件取到,则从迭代器中移除
iterator.remove();
//业务逻辑
handleKey(key);
}
}
}
//业务逻辑处理
public void handleKey(SelectionKey selectionKey) throws IOException {
ServerSocketChannel server = null;
SocketChannel client = null;
String receviedText;
String sendText;
int count = 0;
//若io事件可以接收客户端的连接
if(selectionKey.isAcceptable()) {
server = (ServerSocketChannel)selectionKey.channel();
client = server.accept();
client.configureBlocking(false);
System.out.println("服务端可以接收到客户端的信息:");
//读事件
client.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()) {
client = (SocketChannel)selectionKey.channel();
//读取到缓冲区
count = client.read(receiveBuffer);
if(count > 0) {
receviedText = new String(receiveBuffer.array(),0,count);
System.out.println("服务端接收到客户端的信息:"+receviedText);
//然后客户端注册一个写的事件
client.register(selector,SelectionKey.OP_WRITE);
}
}else if(selectionKey.isWritable()) {
//清除写的缓冲区
sendBuffer.clear();
client = (SocketChannel)selectionKey.channel();
sendText = "msg send to client"+flag++;
sendBuffer.put(sendText.getBytes());
//发送缓冲区的数据
sendBuffer.flip();
client.write(sendBuffer);
System.out.println("服务端发送数据给客户端:"+sendText);
}
}
public static void main(String[] args) throws IOException {
int port = 7080;
NIOServer server = new NIOServer(port);
server.listen();
}
}
服务端在7080端口上,并且开始了监听连接。
而客户端,则连接到到7080端口上,开始了读写的请求
package nio;
import java.io.IOException;
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 NIOClient {
private static int flag = 1;
//块大小
private static int blockSize = 4096;
//发送数据的缓冲区
private static ByteBuffer sendBuffer = ByteBuffer.allocate(blockSize);
//接收数据缓冲区
private static ByteBuffer receiveBuffer = ByteBuffer.allocate(blockSize);
//服务器地址
private final static InetSocketAddress serverAddress = new InetSocketAddress("127.0.0.1", 7080);
public static void main(String[] args) throws IOException {
//客户端的管道打开,设置为非阻塞
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
//选择器,客户端注册连接服务器事件
Selector selector = Selector.open();
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(serverAddress);
Set<SelectionKey> selectionKeys;
Iterator<SelectionKey> iterator;
SelectionKey selectionKey;
SocketChannel client;
//接收发送数据
String receviedText;
String sendText;
int count;
//监听
while (true) {
//不断获得选择器
selector.select();
selectionKeys = selector.selectedKeys();
iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
selectionKey = iterator.next();
//若IO事件是可以连接的
if (selectionKey.isConnectable()) {
System.out.println("客户端发起连接 client connect");
client = (SocketChannel) selectionKey.channel();
if (client.isConnectionPending()) {
client.finishConnect();
System.out.println("客户端完成连接操作");
//发送消息
sendBuffer.clear();
sendBuffer.put("hello server".getBytes());
//写到缓冲区
sendBuffer.flip();
//将缓冲区的数据发送出去
client.write(sendBuffer);
}
//注册读的事件
client.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) {
//可读,服务端发送过来的数据
client = (SocketChannel) selectionKey.channel();
//先读到缓冲区,然后显示
receiveBuffer.clear();
count = client.read(receiveBuffer);
if (count > 0) {
receviedText = new String(receiveBuffer.array(), 0, count);
System.out.println("客户端接收到服务端的信息:" + receviedText);
}
//注册写事件
client.register(selector, SelectionKey.OP_WRITE);
}
if (selectionKey.isWritable()) {
client = (SocketChannel) selectionKey.channel();
sendBuffer.clear();
sendText = "msg send to server-->" + flag++;
sendBuffer.put(sendText.getBytes());
sendBuffer.flip();
client.write(sendBuffer);
System.out.println("客户端发送信息给服务端:" + sendText);
client.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}
效果就是这样:分别启动服务器和客户端,然后发现两边在不停的通信;
而AIO的服务端
package nio.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;
/**
* AIO的服务端
* 主要监听,监听客户端,并且接收客户端的数据
*/
public class AIOServer {
public AIOServer(int port) throws IOException {
AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port));
listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {
@Override
public void completed(AsynchronousSocketChannel ch, Void vi) {
listener.accept(null,this);//接收下一个连接
handler(ch);
}
@Override
public void failed(Throwable exc, Void vi) {
System.out.println("异步IO失败");
}
});
}
//真正的逻辑
private void handler(AsynchronousSocketChannel ch) {
ByteBuffer buffer = ByteBuffer.allocate(32);
//从管道中读取
try {
ch.read(buffer).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
buffer.flip();
System.out.println("服务端接收客户端的数据:"+buffer.get());
}
public static void main(String[] args) throws IOException, InterruptedException {
//启动服务器
int port = 7080;
AIOServer server = new AIOServer(port);
System.out.println("监听端口"+port);
Thread.sleep(100000);
}
}
AIO的客户端
package nio.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class AIOClient {
private AsynchronousSocketChannel client = null;
public AIOClient(String host,int port) throws IOException, ExecutionException, InterruptedException {
client = AsynchronousSocketChannel.open();
Future<?> future = client.connect(new InetSocketAddress(host,port));
System.out.println(future.get());
}
private void write(Byte b) {
ByteBuffer buffer = ByteBuffer.allocate(32);
buffer.put(b);
buffer.flip();
client.write(buffer);
}
public static void main(String[] args) throws InterruptedException, ExecutionException, IOException {
AIOClient client = new AIOClient("localhost",7080);
client.write((byte) 1);
Thread.sleep(10);
client = new AIOClient("localhost",7080);
client.write((byte) 29);
Thread.sleep(10);
client = new AIOClient("localhost",7080);
client.write((byte) 30);
}
}
最终效果
以上就是我对各种IO的初步体验了,主要还是各种不明白,不过不妨碍我继续学下去,加油!