Java NIO框架Netty教程(四) – ServerBootStrap启动流程源码分析

释放双眼,带上耳机,听听看~!

有一段事件没有更新文章了,各种原因都有吧。搬家的琐事,搬家后的安逸呵呵。不过,OneCoder明白,绝不能放松。对于Netty的学习,也该稍微深入一点了。

所以,这次 OneCoder花了几天时间,仔细梳理了一下Netty的源码,总结了一下ServerBootStrap的启动和任务处理流程,基本涵盖了Netty的关键架构。

 

OneCoder总结了一张流程图:

 

 

该图是 OneCoder通过阅读Netty源码,逐渐记录下来的。基本可以说明Netty服务的启动流程。这里在具体讲解一下。

 

首先说明,我们这次顺利的流程是基于NioSocketServer的。也就是基于Java NIO Selector的实现方式。在第六讲 《Java NIO框架Netty教程(六)-Java NIO Selector模式》中,我们已经知道使用Selector的几个关键点,所以这里我们也重点关注一下,这些点在Netty中是如何使用的。

 

很多看过Netty源码的人都说Netty源码写的很漂亮。可漂亮在哪呢?Netty通过一个ChannelFactory决定了你当前使用的协议类型 (Nio/oio等),比如,OneCoder这里使用的是NioServerSocket,那么需要声明的Factory即为 NioServerSocketChannelFactory,声明了这个Factory,就决定了你使用的Channel,pipeline以及 pipeline中,具体处理业务的sink的类型。这种使用方式十分简洁的,学习曲线很低,切换实现十分容易。

 

Netty采用的是基于事件的管道式架构设计,事件(Event)在管道(Pipeline)中流转,交由(通过pipelinesink)相应的处理器(Handler)。这些关键元素类型的匹配都是由开始声明的ChannelFactory决定的。

 

Netty框架内部的业务也遵循这个流程,Server端绑定端口的binder其实也是一个Handler,在构造完Binder后,便要声明一个 Pipeline管道,并赋给新建一个Channel。Netty在newChannel的过程中,相应调用了Java中的 ServerSocketChannel.open方法,打开一个channel。然后触发fireChannelOpen事件。这个事件的接受是可以复写的。Binder自身接收了这个事件。在事件的处理中,继续向下完成具体的端口的绑定。对应Selector中的 socketChannel.socket().bind()。然后触发fireChannelBound事件。默认情况下,该事件无人接受,继续向下开始构造Boss线程池。我们知道在Netty中Boss线程池是用来接受和分发请求的核心线程池。所以在channel绑定后,必然要启动Boss线城池,随时准备接受client发来的请求。在Boss构造函数中,第一次注册了selector感兴趣的事件类型,SelectionKey.OP_ACCEPT。至此,在第六讲中介绍的使用Selector的几个关键步骤都体现在Netty中了。在Boss中回启动一个死循环来查询是否有感兴趣的事件发生,对于第一次的客户端的注册事件,Boss会将Channel注册给worker保存。

 

这里补充一下,也是图中忽略的部分,就是关于worker线程池的初始化时机问题。worker池的构造,在最开始构造ChannelFactory的时候就已经准备好了。在NioServerSocketChannelFactory的构造函数里,会new一个NioWorkerPool。在 NioWorkerPool的基类AbstractNioWorkerPool的构造函数中,会调用OpenSelector方法,其中也打开了一个 selector,并且启动了worker线程池。


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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
1private
2           void
3          openSelector() {
4        
5
6        
7
8
9                  
10          try
11          {
12        
13
14        
15
16
17                      
18          selector = Selector.open();
19        
20
21        
22
23
24                  
25          }  
26          catch
27          (Throwable t) {
28        
29
30        
31
32
33                      
34          throw
35          new
36          ChannelException(
37          "Failed to create a selector."
38          , t);
39        
40
41        
42
43
44                  
45          }
46        
47
48        
49
50
51            
52        
53
54        
55
56
57                  
58          // Start the worker thread with the new Selector.
59        
60
61        
62
63
64                  
65          boolean
66          success =  
67          false
68          ;
69        
70
71        
72
73
74                  
75          try
76          {
77        
78
79        
80
81
82                      
83          DeadLockProofWorker.start(executor,  
84          new
85          ThreadRenamingRunnable(
86          this
87          ,  
88          "New I/O  worker #"
89           + id));
90        
91
92        
93
94
95                      
96          success =  
97          true
98          ;
99        
100
101        
102
103
104                  
105          }  
106          finally
107          {
108        
109
110        
111
112
113                      
114          if
115          (!success) {
116        
117
118        
119
120
121                          
122          // Release the Selector if the execution fails.
123        
124
125        
126
127
128                          
129          try
130          {
131        
132
133        
134
135
136                              
137          selector.close();
138        
139
140        
141
142
143                          
144          }  
145          catch
146          (Throwable t) {
147        
148
149        
150
151
152                              
153          logger.warn(
154          "Failed to close a selector."
155          , t);
156        
157
158        
159
160
161                          
162          }
163        
164
165        
166
167
168                          
169          selector =  
170          null
171          ;
172        
173
174        
175
176
177                          
178          // The method will return to the caller at this point.
179        
180
181        
182
183
184                      
185          }
186        
187
188        
189
190
191                  
192          }
193        
194
195        
196
197
198                  
199          assert
200          selector !=  
201          null
202          && selector.isOpen();
203        
204
205        
206
207
208              
209          }
210

至此,会分线程启动AbstractNioWorker中run逻辑。同样是循环处理任务队列。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1processRegisterTaskQueue();
2        
3
4        
5
6
7         processEventQueue();
8        
9
10        
11
12
13         processWriteTaskQueue();
14        
15
16        
17
18
19         processSelectedKeys(selector.selectedKeys());
20

 

这样,设计使事件的接收和处理模块完全解耦。

由此可见,如果你想从nio切换到oio,只需要构造不同的ChannelFacotry即可。果然简洁优雅。

给TA打赏
共{{data.count}}人
人已打赏
安全技术

用node.js从零开始去写一个简单的爬虫

2021-12-21 16:36:11

安全技术

从零搭建自己的SpringBoot后台框架(二十三)

2022-1-12 12:36:11

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索