JAVA IO模型演进及Reactor模式

一、传统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模式示例代码

?
1
2
3
4
5
6
7
8
9
10
11
12
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;
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
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);
     }
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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." ));
     }
 
}

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
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<selectionkey> 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();
             }
         }
     }
 
}
</selectionkey>

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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);
     }
 
 
}

猜你喜欢

转载自blog.csdn.net/lldouble/article/details/80737618