從一段代碼開始說起
- Java NIO實現
public class demo{
public void socket() {
// 使用 socket 提供的 ServerSocketChannel建立一個 通信通道 channel
ServerSocketChannel server = ServerSocketChannel.open();
// 進行 IP 端口綁定,默認本地IP
server.bind(new InetSocketAddress("port"));
// 手動設置非阻塞
server.configurationBlocking(false);
// 打開選擇器 可以開始接收請求了
Selector selector = Selector.open();
// ON_ACCEPT :設置爲可接收請求狀態
server.register(this.selector, SelectionKey.OP_ACCEPT);
// 使用輪詢的方法 處理請求
while (true) {
/* 多路複用體現
* 多路:select();方法可以接收多個TCP連接請求
* 複用:複用一個主線程去處理多個請求
*/
// 接收所有請求 select()方法會阻塞,直到拿到一個新的key
selector.select();
// 拿到請求
Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 處理請求.....
while (iterator.hasNext()) {
// 每一個key代表一種狀態
SelectionKey key = iterator.next();
// 每一個key都有一個通道去處理
key.channel();
// to do
}
}
}
}
Netty的做法
- 通過複習java NIO的實現,對NIO有了更深一層的記憶
- Netty是基於Java的一個高性能的RPC框架
- Netty提供瞭如 零拷貝、內存池、無鎖串行化設計理念、Reactor線程模型 等高性能操作
- 迴歸正題:Netty既然是基於Java實現的,那麼利用Netty編程和使用Java實現NIO必然有異曲同工之處
Netty代碼示例
- 說實話,屬實不知道怎麼去搞懂它
- 不過記住一件事,這裏做的事情和java NIO做的事情是一樣的,只不過性能更優
- 其實從 serverBootstrap.bind() 方法也可以看出,是綁定端口的操作
從Channel()開始學起
- Java NIO中有 ServerSocketChannel.open() 建立一個channel對象
- 那麼Netty中必然也有Channel對象,從代碼中也可以窺見一二
- Netty的 Channel.java
- 部分 API
- 連接到socket網絡或者組件進行IO操作、讀、寫、綁定或連接
- A channel providers a user : 用來提供Channel對象的
- 通過其很多的實現類可以瞭解到,Netty.Channel針對不同的連接協議提供了支持
Channel的創建
- 從代碼入手,本例 完成了 對 NioServerSocketChannel的初始化
ServerBootStrap 提供了 channel() 方法 其本身是由 Netty 提供用來完成Netty客戶端/服務端初始化操作的
channel方法 返回了 一個工廠對象
- 通過跟蹤代碼,channel初始化操作是由 serverBootstrap.bind(port) 完成
- 交給 channelFactory 工廠來創建
- 最終通過反射進行 Channel() 對象的創建
- 隨後調用 NioServerSocketChannel 的構造方法 NioServerSocketChannel 在 調用Channel()方法階段設置
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
- 接下來就是通過父類的層層調用—直到 AbstractChannel 的構造器
- 至此,以 NioServerSocketChannel 爲例的 Channel初始化就完成了
總結
- NioServerSocketChannel 構造方法中,使用 newSocket 方法新建 一個 ServerSocketChannel
- 需要初始化的屬性在 AbstractChannel 中完成,並 爲每一個channel設置了一個唯一的id
- 在執行AcstractChannel構造器的時候同時新建了一個unsafe對象並完成了pipeline對象的實例化
- 在代碼編程方面
- 我們可以理解到,ServerBootstrap 完成了初始化預設參數
- 在調用 bind 方法的時候完成註冊和端口綁定
Unsafe的初始化
- 在 Channel 的創建過程中,在父類 AbstractChannel 的構造方法中調用 newUnsafe() 方法來獲取一個unsafe實例
- 瞭解一下 Unsafe 接口所提供的方法
interface Unsafe {
RecvByteBufAllocator.Handle recvBufAllocHandle();
SocketAddress localAddress();
SocketAddress remoteAddress();
void register(EventLoop eventLoop, ChannelPromise promise);
void bind(SocketAddress localAddress, ChannelPromise promise);
void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
void disconnect(ChannelPromise promise);
void close(ChannelPromise promise);
void closeForcibly();
void deregister(ChannelPromise promise);
void beginRead();
void write(Object msg, ChannelPromise promise);
void flush();
ChannelPromise voidPromise();
ChannelOutboundBuffer outboundBuffer();
}
- Netty提供的Unsafe類對Java底層Socket的操作進行了封裝,是Netty和Java底層**”溝通“**的重要橋樑
- Unsafe類以內部類的方式實現,同時避免了上層業務的調用
Pipeline初始化
- 在ChannelPipline的註釋中有這樣一句話
- 在上文Channel初始化過程中,AbstractChannel構造器中完成了對Pipline的初始化
- 傳入 Channel 對象作爲初始化參數,進行綁定
- 對Pipline暫時先了解到這裏
關於EventLoop
-
最開始的時候我們創建了兩個NioEventLoopGroup對象
- 通過其構造方法可以發現其構造器中可傳入線程數量、線程工廠對象、線程池對象進行初始化
- 通過其構造方法可以發現其構造器中可傳入線程數量、線程工廠對象、線程池對象進行初始化
-
通過跟蹤代碼瞭解其構造器參數的意義
- 首先Neyyt通過io.netty.eventLoopThreads從系統屬性中獲取值
- 默認值爲處理器核心數*2
-
最終會調用到 MultithreadEventExecutorGroup 的構造方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args) {
......
children = new EventExecutor[nThreads];
......
chooser = chooserFactory.newChooser(children);
......
}
-
這裏的判斷意思是:如果nThreads是2的冪,則使用PowerOfTwoEventExecutorChooser否則GenericEventExecutorChooser
-
兩種 Chooser 都重寫了 next() 方法
-
next()方法中的運算邏輯是數組索引循環位移
-
當索引移動到最後要給位置,再次調用next()方法將會使索引位置重新指向0
- 運算邏輯:每次索引自增後和數組長度取模 idx.getAndIncrement() % executors.length
- Netty優化算法:idx.getAndIncrement() & executors.length-1
- 注:在計算機底層&比%運算效率更高
總結
- EventLoop其實就是創建了一個大小爲nThreads類型爲EventExecutor的 childern 數組,構成了一個線程池
- 當我們實例化NioEventLoopGroup的時候,如果指定線程池大小,則 nThreads 就是指定的值,反之爲處理器核心數*2
Channel註冊到Seclector
- 當Channel在 initAndRegister() 方法中註冊的時候還會將初始化完成的Channel註冊到NioEventloop的Selector中
// initAndRegister()方法
ChannelFuture regFuture = config().group().register(channel);
- 其調用關係如下
// MultithreadEventLoopGroup.java
public ChannelFuture register(Channel channel) {
return next().register(channel);
}
// SingleThreadEventLoop.java
public ChannelFuture register(Channel channel) {
return register(new DefaultChannelPromise(channel, this));
}
// AbstractChannel.java
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
AbstractChannel.this.eventLoop = eventLoop;
register0(promise);
}
private void register0(ChannelPromise promise) {
doRegister();
}
- 到這裏,實現了SocketChannel註冊到EventLoop對象的selector上
- 註冊流程總結
- 本質上所做的事情就是建channel與對應的EventLoop關聯。關聯完成,把Channel註冊到EcentLoop對象中的selector中
如何添加Handler
- 在開始時有這樣的代碼,這裏體現了Netty的Handler機制
.group().channel().childHandler
- 從官方偷來一段註釋瞧瞧
- 處理IO事件或攔截IO操作,然後交給其ChannelPipline去處理
Handles an I/O event or intercepts an I/O operation, and forwards it to its next handler in its
{@link ChannelPipeline}.
- 簡單理解就是很強大還支持擴展。針對不同場景使用不同的Handler,先了解到這裏
通過這次代碼分析流程,有助於後續學習Netty