netty系列之:好馬配好鞍,為channel選擇配套的selector
簡介
我們知道netty的基礎是channel和在channel之上的selector,當然作為一個nio框架,channel和selector不僅僅是netty的基礎,也是所有nio實現的基礎。
同樣的,我們知道netty很多種不同的協議,這些協議都是在channel上進行通訊的,那麼對於不同的協議來說,使用的channel和selector會有所不同嗎?
帶着這個疑問,我們一起來深入探究一下吧。
netty服務的基本構建方式
netty可以分為客戶端和服務器端,實際上客戶端和服務器端的構造方式差別不大,這裡為了簡單起見,以netty中服務器端的構建為例子進行研究。
回顧一下我們最開始搭建的netty服務器,其對應的代碼如下:
//建立兩個EventloopGroup用來處理連接和消息
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new FirstServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 綁定端口並開始接收連接
ChannelFuture f = b.bind(port).sync();
我們要注意的是兩個地方,一個是ServerBootstrap的group方法,一個是它的channel方法。
EventLoopGroup
group有兩種實現方式,可以帶一個參數,也可以帶兩個參數。參數都是EventLoopGroup,EventLoopGroup主要用來註冊channel, 供後續的Selector進行選擇。
如果使用一個參數的形式,則一個EventLoopGroup同時處理acceptor和client的事件,如果使用兩個參數,則會將兩者分開。
當然,這都不是今天要講的重點,今天要講的是EventLoopGroup的構建在不同的協議中有什麼不同。
EventLoopGroup本身是一個接口,他有很多種實現,但是本質上還是兩種EventLoop:SingleThreadEventLoop和MultithreadEventLoopGroup.
也就是用單線程進行EventLoop處理和多線程進行EventLoop處理。
比如上面我們常用的NioEventLoopGroup,就是一個單線程的EventLoop。
NioEventLoopGroup通常我們使用的是無參的構造函數,實際上NioEventLoopGroup可以傳入ThreadFactory,thread的個數,SelectorProvider和SelectStrategyFactory.
netty只提供了一個SelectStrategyFactory的實現:DefaultSelectStrategyFactory。
而對應SelectorProvider來說,默認的實現是SelectorProvider.provider(), 我們看下這個方法的具體實現:
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
可以看到默認情況下,SelectorProvider有三種創建方式。
第一種就是從系統屬性中查找:java.nio.channels.spi.SelectorProvider:
String cn = System.getProperty("java.nio.channels.spi.SelectorProvider");
Class<?> c = Class.forName(cn, true,
ClassLoader.getSystemClassLoader());
provider = (SelectorProvider)c.newInstance();
如果有定義,則創建一個實例返回。
如果沒有的話,則會從”META-INF/services/”中加載service Loader :
private static boolean loadProviderAsService() {
ServiceLoader<SelectorProvider> sl =
ServiceLoader.load(SelectorProvider.class,
ClassLoader.getSystemClassLoader());
Iterator<SelectorProvider> i = sl.iterator();
如果servie也沒有找到的話,則會使用最後默認的sun.nio.ch.DefaultSelectorProvider.
channel
默認情況下,我們使用的是NioServerSocketChannel。他實際是從上面提到的默認的SelectorProvider來創建的。
private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
return DEFAULT_SELECTOR_PROVIDER.openServerSocketChannel();
所以使用的channel需要跟selector相匹配。
我們可以直接使用channel,也可以使用ChannelFactory,通過這些Factory來生成channel。
如果要使用ChannelFactory,則可以調用ServerBootstrap的channelFactory方法。
多種構建方式
上面提到了最基本的netty server構建方式。對應的是socket協議。
如果是要進行UDP連接,對應的channel應該換成NioDatagramChannel,如下:
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new UDPServerHandler());
b.bind(PORT).sync().channel().closeFuture().await();
EventLoopGroup可以保持不變。
因為netty底層是基於Socket進行通訊的,socket底層又是基於TCP或者UDP協議,所以在netty中實現的http或者http2或者SOCKS協議都是在socket連接基礎上進行的。
所以對http或者http2來說,channel還是NioServerSocketChannel。
可以看到只有UDP協議有所不同。同樣的基於UDP協議之上的UDT協議也是不同的,其使用如下:
final NioEventLoopGroup acceptGroup = new NioEventLoopGroup(1, acceptFactory, NioUdtProvider.BYTE_PROVIDER);
final NioEventLoopGroup connectGroup = new NioEventLoopGroup(1, connectFactory, NioUdtProvider.BYTE_PROVIDER);
final ServerBootstrap boot = new ServerBootstrap();
boot.group(acceptGroup, connectGroup)
.channelFactory(NioUdtProvider.BYTE_ACCEPTOR)
.option(ChannelOption.SO_BACKLOG, 10)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<UdtChannel>() {
@Override
public void initChannel(final UdtChannel ch) {
ch.pipeline().addLast(
new LoggingHandler(LogLevel.INFO),
new UDTEchoServerHandler());
}
});
UDT使用的是NioUdtProvider中提供的BYTE_PROVIDER和BYTE_ACCEPTOR分別作為selector和channelFactory。
其他的channel
除了NioSocketChannel之外,還有EpollChannel、KQueueChannel、SctpChannel,這些channel都是針對不同協議來使用的。我們會在後續的文章中詳細進行介紹。
總結
channel和selector是netty的基礎,在這基礎之上,netty可以擴展適配所有基於tcp和udp的協議,可以說非常的強大。
本文已收錄於 //www.flydean.com/39-netty-selecto…r-channelfactory/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!