#BIO---Blocking IO
- 每个62616964757a686964616fe78988e69d8331333365633865socket一个线程,读写时线程处于阻塞状态。
优点:实现简单
缺点:无法满足高并发,高接入的需求
- 不使用线程池的BIO模型,除了无法满足高并发需求外,由于需要为每个请求创建一个线程,还可能因为接入大量不活跃连接而耗尽服务器资源。
- 使用线程池的BIO模型,虽然控制了线程数量,但由于其本质上读写仍是阻塞的,仍无法满足高并发需求。
#NIO---Non-Blocking IO(非阻塞IO)
##非阻塞IO和多路复用
非阻塞IO和多路复用实际上是两个不用的概念,由于两者通常结合在一起使用,因此两者往往被混为一谈。下面我将试着分清这两个概念:
###非阻塞IO
与BIO相对应,非阻塞IO的读写方法无论是否有数据都立即返回,因此可以通过轮询方式来实现,但轮询方式的效率并不比BIO有显著提高,因为每个连接仍然需要占用一个线程。下面是轮询方式实现的IO模式图:
###多路复用
- 多路复用结合非阻塞IO能够明显提高IO的效率,这也是Java1.4把非阻塞IO和多路复用同时发布的原因。
- 多路复用的核心是多路复用器(Selector),它是需要操作系统底层支持的,简单的说,就是进程把多个socket和它们关心的事件(比如连接请求或数据已准备好)都注册在多路复用器上,操作系统会在事件发生时通知多路复用器,这样进程就可以通过多路复用器知道在那个socket上发生了什么时间,从而进行对应的处理。
- 多路复用的优点在于只需要一个线程监测(阻塞或轮询方式均可)多路选择器的状态,只有在有事件需要发生时才会真正的创建线程进行处理,因此更适合高并发多接入的应用环境。
- 在Linux系统下,多路复用的底层实现是epoll方法,与select/poll的顺序扫描不同,epoll采用效率更高的事件驱动方式,而且epoll方式并没有socket个数限制。
##BIO和NIO的比较
- BIO适用于连接长期保持的应用,比如一个复杂系统中模块之间通过长连接来进行通信。
- NIO加多路复用的模式更适合短连接、高并发、多接入的情形,比如网络服务器。
##NIO网络编程的常用接口
##Reactor模式
Reactor模式用于解决事件分发处理的问题,Handler把自己的channel和关注的事件注册到Selector中,当对应的事件发生在自己的channel上时,对应的handler就会得到通知并进行处理。
- 单线程的Reactor
消息的分发、读写、处理都在一个线程中处理,是Reactor最简单的实现方式,如果消息的处理需要较长时间,会影响效率。
```java
//Reactor类,负责分发事件并调用对应的handler
class Reactor implements Runnable {
final Selector selector;
final ServerSocketChannel serverSocket;
//Reactor初始化
Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false); //必须配置为非阻塞
//Acceptor会在Reactor初始化时就注册到Selector中,用于接受connect请求
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor()); //attach callback object, Acceptor
}
//分发消息并调用对应的handler
public void run() {
try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())
dispatch((SelectionKey)(it.next()); //Reactor负责dispatch收到的事件
selected.clear();
}
} catch (IOException ex) { /* ... */ }
}
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment()); //调用之前注册的callback对象
if (r != null)
r.run();
}
//Acceptor也是一个handler,负责创建socket并把新建的socket也注册到selector中
class Acceptor implements Runnable { // inner
public void run() {
try {
SocketChannel c = serverSocket.accept();
if (c != null)
new Handler(selector, c);
}
catch(IOException ex) { /* ... */ }
}
}
}
//Concrete Handler:用于收发和处理消息。
//在当前的实现中,使用Runnable接口作为每个具体Handler的统一接口
//如果在处理时需要参数和返回值,也可以为Handler另外声明一个统一接口来代替Runnable接口
final class Handler implements Runnable {
final SocketChannel socket;
final SelectionKey sk;
ByteBuffer input = ByteBuffer.allocate(MAXIN);
ByteBuffer output = ByteBuffer.allocate(MAXOUT);
static final int READING = 0, SENDING = 1;
int state = READING;
Handler(Selector sel, SocketChannel c) throws IOException {
socket = c; c.configureBlocking(false);
// Optionally try first read now
sk = socket.register(sel, 0);
sk.attach(this); //将Handler作为callback对象
sk.interestOps(SelectionKey.OP_READ); //第二步,接收Read事件
sel.wakeup();
}
boolean inputIsComplete() { /* ... */ }
boolean outputIsComplete() { /* ... */ }
void process() { /* ... */ }
public void run() {
try {
if (state == READING) read();
else if (state == SENDING) send();
} catch (IOException ex) { /* ... */ }
}
void read() throws IOException {
socket.read(input);
if (inputIsComplete()) {
process();
state = SENDING;
// Normally also do first write now
sk.interestOps(SelectionKey.OP_WRITE); //第三步,接收write事件
}
}
void send() throws IOException {
socket.write(output);
if (outputIsComplete()) sk.cancel(); //write完就结束了, 关闭select key
}
}
//上面 的实现用Handler来同时处理Read和Write事件, 所以里面出现状态判断
//我们可以用State-Object pattern来更优雅的实现
class Handler { // ...
public void run() { // initial state is reader
socket.read(input);
if (inputIsComplete()) {
process();
sk.attach(new Sender()); //状态迁移, Read后变成write, 用Sender作为新的callback对象
sk.interest(SelectionKey.OP_WRITE);
sk.selector().wakeup();
}
}
class Sender implements Runnable {
public void run(){ // ...
socket.write(output);
if (outputIsComplete()) sk.cancel();
}
}
}
```
- 多线程Reacotr
处理消息过程放在其他线程中执行
```java
class Handler implements Runnable {
// uses util.concurrent thread pool
static PooledExecutor pool = new PooledExecutor(...);
static final int PROCESSING = 3;
// ...
synchronized void read() { // ...
socket.read(input);
if (inputIsComplete()) {
state = PROCESSING;
pool.execute(new Processer()); //使用线程pool异步执行
}
}
synchronized void processAndHandOff() {
process();
state = SENDING; // or rebind attachment
sk.interest(SelectionKey.OP_WRITE); //process完,开始等待write事件
}
class Processer implements Runnable {
public void run() { processAndHandOff(); }
}
}
```
- 使用多个selector
mainReactor只负责处理accept并创建socket,多个subReactor负责处理读写请求
```java
Selector[] selectors; //subReactors集合, 一个selector代表一个subReactor
int next = 0;
class Acceptor { // ...
public synchronized void run() { ...
Socket connection = serverSocket.accept(); //主selector负责accept
if (connection != null)
new Handler(selectors[next], connection); //选个subReactor去负责接收到的connection
if (++next == selectors.length) next = 0;
}
}
```
#AIO
AIO是真正的异步IO,它于JDK1.7时引入,它和NIO的区别在于:
- NIO仍然需要一个线程阻塞在select方法上,AIO则不需要
- NIO得到数据准备好的消息以后,仍然需要自己把消息复制到用户空间,AIO则是通过操作系统的支持把数据异步复制到用户空间以后再给应用进程发出信号。