Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9538194

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

File tree

Expand file treeCollapse file tree

6 files changed

+270
-9
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+270
-9
lines changed
Open diff view settings
Collapse file

‎JdkLearn/pom.xml‎

Copy file name to clipboardExpand all lines: JdkLearn/pom.xml
-1Lines 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依赖 -->
Collapse file

‎README.md‎

Copy file name to clipboardExpand all lines: README.md
+5-4Lines changed: 5 additions & 4 deletions
  • Display the source diff
  • Display the rich diff
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
Collapse file

‎Spring-Netty/learnnetty.iml‎

Copy file name to clipboardExpand all lines: Spring-Netty/learnnetty.iml
+1-1Lines 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>
Collapse file

‎Spring-Netty/pom.xml‎

Copy file name to clipboardExpand all lines: Spring-Netty/pom.xml
-2Lines 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>
Collapse file

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

Copy file name to clipboardExpand all lines: note/Netty/Netty底层源码解析-Netty服务端启动分析.md
-1Lines changed: 0 additions & 1 deletion
  • Display the source diff
  • Display the rich diff
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」创作共享协议,转载请在文章明显位置注明作者及出处。' />
Collapse file
+264Lines changed: 264 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.