Skip to content

Commit 9538194

Browse files
committed
[A] 新增Netty底层原理分析文章
1 parent e8dd657 commit 9538194

File tree

6 files changed

+270
-9
lines changed

6 files changed

+270
-9
lines changed

JdkLearn/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
<dependency>
3131
<groupId>io.netty</groupId>
3232
<artifactId>netty-all</artifactId>
33-
<version>4.1.6.Final</version>
3433
</dependency>
3534

3635
<!-- redis依赖 -->

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<img src="https://img.shields.io/badge/Spring--Security--OAuth2-2.3.5.RELEASE-red" alt="Spring-Security-OAuth2">
1818
</a>
1919
<a href="https://netty.io/">
20-
<img src="https://img.shields.io/badge/Netty-4.1.60.Final-blue" alt="Netty">
20+
<img src="https://img.shields.io/badge/Netty-4.1.43.Final-blue" alt="Netty">
2121
</a>
2222
<a href="https://rocketmq.apache.org/">
2323
<img src="https://img.shields.io/badge/RocketMQ-4.9.0-green" alt="Netty">
@@ -121,16 +121,17 @@ SpringCloud源码
121121
- Dubbo底层源码学习(七)—— Dubbo的服务消费
122122

123123
- Netty底层源码解析
124-
- Netty版本:4.1.60.Final
124+
- Netty版本:4.1.43.Final
125125
- [二进制运算以及源码、反码以及补码学习](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/%E4%BA%8C%E8%BF%9B%E5%88%B6.md)
126126
- [Netty源码包结构](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E6%BA%90%E7%A0%81%E5%8C%85%E7%BB%93%E6%9E%84.md)
127-
- [Netty中的EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md)
127+
- [Netty底层源码解析-EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md)
128128
- [Netty底层源码解析-初始Netty及其架构](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-%E5%88%9D%E5%A7%8BNetty%E5%8F%8A%E5%85%B6%E6%9E%B6%E6%9E%84.md)
129129
- [Netty底层源码解析-Netty服务端启动分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-Netty%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%90%AF%E5%8A%A8%E5%88%86%E6%9E%90.md)
130130
- [Netty底层源码解析-NioEventLoop原理分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-NioEventLoop%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md)
131-
- Netty底层源码解析-Channel分析
132131
- [Netty底层源码解析-ChannelPipeline分析(上)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8A%EF%BC%89.md)
133132
- [Netty底层源码解析-ChannelPipeline分析(下)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8B%EF%BC%89.md)
133+
- Netty底层源码解析-NioServerSocketChannel接受数据原理分析
134+
- Netty底层源码解析-NioSocketChannel接受、发送数据原理分析
134135
- Netty底层源码解析-FastThreadLocal原理分析
135136
- Netty底层源码解析-内存分配原理分析
136137
- Netty底层源码解析-RocketMQ底层使用到的Netty

Spring-Netty/learnnetty.iml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
<orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.2.1.RELEASE" level="project" />
9393
<orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.2.1.RELEASE" level="project" />
9494
<orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.6.3" level="project" />
95-
<orderEntry type="library" name="Maven: io.netty:netty-all:4.1.6.Final" level="project" />
95+
<orderEntry type="library" name="Maven: io.netty:netty-all:4.1.43.Final" level="project" />
9696
<orderEntry type="library" name="Maven: com.alibaba:fastjson:1.2.76" level="project" />
9797
</component>
9898
</module>

Spring-Netty/pom.xml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
<properties>
1818
<java.version>1.8</java.version>
19-
<netty-all.version>4.1.6.Final</netty-all.version>
2019
</properties>
2120

2221
<dependencies>
@@ -35,7 +34,6 @@
3534
<dependency>
3635
<groupId>io.netty</groupId>
3736
<artifactId>netty-all</artifactId>
38-
<version>${netty-all.version}</version>
3937
</dependency>
4038

4139
<dependency>

note/Netty/Netty底层源码解析-Netty服务端启动分析.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,3 @@ Netty服务端核心启动流程主要是为了创建NioServerSocketChannel,
496496
- AbstractChannel.AbstractUnsafe#doBind() 注册端口号
497497

498498

499-
<Boxx type='tip' content='本站使用「CC BY 4.0」创作共享协议,转载请在文章明显位置注明作者及出处。' />
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
## NioServerSocketChannel读取数据原理分析
2+
3+
NioServerSocketChannel是AbstractNioMessageChannel的子类,而NioSocketChannel是AbstractNioByteChannel的子类,并且他们都有一个公共的父类:AbstractChannel。
4+
5+
在Netty中Channel是用来定义对网络IO的读写操作的相关接口,与NIO的Channel接口类似。Channel的功能主要有网络IO的读写、客户端发起的连接、主动关闭连接、关闭链路、获取通信双方的网络地址等。一些公共的基础方法都在这个AbstractChannel抽象类中实现,但对于一些特定的功能则需要不同的实现类去实现,这样最大限度地实现了功能和接口的重用。
6+
7+
另外,AbstractChannel的构造方法中对Unsafe类和ChannelPipeline类进行了初始化。
8+
9+
## 1. NioServerSocketChannel源码分析
10+
11+
NioServerSocketChannel是AbstractNioMessageChannel的子类,由于它由服务端使用,并且只负责监听Socket的接入,不关心IO的读写,所以与NioSocketChannel相比要简单得多。
12+
13+
NioServerSocketChannel封装了NIO中的ServerSocketChannel,并通过newSocket()方法打开了ServerSocketChannel
14+
15+
NioServerSocketChannel.class
16+
17+
```java
18+
private static ServerSocketChannel newSocket(SelectorProvider provider) {
19+
try {
20+
return provider.openServerSocketChannel();
21+
} catch (IOException e) {
22+
throw new ChannelException(
23+
"Failed to open a server socket.", e);
24+
}
25+
}
26+
```
27+
28+
对于NioServerSocketChannel注册至selector上的操作,是在AbstractNioChannel中实现的,源码如下:
29+
30+
```java
31+
@Override
32+
protected void doRegister() throws Exception {
33+
boolean selected = false;
34+
for (;;) {
35+
try {
36+
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
37+
return;
38+
} catch (CancelledKeyException e) {
39+
if (!selected) {
40+
eventLoop().selectNow();
41+
selected = true;
42+
} else {
43+
throw e;
44+
}
45+
}
46+
}
47+
}
48+
```
49+
50+
在ServerChannel的开启,selector上的注册等前期工作完成后,NioServerSocketChannel的开始监听新连接的加入,源码如下:
51+
52+
```java
53+
@Override
54+
protected int doReadMessages(List<Object> buf) throws Exception {
55+
// 拿到jdk底层channel
56+
SocketChannel ch = SocketUtils.accept(javaChannel());
57+
58+
try {
59+
if (ch != null) {
60+
// new出一个NioSocketChannel,将jdk SocketChannel封装成NioSocketChannel,并且这里给NioSocketChannel注册了一个SelectionKey.OP_READ事件
61+
buf.add(new NioSocketChannel(this, ch)); // 往buf里写入NioSocketChannel
62+
return 1;
63+
}
64+
} catch (Throwable t) {
65+
logger.warn("Failed to create a new channel from an accepted socket.", t);
66+
67+
try {
68+
ch.close();
69+
} catch (Throwable t2) {
70+
logger.warn("Failed to close a socket.", t2);
71+
}
72+
}
73+
74+
return 0;
75+
}
76+
```
77+
78+
上面的源码展示了Netty最终拿到新连接请求后,将jdk底层的SocketChannel封装NioSocketChannel的过程,那么selector是如何获取到accept事件后,调用到这个doReadMessages方法的呢?
79+
80+
为了分析原理的延续,故事还要回到bossGroup的NioEventLoop里,当bossGroup启动,NioServerSocketChannel实例新建并注册到selector之后,Netty的bossGroup就会运行一个NioEventLoop,它的核心工作就是作为一个selector一直去监听客户端发出的accept、connect、read、write等事件。具体逻辑查看NioEventLoop#run()方法,详细的原理请回看之前的NioEventLoop的原理分析,此处只分析NioEventLoop#run()获取到链接事件到调用NioServerSocketChannel#doReadMessages()的链路。
81+
82+
1. NioEventLoop#run()一直轮训,监听这客户端发出的事件,在轮训过程中如果有任务产生,则会优先执行这些任务,调用非阻塞的selectNow(),否则调用select(deadlineNanos)阻塞指定时间去监听客户端事件。
83+
2. 调用NioEventLoop#processSelectedKeys(),Netty默认用的是优化过后的selectedKey,所以调用的是NioEventLoop#processSelectedKeysOptimized()方法。
84+
3. 在processSelectedKeysOptimized方法里会遍历selectedKeys,去拿selectedKeys中的SelectionKey,这个key就是从网络中获取到的感兴趣事件。
85+
4. 先通过SelectionKey获取attachment,及对应的事件channel。由于这里是获取的是accept事件,所以SelectionKey#attachment()获取到的是NioServerSocketChannel对象。
86+
5. 在NioEventLoop#processSelectedKey()方法中,首先拿到NioServerSocketChannel父类AbstractNioMessageChannel中的NioMessageUnsafe对象,接着根据readyOps进行判断,这里当然就是SelectionKey.OP_ACCEPT事件。
87+
6. 调用NioMessageUnsafe#read()方法,最终该方法调用了NioServerSocketChannel#doReadMessages(),完了之后会新建一个对SelectionKey.OP_READ事件感兴趣的NioSocketChannel对象,并存放在readBuf的一个集合中。
88+
7. 接着调用ChannelPipeline#fireChannelRead()方法,目的在于最终调用ServerBootstrapAcceptor#channelRead()方法,调用childGroup#register(child),把新建的NioSocketChannel对象注册到selector上。
89+
90+
这样,NioServerSocketChannel监听accept事件,接收到客户端连接后,封装客户端的“连接”到NioSocketChannel对象,并注册到selector上,后面的网络IO的读写操作都由这个NioSocketChannel对象来负责处理。
91+
92+
上述核心的6步源码如下:
93+
94+
NioEventLoop.class
95+
```java
96+
@Override
97+
protected void run() {
98+
for (;;) {
99+
try {
100+
try {
101+
switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) {
102+
// ... 省略
103+
case SelectStrategy.SELECT:
104+
select(wakenUp.getAndSet(false));
105+
// ... 省略
106+
if (wakenUp.get()) {
107+
selector.wakeup();
108+
}
109+
// fall through
110+
default:
111+
}
112+
} catch (IOException e) {
113+
rebuildSelector0();
114+
handleLoopException(e);
115+
continue;
116+
}
117+
// ... 省略
118+
119+
// 步骤1
120+
processSelectedKeys();
121+
runAllTasks();
122+
123+
// ... 省略
124+
} catch (Throwable t) {
125+
handleLoopException(t);
126+
// ... 省略
127+
}
128+
}
129+
}
130+
```
131+
132+
NioEventLoop.class
133+
```java
134+
// 步骤2
135+
private void processSelectedKeysOptimized() {
136+
for (int i = 0; i < selectedKeys.size; ++i) {
137+
// 步骤3
138+
final SelectionKey k = selectedKeys.keys[i];
139+
selectedKeys.keys[i] = null;
140+
141+
// 步骤4
142+
final Object a = k.attachment();
143+
144+
if (a instanceof AbstractNioChannel) {
145+
// 步骤5
146+
processSelectedKey(k, (AbstractNioChannel) a);
147+
} else {
148+
@SuppressWarnings("unchecked")
149+
NioTask<SelectableChannel> task = (NioTask<SelectableChannel>) a;
150+
processSelectedKey(k, task);
151+
}
152+
153+
if (needsToSelectAgain) {
154+
selectedKeys.reset(i + 1);
155+
156+
selectAgain();
157+
i = -1;
158+
}
159+
}
160+
}
161+
```
162+
163+
NioEventLoop.class
164+
```java
165+
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
166+
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
167+
if (!k.isValid()) {
168+
final EventLoop eventLoop;
169+
try {
170+
eventLoop = ch.eventLoop();
171+
} catch (Throwable ignored) {
172+
return;
173+
}
174+
if (eventLoop != this || eventLoop == null) {
175+
return;
176+
}
177+
unsafe.close(unsafe.voidPromise());
178+
return;
179+
}
180+
181+
try {
182+
int readyOps = k.readyOps();
183+
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
184+
int ops = k.interestOps();
185+
ops &= ~SelectionKey.OP_CONNECT;
186+
k.interestOps(ops);
187+
188+
unsafe.finishConnect();
189+
}
190+
191+
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
192+
ch.unsafe().forceFlush();
193+
}
194+
195+
// 步骤5
196+
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
197+
unsafe.read();
198+
}
199+
} catch (CancelledKeyException ignored) {
200+
unsafe.close(unsafe.voidPromise());
201+
}
202+
}
203+
```
204+
205+
NioServerSocketChannel.class
206+
207+
```java
208+
@Override
209+
protected int doReadMessages(List<Object> buf) throws Exception {
210+
// 拿到jdk 的SocketChannel,代表着和客户端的一个连接socket
211+
SocketChannel ch = SocketUtils.accept(javaChannel());
212+
213+
try {
214+
if (ch != null) {
215+
// 步骤6
216+
// 封装一个NioSocketChannel对象,并且设置感兴趣事件为:SelectionKey.OP_READ
217+
buf.add(new NioSocketChannel(this, ch));
218+
return 1;
219+
}
220+
} catch (Throwable t) {
221+
logger.warn("Failed to create a new channel from an accepted socket.", t);
222+
223+
try {
224+
ch.close();
225+
} catch (Throwable t2) {
226+
logger.warn("Failed to close a socket.", t2);
227+
}
228+
}
229+
230+
return 0;
231+
}
232+
```
233+
234+
ServerBootstrapAcceptor.class
235+
236+
```java
237+
public void channelRead(ChannelHandlerContext ctx, Object msg) {
238+
final Channel child = (Channel) msg;
239+
240+
child.pipeline().addLast(childHandler);
241+
242+
setChannelOptions(child, childOptions, logger);
243+
setAttributes(child, childAttrs);
244+
245+
try {
246+
// 步骤7
247+
// 在workerGroup的NioEventLoop上的selector注册了NioSocketChannel
248+
childGroup.register(child).addListener(new ChannelFutureListener() {
249+
@Override
250+
public void operationComplete(ChannelFuture future) throws Exception {
251+
if (!future.isSuccess()) {
252+
forceClose(child, future.cause());
253+
}
254+
}
255+
});
256+
} catch (Throwable t) {
257+
forceClose(child, t);
258+
}
259+
}
260+
```
261+
262+
以上就是Netty中有关NioServerSocketChannel读取数据的底层原理分析。
263+
264+
下一篇分析NioSocketChannel的发送、读取数据底层原理。

0 commit comments

Comments
 (0)