Skip to content

Commit f5094df

Browse files
netty4.2
1 parent ca7621d commit f5094df

10 files changed

+130
-0
lines changed

SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
## reactive
1212

1313
* [响应式的锁](reactive_lock.md)
14+
* [Netty4.2模型变化简介](netty-4-2.md)
1415

1516
## Loom
1617

Loading

assets/image-20241006173540940.png

280 KB
Loading

assets/image-20241006174406263.png

200 KB
Loading

assets/image-20241006175009602.png

691 KB
Loading

assets/image-20241006175430762.png

524 KB
Loading

assets/image-20241006180724882.png

584 KB
Loading

assets/image-20241006181243887.png

535 KB
Loading

assets/image-20241006190421128.png

72.6 KB
Loading

netty-4-2.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Netty4.2模型变化简介
2+
3+
## 前言
4+
5+
Netty is *an asynchronous event-driven network application framework*
6+
for rapid development of maintainable high performance protocol servers & clients.这是Netty对于自己的简介,那么很明显它是专注于网络库的,所以它的模型*EventLoop+Channel,似乎可以处理大部分的网络IO情况,如果需要切换Reactor核心则需要使用一个新的EventLoop子类,似乎看起来没什么问题?
7+
8+
如果你对于Linux上的各种fd足够熟悉的话,你会发现这一套似乎不能套在诸如pipefd,eventfd上,虽然其底层用了Epoll作为Reactor实现,但是却被Netty的模型限制住了扩展性
9+
10+
## 模型切换
11+
12+
### 4.1现状
13+
14+
当前使用EventLoop直接管理对应的channel,将EventLoop和Channel的关系非常紧密的绑在一起,如果需要切换EventLoop实现也需要同步切换Channel,似乎看起来没问题?
15+
16+
![img](assets/1659e32ec5a6e1catplv-t2oaga2asx-jj-mark3024000q75.png)
17+
18+
如果我们仔细看一下NIOEventLoop和EpollEventLoop的实现,会发现它们存在大量重复的代码,因为大多数EventLoop实现都共享运行非 IO 任务的相同逻辑。
19+
20+
而且由于netty io_uring的引入,对于一个File很难把这套Channel的抽象套上去(流和块的不同)
21+
22+
### 4.2更改
23+
24+
> 本节内容来自于
25+
>
26+
> https://github.com/netty/netty/pull/13991
27+
>
28+
> https://github.com/netty/netty/pull/14024
29+
30+
#### EventLoop更新
31+
32+
目前不再使用定制化的EvenLoop,而是将对应的IO逻辑抽离为IoHandler,交由具体的Reactor实现
33+
34+
旧 API:
35+
36+
```java
37+
new EpollEventLoop()
38+
```
39+
40+
新 API:
41+
42+
```java
43+
new MultiThreadIoHandleEventLoopGroup(EpollHandler.newFactory();
44+
```
45+
46+
这样就解决了不同EventLoop之间的非IO逻辑无法复用的问题
47+
48+
#### ChannelEventLoop解耦
49+
50+
我们首先开看下IoHandler的接口声明,核心是两个函数run和register
51+
52+
- run —— 抽象的IO Poll动作,EventLoop会调用这个函数进行原来的Selector::select,即请求Poller实现来进行多路复用
53+
- register —— 用来给Poller注册感兴趣事件的回调,调用此方法只是将IoHandle(注意看这里是handle而非handler)与这个IoHandler关联起来,具体注册可读/可写事件是在返回的IoRegistration上实现的
54+
55+
![image-20241006173540940](assets/image-20241006173540940.png)
56+
57+
那么最终EventLoop就可以不感知Channel这种结构了一切交给IoHandler进行实现(跟boost.asio的iocontext有点像)
58+
59+
![image-20241006174406263](assets/image-20241006174406263.png)
60+
61+
而对于IoHandler感知的则是fd->IoHandle的映射,然后拿到IoHandle只是简单执行下IoHandle回调而已,旧有的Channel-Pipeline体系仍旧可以使用,只不过触发者从原来的EventLoop直接触发,变成了EventLoop->IoHandler->IoHandle->Channel而已,做了一个抽象层出来这样就不必跟Channel模型强耦合了
62+
63+
![Epoll IoHandler实现](assets/image-20241006175009602.png)
64+
65+
#### 旧有Channel-Pipeline机制适配
66+
67+
> 这里展示的实现是NioChannel这一套,即Netty使用的JDK的默认实现
68+
69+
在旧有的代码里面其实注册,读写Socket触发Pipeline都是Netty中的Unsafe类完成的,其与一个Channel进行了关联,那么在新模型下面让其实现一个IOHandle再合适不过
70+
71+
![image-20241006175430762](assets/image-20241006175430762.png)
72+
73+
此时有感兴趣事件被poll到了就会通知到IoHandle(即Unsafe),其内部再进行分发,根据不同事件触发旧有的不同逻辑
74+
75+
![image-20241006180724882](assets/image-20241006180724882.png)
76+
77+
那么就剩下最后一个问题了,IoHandle注册到IoHandler之后得到的IoRegistration在哪里使用的呢?
78+
79+
在我们注册之后的的异步回调里面 如果开启了自动读(默认开启)则会触发`BeginRead`此时就会注册对应的事件
80+
81+
![image-20241006181243887](assets/image-20241006181243887.png)
82+
83+
## 模型利用
84+
85+
4.2模型更新之后其实我们需要实现对应的IoHandle即可,再也不用强行把某些东西套在Channel上了
86+
87+
以netty-transport-native-epoll为例子,我们来介绍如何将一个EventFd挂在到当前已经存在的EventLoop
88+
89+
首先你肯定有这样一个EventLoopGroup
90+
91+
```java
92+
MultiThreadIoEventLoopGroup masterGroup = new MultiThreadIoEventLoopGroup(1, EpollIoHandler.newFactory());
93+
94+
```
95+
96+
那么结合我们刚才分析的你这里需要一个IoHandle来注册到Iohandler,由于不同IoHandler的底层不一样所以需要不同IoHandle实现,这里我们需要实现的是EpollIoHandle,相比于IoHandle只是多了一个fd,传入一个Epoll能poll的fd就好了,这里我们是EventFd满足这个条件
97+
98+
```java
99+
@Override
100+
public FileDescriptor fd() {
101+
return eventFd;
102+
}
103+
@Override
104+
public void handle(IoRegistration registration, IoEvent ioEvent) {
105+
//。。。。
106+
}
107+
```
108+
109+
那么我们再来仔细看下回调实现,在EpollIoHandler发现当前Fd有事件被触发来之后用当前被触发的事件进行回调
110+
111+
```java
112+
@Override
113+
public void handle(IoRegistration registration, IoEvent ioEvent) {
114+
EpollIoEvent epollIoEvent = (EpollIoEvent) ioEvent;
115+
System.out.println(epollIoEvent.ops());
116+
if (epollIoEvent.ops().contains(EpollIoOps.EPOLLIN)) {
117+
long readEvent = readEvent();
118+
System.out.println("read event: " + readEvent);
119+
}
120+
}
121+
```
122+
123+
让我们仔细看一下IoEvent,会发现这个是一个标记性接口,所以需要根据当前的实现进行转为对应的Event从而获取到具体事件
124+
125+
![image-20241006190421128](assets/image-20241006190421128.png)
126+
127+
详细源码可见:
128+
129+
<script src="https://gist.github.com/dreamlike-ocean/ae6031e198011a80ad8771d9d6fb6c96.js"></script>

0 commit comments

Comments
 (0)