本章续上一章节没讲完的IO模型。
3.2.2. Netty中的组件channel
io.netty.channel.Channel时Netty对网络的抽象,它组合了一组功能,包括不限于网络的读、写、客户端发起连接,主动关闭连接,关闭链路,获取通信双方的地址等,还包括获取该channel的eventLoop,获取缓冲区分配类BytebufferAllocator和pipeline等。
jdk自带了channel,定义如下
package java.nio.channels;
import java.io.IOException;
import java.io.Closeable;
public interface Channel extends Closeable {
public boolean isOpen();
public void close() throws IOException;
}
netty自实现了channel,
package io.netty.channel;
import io.netty.buffer.ByteBufAllocator;
import io.netty.util.AttributeMap;
import java.net.SocketAddress;
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
ChannelId id();
EventLoop eventLoop();
Channel parent();
ChannelConfig config();
boolean isOpen();
boolean isRegistered();
boolean isActive();
ChannelMetadata metadata();
SocketAddress localAddress();
SocketAddress remoteAddress();
ChannelFuture closeFuture();
boolean isWritable();
long bytesBeforeUnwritable();
long bytesBeforeWritable();
Unsafe unsafe();
ChannelPipeline pipeline();
ByteBufAllocator alloc();
Channel read();
Channel flush();
}
为什么不适用JDK提供的channel,Netty自实现了channel,主要原因有:
- jdk的serverSocket,socketChannel是SPI类接口,主要职责是网络IO操作,扩展它与重新开发一个Channel工作量差不多;
- Netty的channel需要配合自身的框架特性,自定义channel功能上更加灵活。
channel中常用的方法:
- channel.read(),从当前channel读取数据到第一个inbound缓冲区中,如果数据被成功读取,会触发一个channelHandler.channelRead(ChannelHanlderContext context, Object)事件。读取操作完后,紧接着会触发ChannelHandler.channelReadComplete(ChannelHandlerContext context)事件,这样业务的channelhandler可判断是否还需要读取数据。
- ChannelFuture write(Object msg),将msg通过pipeline写入channel中。注意,write操作只是将数据存入到消息发送的环形数组中,并没有真正发送,调用flush操作后才会被发送。
- ChannelFuture write(Object msg, ChannelPromise promise),与write功能相同,promise负责设置写入操作的结果。
- ChannelFuture writeAndFlush(Object msg, ChannelPromise promise),相当于write和flush操作的结合ChannelFuture writeAndFlush(Object msg)
- ChannelFuture close( ChannelPromise promise),主动关闭当前连接,promise处理操作结果并负责通知
- ChannelFuture disconnect( ChannelPromise promise),请求断开与远程通信端的连接,promise获取操作结果的通知消息
- ChannelFuture connect(SocketAddress address),与远程地址建立连接,如果请求连接被拒绝,操作结果为ConnectException。这个操作会触发channelHandler.connect(ChannelHandlerContext context, SocketAddress address, ChannelPromise promise)事件。
- ChannelFuture bind(SocketAddress localAddress),绑定指定的本地地址,会触发事件
NioServerSocketChannel和NioSocketChannel,UnSafe类是channel的辅助接口,所以的IO读写都是同UnSafe类完成的。
ChannelPipeline和ChannelHandler
ChannelPipeline和ChannelHandler的关系类似Servlet和filter的关系,channelHandler对channel进行过滤,而ChannelPipeline提供环境,持有channelHandler事件拦截器的链表。可以方便的增加和删除channelHandler来实现对channel中数据流的处理。
public interface ChannelPipeline extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Map.Entry<String, ChannelHandler>> {
ChannelPipeline addFirst(String var1, ChannelHandler var2);
ChannelPipeline addFirst(EventExecutorGroup var1, String var2, ChannelHandler var3);
...
ChannelPipeline fireChannelRead(Object var1);
ChannelPipeline fireChannelReadComplete();
ChannelPipeline fireChannelWritabilityChanged();
ChannelPipeline flush();
}
public interface ChannelHandler {
void handlerAdded(ChannelHandlerContext var1) throws Exception;
void handlerRemoved(ChannelHandlerContext var1) throws Exception;
/** @deprecated */
@Deprecated
void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
@Inherited
@Documented
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Sharable {
}
}
用户在操作Netty不需要自己创建pipeline,因为bootstrap在启动的时候,会为每一个channel创建一个独立的pipeline,对于使用者来说,只需要将channelHandler加入到pipeline中。ChannelPipeline支持运行时动态添加或删除handler,并且ChannelPipeline是线程安全的。
自定义实现channelHandler,通常只需要继承ChannelHandlerAdapter就行,对需要处理的方法进行覆盖。
ChannelFuture和Promise
Netty中所有的操作都是异步的,为了获取操作的结果,设计了ChannelFuture。ChannelFuture有两种状态,当一个IO操作开始时,会创建一个ChannelFuture,状态是unCompleted。一旦IO操作完成,会被设置为completed状态。
public interface ChannelFuture extends Future<Void> {
Channel channel();
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> var1);
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
ChannelFuture sync() throws InterruptedException;
ChannelFuture syncUninterruptibly();
ChannelFuture await() throws InterruptedException;
ChannelFuture awaitUninterruptibly();
boolean isVoid();
}
强烈建议通过增加ChannelFuture的监听器GenericFutureListener处理。通过GenericFutureListener代替get的原因是:异步IO时,如果不设置超时时间,有可能导致线程一直被挂起,甚至挂死;一旦设置超时时间,如果时间到达后事件还未完成,就会出现异常,所以通过异步回调的方式最佳。
此外,不要在ChannelHandler中调用ChannelFuture.await()方法,可能会导致死锁,原因是:由IO线程负责异步通知发起IO操作的用户线程,如果IO线程和用户线程是同一个,就会导致IO线程等待自己通知完成操作,陷入相互等待。
public interface Promise<V> extends Future<V> {
Promise<V> setSuccess(V var1);
boolean trySuccess(V var1);
Promise<V> setFailure(Throwable var1);
boolean tryFailure(Throwable var1);
boolean setUncancellable();
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... var1);
Promise<V> await() throws InterruptedException;
Promise<V> awaitUninterruptibly();
Promise<V> sync() throws InterruptedException;
Promise<V> syncUninterruptibly();
}
Promise是可写的Future,因为jdk自带的Future并没有提供写方法,Netty通过promise对future扩展,用于设置IO操作的结果。
3.2.3 Selector
Selector
能够检测多个注册的通道channel上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的处理,这样就可以只用一个单线程去管理多个通道,即IO多路复用。当channel真正有读写事件发生时,线程才会进行读写,大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,也避免了多线程之间的上下文切换开销。
jdk中selector的定义:
public abstract class Selector implements Closeable {
// 初始化selector
protected Selector() { }
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
public abstract boolean isOpen();
public abstract SelectorProvider provider();
// 返回selector的keyset
public abstract Set<SelectionKey> keys();
// 返回选中的key
public abstract Set<SelectionKey> selectedKeys();
/**
* Selects a set of keys whose corresponding channels are ready for I/O
* operations. 非阻塞方法
*/
public abstract int selectNow() throws IOException;
// 阻塞方法,直到有一个IO ready,或者主动调用selector的wakeUp方法,或者等待timeout过期后返回
public abstract int select(long timeout)
throws IOException;
public abstract int select() throws IOException;
public abstract Selector wakeup();
public abstract void close() throws IOException;
}
netty中将selector封装到了NioEventLoop中。
public final class NioEventLoop extends SingleThreadEventLoop {
...
private Selector selector;
private Selector unwrappedSelector;
NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler);
if (selectorProvider == null) {
throw new NullPointerException("selectorProvider");
} else if (strategy == null) {
throw new NullPointerException("selectStrategy");
} else {
this.provider = selectorProvider;
SelectorTuple selectorTuple = this.openSelector();
this.selector = selectorTuple.selector;
this.unwrappedSelector = selectorTuple.unwrappedSelector;
this.selectStrategy = strategy;
}
}
private SelectorTuple openSelector() {
final AbstractSelector unwrappedSelector;
try {
unwrappedSelector = this.provider.openSelector();
} catch (IOException var7) {
throw new ChannelException("failed to open a new selector", var7);
}
if (DISABLE_KEYSET_OPTIMIZATION) {
return new SelectorTuple(unwrappedSelector);
} else {
final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
return Class.forName("sun.nio.ch.SelectorImpl", false, PlatformDependent.getSystemClassLoader());
} catch (Throwable var2) {
return var2;
}
}
});
if (maybeSelectorImplClass instanceof Class && ((Class)maybeSelectorImplClass).isAssignableFrom(unwrappedSelector.getClass())) {
final Class<?> selectorImplClass = (Class)maybeSelectorImplClass;
Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Object run() {
try {
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField);
if (cause != null) {
return cause;
} else {
cause = ReflectionUtil.trySetAccessible(publicSelectedKeysField);
if (cause != null) {
return cause;
} else {
selectedKeysField.set(unwrappedSelector, selectedKeySet);
publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
return null;
}
}
} catch (NoSuchFieldException var4) {
return var4;
} catch (IllegalAccessException var5) {
return var5;
}
}
});
if (maybeException instanceof Exception) {
this.selectedKeys = null;
Exception e = (Exception)maybeException;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, e);
return new SelectorTuple(unwrappedSelector);
} else {
this.selectedKeys = selectedKeySet;
logger.trace("instrumented a special java.util.Set into: {}", unwrappedSelector);
return new SelectorTuple(unwrappedSelector, new SelectedSelectionKeySetSelector(unwrappedSelector, selectedKeySet));
}
} else {
if (maybeSelectorImplClass instanceof Throwable) {
Throwable t = (Throwable)maybeSelectorImplClass;
logger.trace("failed to instrument a special java.util.Set into: {}", unwrappedSelector, t);
}
return new SelectorTuple(unwrappedSelector);
}
}
}
}
Netty对SelectionKey的优化
jdk中对selectorImpl定义有:
public abstract class SelectorImpl extends AbstractSelector {
protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();
private Set<SelectionKey> publicKeys;
private Set<SelectionKey> publicSelectedKeys;
protected SelectorImpl(SelectorProvider sp) {
super(sp);
keys = new HashSet<>();
selectedKeys = new HashSet<>();
publicKeys = Collections.unmodifiableSet(keys);
publicSelectedKeys = Util.ungrowableSet(selectedKeys);
}
}
Netty中selectedKeys对象
final class SelectedSelectionKeySet extends AbstractSet<SelectionKey> {
SelectionKey[] keys = new SelectionKey[1024];
int size;
SelectedSelectionKeySet() {}
public boolean add(SelectionKey o) {
if (o == null) {
return false;
} else {
this.keys[this.size++] = o;
if (this.size == this.keys.length) {
this.increaseCapacity();
}
return true;
}
}
...
}
修改成了数组。HashSet的add方法在发生哈希冲突时的时间复杂度是O(n), jdk优化后再O(log n), 为此Netty通过反射机制, 将底层的这个HashSet用数组替换了, 毕竟向数组中添加数据的时间复杂度是O(1).
补:EventLoop和EventLoopGroup
首先我们看一下EventLoop的类图结构
可以看到EventLoop继承自eventLoopGroup, 同时继承了OrderedEventExecutor。 EventLoop内部包含了线程池所有的执行方法,也包含了Scheduled调度方法,可以独立进行任务调度。同时,OrderedEventExecutor可以判断一个线程是否属于当前的eventLoop以及eventLoop所在的组。
public interface EventLoop extends OrderedEventExecutor, EventLoopGroup {
EventLoopGroup parent();
}
public interface EventLoopGroup extends EventExecutorGroup {
EventLoop next();
ChannelFuture register(Channel var1);
ChannelFuture register(ChannelPromise var1);
/** @deprecated */
@Deprecated
ChannelFuture register(Channel var1, ChannelPromise var2);
}
public interface EventExecutorGroup extends ScheduledExecutorService, Iterable<EventExecutor> {
boolean isShuttingDown();
Future<?> shutdownGracefully();
Future<?> shutdownGracefully(long var1, long var3, TimeUnit var5);
Future<?> terminationFuture();
/** @deprecated */
@Deprecated
void shutdown();
/** @deprecated */
@Deprecated
List<Runnable> shutdownNow();
EventExecutor next();
Iterator<EventExecutor> iterator();
Future<?> submit(Runnable var1);
<T> Future<T> submit(Runnable var1, T var2);
<T> Future<T> submit(Callable<T> var1);
ScheduledFuture<?> schedule(Runnable var1, long var2, TimeUnit var4);
<V> ScheduledFuture<V> schedule(Callable<V> var1, long var2, TimeUnit var4);
ScheduledFuture<?> scheduleAtFixedRate(Runnable var1, long var2, long var4, TimeUnit var6);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable var1, long var2, long var4, TimeUnit var6);
}
EventLoopGroup是一组EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全)。
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
...
public interface Unsafe {
...
void register(EventLoop var1, ChannelPromise var2); // 将channel绑定到eventLoop上
void bind(SocketAddress var1, ChannelPromise var2);
void connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);
void disconnect(ChannelPromise var1);
...
}
}
// 具体的执行register方法
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
} else if (AbstractChannel.this.isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
} else if (!AbstractChannel.this.isCompatible(eventLoop)) {
promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
} else {
AbstractChannel.this.eventLoop = eventLoop;
if (eventLoop.inEventLoop()) {
this.register0(promise);
} else {
try {
eventLoop.execute(new Runnable() {
public void run() {
AbstractUnsafe.this.register0(promise);
}
});
} catch (Throwable var4) {
AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
this.closeForcibly();
AbstractChannel.this.closeFuture.setClosed();
this.safeSetFailure(promise, var4);
}
}
}
}
常见主要有以下两种EventLoopGroup:
- 1)NioEventLoopGroup:处理IO事件,普通任务,定时任务;
- 2)DefaultEventLoopGroup:处理普通任务,定时任务。
public class EventLoopTest {
public static void main(String[] args) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
for(int i = 10; i>0; i--){
System.out.println(eventLoopGroup.next());
}
}
}
输出:
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
io.netty.channel.nio.NioEventLoop@45018215
io.netty.channel.nio.NioEventLoop@65d6b83b
可以看到这里会对生成的两个NioEventLoop循环打印。
public static void main(String[] args) {
NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup(2);
// 普通的线程提交
eventLoopGroup.next().execute(EventLoopTest::print);
// 定时线程提交
eventLoopGroup.next().scheduleAtFixedRate(EventLoopTest::print, 1,1, TimeUnit.SECONDS);
}
private static void print() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())+ " " +Thread.currentThread());
}
2022-10-23 11:00:25 Thread[nioEventLoopGroup-2-1,10,main]
2022-10-23 11:00:26 Thread[nioEventLoopGroup-2-2,10,main]
2022-10-23 11:00:27 Thread[nioEventLoopGroup-2-2,10,main]
2022-10-23 11:00:28 Thread[nioEventLoopGroup-2-2,10,main]