一、传统BIO模型
在基于传统同步阻塞模型中:ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入输出流进行同步阻塞式通信。
通信过程:
1)服务端通常由一个独立的Acceptor线程负责监听客户端的连接;
2)Acceptor监听到客户端的连接请求后,为每个客户端创建一个新的线程进行链路处理;
3)链路处理线程完成客户端请求的处理后,通过输出流返回应答给客户端,然后线程销毁。
模型缺点:
1)服务端线程个数与客户端并发访问连接数是1:1的关系;
2)随着客户端并发访问量增大,服务端线程个数线性膨胀,系统性能急剧下降。
二、优化后的BIO模型
服务端通过线程池来处理多个客户端的接入请求,通过线程池约束及调配服务端线程资源。形成客户端个数M:服务端线程池最大线程数N的比例关系。
通信过程:
1)当有新的客户端接入时,将客户端Socket封装成一个Task投递到服务端任务队列;
2)服务端任务线程池中的多个线程对任务队列中的Task进行并行处理;
3)任务线程处理完当前Task后,继续从任务队列中取新的Task进行处理。
模型缺点:
1)BIO的读和写操作都是同步阻塞的,阻塞时间取决于对端IO线程的处理速度和网络IO的传输速度,可靠性差;
2)当线程池中所有线程都因对端IO线程处理速度慢导致阻塞时,所有后续接入的客户端连接请求都将在任务队列中排队阻塞堆积;
3)任务队列堆积满后,新的客户端连接请求将被服务端单线程Acceptor阻塞或拒绝,客户端会发生大量连接失败和连接超时。
三、NIO模型
多路复用器Selector是NIO模型的基础,一个多路复用器Selector可以同时轮询多个注册在它上面的Channel,服务端只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端连接。
模型优点:
1)NIO中Channel是全双工的,Channel比流可以更好地映射底层操作系统的API(UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作);
2)客户端发起的连接操作是异步的,不需要像之前的客户端那样被同步阻塞;
3)JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,使得一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随客户端连接的增加而线性下降,适合做高性能高负载的网络服务器方案。
四、AIO模型
NIO2.0的异步套接字通道是真正的异步非阻塞IO,对应于UNIX网络编程中的事件驱动IO(AIO)。
它不需要通过多路复用器Selector对注册的通道进行轮询操作即可实现异步读写。通过事件驱动+回调函数的方式完成。
五、几种IO模型的功能特性对比
六、NIO模型+Reactor模式的网络服务端
1、Reactor模式思想:分而治之+事件驱动
1)分而治之
一个connection里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。
Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
2)事件驱动
每个Task对应一个特定事件,当Task准备就绪时,对应的事件通知就会发出。
Reactor收到事件通知后,分发给绑定了对应事件的Handler执行Task。
2、单线程版本Reactor模式
1)结构图
Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。
2)客户端连接服务端
a、服务端将绑定了accept事件的Acceptor注册到Reactor中,准备accept新的connection;
b、服务端循环执行Reactor的多路复用器Selector的select功能,监听就绪事件;
c、客户端connect服务器;
d、Reactor的多路复用器监听到accept事件,分发给Acceptor处理事件;
e、Acceptor执行事件Task,接收建立与客户端的连接,并创建一个Handler用于执行该连接的后续请求事件;
f、Handler绑定连接的read事件,并将自己注册到Reactor的Selector中监听。
3)服务端处理客户端请求
a、客户端发送请求;
b、客户端请求到达服务端时,Reactor监听到read事件,将事件分发给对应Handler处理;
c、Handler处理read事件,异步读取客户端请求数据;
d、Handler解析(decode)客户端请求数据;
e、Handler处理(process)客户端请求;
f、Handler重新绑定write事件;
g、当连接可以开始write时,Reactor监听到write事件,将事件分发给Handler处理;
h、Handler处理write事件,异步写出服务端响应数据。
4)模型优缺点
a、单线程版本Reactor模型优点是不需要做并发控制,代码实现简单清晰;
b、缺点是不能利用多核CPU,一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,如果其中decode、process、encode事件的处理很耗时,则服务端无法及时响应其他客户端的请求事件。
3、Reactor模式的其他版本
1)Worker threads
a、使用线程池执行数据的具体处理过程decode、process、encode,提高数据处理过程的响应速度;
b、Reactor所在单线程只需要专心监听处理客户端请求事件accept、read、write;
c、因为Reactor仍是单线程,无法并行响应多个客户端的请求事件(比如同一时刻只能read一个客户端的请求数据)。
2)Multiple reactor threads
a、采用多个Reactor,每个Reactor在自己单独线程中执行,可以并行响应多个客户端的请求事件;
b、Netty采用类似这种模式,boss线程池就是多个mainReactor,worker线程池就是多个subReactor。
注:以上内容参照《Netty权威指南》和网络文章
4、NIO单线程Reactor模式示例代码
package com.zhangyiwen.study.nio.reactor_demo;
import java.io.IOException;
import java.nio.channels.SelectionKey;
/**
* Created by zhangyiwen on 16/11/8.
*/
public interface Handler {
void handle(SelectionKey sk) throws IOException;
}
package com.zhangyiwen.study.nio.reactor_demo;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* Created by zhangyiwen on 16/11/7.
*/
public class EventHandler implements Handler{
SocketChannel socketChannel;
SelectionKey selectionKey;
ByteBuffer readBuffer = ByteBuffer.allocate(MAXIN);
ByteBuffer outBuffer = ByteBuffer.allocate(MAXOUT);
static final int MAXIN = 256*1024;
static final int MAXOUT = 256*1024;
static final Charset charset = Charset.forName("UTF-8");
EventHandler(SocketChannel socketChannel, Selector selector) throws IOException {
this.socketChannel = socketChannel;
// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听该连接上得read事件
this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
// 绑定handler
this.selectionKey.attach(this);
}
@Override
public void handle(SelectionKey sk) throws IOException{
if (sk.isReadable()) {
System.out.println("[event]read");
read();
} else if (sk.isWritable()) {
System.out.println("[event]write");
write();
}
}
/**
* 处理read事件
* @throws IOException
*/
private void read() throws IOException{
// 读取数据
readBuffer.clear();
StringBuilder content = new StringBuilder();
int readNum = socketChannel.read(readBuffer);
if(readNum==0){
return;
}else if(readNum<0){
throw new IOException("exception.");
}else {
readBuffer.flip();
content.append(charset.decode(readBuffer)); //decode
}
while(socketChannel.read(readBuffer) > 0)
{
readBuffer.flip();
content.append(charset.decode(readBuffer)); //decode
}
// 处理数据
process(content.toString());
//
selectionKey.interestOps(SelectionKey.OP_WRITE);
}
/**
* 处理客户端请求数据
* @param content
*/
private void process(String content) throws IOException{
System.out.println("[receive from client] -> client:" + socketChannel.getRemoteAddress() + ", content: " + content);
outBuffer = ByteBuffer.wrap(content.toUpperCase().getBytes());
}
/**
* 处理write事件
* @throws IOException
*/
private void write() throws IOException {
// 写数据
socketChannel.write(outBuffer);
if (outBuffer.remaining() > 0) {
return;
}
//
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
package com.zhangyiwen.study.nio.reactor_demo;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
/**
* Created by zhangyiwen on 16/11/8.
*/
public class Acceptor implements Handler{
static final Charset charset = Charset.forName("UTF-8");
private ServerSocketChannel serverChannel;
private Selector selector;
public Acceptor(ServerSocketChannel serverChannel, Selector selector) {
this.serverChannel = serverChannel;
this.selector = selector;
}
@Override
public void handle(SelectionKey sk) throws IOException{
System.out.println("[event]connect");
// 建立连接
SocketChannel socketChannel = serverChannel.accept();
System.out.println("[new client connected] client:" + socketChannel.getRemoteAddress());
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 创建Handler,专门处理该连接后续发生的OP_READ和OP_WRITE事件
new EventHandler(socketChannel, this.selector);
// 发送欢迎语
socketChannel.write(charset.encode("welcome my client."));
}
}
package com.zhangyiwen.study.nio.reactor_demo;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.Iterator;
/**
* Created by zhangyiwen on 16/11/7.
* 服务端,会先发欢迎语,后续会将客户端发来的消息转成大写后返回
*/
public class NioServer {
private InetAddress hostAddress;
private int port;
private Selector selector;
private ServerSocketChannel serverChannel;
public NioServer(InetAddress hostAddress, int port) throws IOException {
this.hostAddress = hostAddress;
this.port = port;
//初始化selector.绑定服务端监听套接字,感兴趣事件,对应的handler
initSelector();
}
public static void main(String[] args) {
try {
// 启动服务器
new NioServer(null, 9090).start();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 初始化selector,绑定服务端监听套接字、感兴趣事件及对应的handler
* @return
* @throws IOException
*/
private void initSelector()throws IOException {
// 创建一个selector
selector = SelectorProvider.provider().openSelector();
// 创建并打开ServerSocketChannel
serverChannel = ServerSocketChannel.open();
// 设置为非阻塞
serverChannel.configureBlocking(false);
// 绑定端口
serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听客户端连接事件
SelectionKey selectionKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
// 绑定handler
selectionKey.attach(new Acceptor(serverChannel,selector));
}
public void start() {
while (true) {
/*
* 选择事件已经ready的selectionKey,该方法是阻塞的.
* 只有当至少存在selectionKey,或者wakeup方法被调用,或者当前线程被中断,才会返回.
*/
try {
selector.select();
} catch (IOException e) {
e.printStackTrace();
}
// 循环处理每一个事件
Iterator items = selector.selectedKeys().iterator();
while (items.hasNext()) {
SelectionKey key = items.next();
items.remove();
if (!key.isValid()) {
continue;
}
// 事件处理分发
dispatch(key);
}
}
}
/**
* 事件处理分发
* @param sk 已经ready的selectionKey
*/
private void dispatch(SelectionKey sk){
// 获取绑定的handler
Handler handler = (Handler) sk.attachment();
try {
if (handler != null) {
handler.handle(sk);
}
} catch (IOException e) {
e.printStackTrace();
sk.channel();
try {
if(sk.channel()!=null){
sk.channel().close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
package com.zhangyiwen.study.nio.reactor_demo;
import java.io.IOException;
import java.net.InetAddress;
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.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
/**
* Created by zhangyiwen on 16/11/8.
* 手动输入客户端
*/
public class NioClient {
private InetAddress hostAddress;
private int port;
private Selector selector;
private SocketChannel socketChannel;
private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
static final Charset charset = Charset.forName("UTF-8");
public NioClient(InetAddress hostAddress, int port) throws IOException {
this.hostAddress = hostAddress;
this.port = port;
initSelector();
}
public static void main(String[] args) {
try {
new NioClient(InetAddress.getByName("localhost"), 9090);
} catch (IOException e) {
e.printStackTrace();
}
}
private void initSelector() throws IOException {
// 创建一个selector
selector = SelectorProvider.provider().openSelector();
// 打开SocketChannel
socketChannel = SocketChannel.open();
// 设置为非阻塞
socketChannel.configureBlocking(false);
// 连接指定IP和端口的地址
socketChannel.connect(new InetSocketAddress(this.hostAddress, this.port));
// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听服务端已建立连接的事件
socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 开启新线程执行
new Thread(new ClientThread()).start();
//在主线程中 从键盘读取数据输入到服务器端
Scanner scan = new Scanner(System.in);
while(scan.hasNextLine())
{
String line = scan.nextLine();
if("".equals(line)) continue; //不允许发空消息
socketChannel.write(charset.encode(line));//sc既能写也能读,这边是写
}
}
private class ClientThread implements Runnable {
@Override
public void run() {
while (true) {
try {
selector.select();
} catch (Exception e) {
e.printStackTrace();
}
Iterator> selectedKeys = selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
dispatch(key);
}
}
}
/**
* 事件处理分发
* @param key 已经ready的selectionKey
*/
private void dispatch(SelectionKey key){
try {
if (key.isConnectable()) {
System.out.println("[event]connect.");
finishConnection(key);
} else if (key.isReadable()) {
System.out.println("[event]read");
read(key);
}
} catch (IOException e) {
e.printStackTrace();
key.channel();
try {
if(key.channel()!=null){
key.channel().close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
/**
* 完成与服务端连接
* @param key
* @throws IOException
*/
private void finishConnection(SelectionKey key) throws IOException {
SocketChannel socketChannel = (SocketChannel) key.channel();
// 判断连接是否建立成功,不成功会抛异常
socketChannel.finishConnect();
// 设置Key的interest set为OP_WRITE事件
key.interestOps(SelectionKey.OP_READ);
}
/**
* 处理read
* @param key
* @throws IOException
*/
private void read(SelectionKey key) throws IOException {
// 读取数据
SocketChannel socketChannel = (SocketChannel) key.channel();
readBuffer.clear();
StringBuilder content = new StringBuilder();
int readNum = socketChannel.read(readBuffer);
if(readNum==0){
return;
}else if(readNum<0){
throw new IOException("exception.");
}else {
readBuffer.flip();
content.append(charset.decode(readBuffer)); //decode
}
while(socketChannel.read(readBuffer) > 0)
{
readBuffer.flip();
content.append(charset.decode(readBuffer));
}
// 处理数据
process(content.toString(), key);
// 设置Key的interest set为OP_READ事件
// key.interestOps(SelectionKey.OP_READ);
}
/**
* 处理服务端响应数据
* @param content
*/
private void process(String content,SelectionKey key) {
System.out.println("[Client receive from server] -> content: " + content);
}
}