簡介
我們知道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的協議,可以說非常的強大。
本文已收錄於 http://www.flydean.com/39-netty-selecto…r-channelfactory/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!