Netty基础学习(一)

文章主要从以下几个方面进行介绍:

  • 为什么要学习netty
  • netty是什么
  • netty与日常工作如何结合
  • netty的高阶使用

1. 为什么要学习netty

Netty是由JBOSS提供的一个Java开源框架,现为Github上的独立项目。Netty 本质是一个基于 Java NIO 的高性能网络编程框架,在很多应用场景中都可以找到netty的身影,比如游戏场景的高性能通信,Hadoop、Spark 等大数据处理框架都使用了 Netty 来实现节点之间的通信,RPC框架、消息中间件等也都使用到netty。

  • 简单的 API 设计:Netty 提供了简洁、易用的 API,使得开发者可以快速上手并进行网络应用的开发。开发者无需深入了解底层的网络编程细节,如 Socket 编程、I/O 多路复用等,只需要关注业务逻辑的实现。例如,使用 Netty 编写一个简单的 TCP 服务器,只需要几行代码就可以完成服务器的启动和客户端连接的处理。
  • 丰富的组件和工具:Netty 提供了丰富的组件和工具,如编解码器、ChannelHandler 等。编解码器可以方便地实现数据的序列化和反序列化,而 ChannelHandler 则可以对网络数据进行拦截和处理。这些组件和工具可以帮助开发者快速构建出功能强大的网络应用。

另一方面,netty作为高性能网络通信基础,有很多很好的设计可以借鉴到我们的日常研发过程中,比如零拷贝操作,bytebuffer理念,pipeline设计等。当然,netty的相关知识也是我们面试时重点考察的对象。

2. netty是什么

上面提到netty是一个高性能的网络通信框架,对netty高性能分为三部分,即网络IO、线程模型和内存优化:

2.1 网络IO模型

Java IO流从概念上来说是一个连续的数据流,既可以从流中读取数据,也可以往流中写数据。IO相关的媒介包括:

  • 文件
  • 管道
  • 网络连接
  • 内存缓存
  • System.in, System.out

IO的设计,主要是解决IO相关的操作。从数据传输的方式上,分为字节流和字符流。字节流一次性读取传输一个字节,而字符流则是以字符为单位进行读取传输。

LINUX中进程无法直接操作I/O设备,必须通过系统调用请求kernel来协助完成I/O动作。内核会为每个I/O设备维护一个缓冲区,IO输入时应用进程请求内核,内核会先看缓冲区中有没有相应的缓存数据,有数据则直接复制到进程空间,没有的话再到设备中读取。通常用户进程中的一个完整IO分为两阶段:用户进程空间<-->内核空间、内核空间<- ->设备空间。

由于CPU和内存的速度远远高于外设的速度,所以在IO编程中,就存在速度严重不匹配的问题,所以有了同步/异步,阻塞和非阻塞IO之分。

2.1.1 IO 模型

IO模型分为:BIO、NIO、IO多路复用、信号驱动IO和AIO。

BIO:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。BIO阻塞时,其它应用进程还能正常执行,所以不消耗CPU时间,这种模型的CPU利用率较高。

NIO:非阻塞IO模型在内核数据没准备好,需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞;由于CPU需要不断轮询内核数据是否准备好,CPU相比利用率会低一些。

IO多路复用:多个的进程的IO可以注册到一个复用器(selector)上,然后用一个进程调用该select,,select会监听所有注册进来的IO,这一过程会被阻塞,当某一个套接字可读时返回,之后再用recvfrom吧数据从内核拷贝到进程中。 IO多路复用也被称为事件驱动IO。

信号驱动IO:当进程发起一个IO操作,会向内核注册一个信号处理函数,内核立即返回,应用程序可以继续进行。当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。相比于非阻塞的IO轮询,信号驱动IO的CPU利用率更高。

AIO:当进程发起一个IO操作,应用进程执行aio_read系统调用会立即返回,应用程序可以继续进行,内核会在所有操作完成后向应用程序发送信号。AIO和信号驱动IO的区别是:AIO的信号是通知进程IO完成,而信号驱动IO是通知应用程序可以进行IO。

2.1.2 Java NIO

NIO即new IO,是在JDK1.4引入的,NIO和IO有相同的作用和目的,但实现方式不同,NIO主要用到的是块,所以NIO的效率要比IO高很多。

NIO的核心对象包括:

  • Buffer:在NIO中,所有的数据都是用Buffer处理的,它是NIO读写数据的中转池。Buffer实质上是一个数组,通常是一个字节数据,但也可以是其他类型的数组。
  • Channel:是一个对象,可以通过channel读取和写入数据,是IO中流的抽象。但是channel是双向的,也可以是异步读写,并且channel读写必须通过buffer。
  • Selector:是一个对象,可以同时监听多个channel上发生的事件,并且能够根据事件情况决定Channel读写。
// 打开Selector
Selector selector = Selector.open();
// 将channel注册到selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

Selector感兴趣的事件有SelectionKey.OP_CONNECT, SelectionKey.OP_ACCEPT, SelectionKey.OP_READ, SelectionKey.OP_WRITE。

SelectionKey表示通道channel在Selector上的注册,事件的传递是通过SelectionKey,也可以通过selectionKey获取注册的channel和对应绑定的selector。

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector(); 

一旦向selector注册一个或者多个通道后,就可以调用重载的select()方法,select()方法会返回读事件已经就绪的那些通道

  • int select():阻塞到至少有一个通道的事件就绪
  • int select(long timeout):与select一样,多个一个超时时间
  • int selectNow():不会阻塞,不管什么通道就绪都立刻返回,如果没有通道可选择,就返回0.

一旦调用了select()方法,它就会返回一个数值,表示一个或多个通道已经就绪,然后你就可以通过调用selector.selectedKeys()方法返回的SelectionKey集合来获得就绪的Channel。某个线程调用select()方法后阻塞了,即使没有通道就绪,也有办法让其从select()方法返回。

  • 让其它线程在调用select方法的对象上调用Selector.wakeup()方法即可,阻塞在select()方法上的线程会立马返回。
  • 如果其它线程调用了wakeup(),但是当前没有线程阻塞在select上,下一个调用select阻塞的线程会被立即唤醒。

2.1.3 Java NIO和netty关系

直接使用Java NIO的缺点:

  • NIO的类库和API繁杂,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等才能很好使用。
  • 可靠性较弱,需要自行维护,工作量大,例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题;
  • JDK NIO的BUG,例如epoll bug,它会导致Selector空轮询,最终导致CPU 100%。

Netty是对Java NIO的封装框架,简化了NIO的使用难度,Netty特性总结如下:

  • API使用简单,开发门槛低
  • 功能强大,预置了多种编解码功能,支持多种主流协议
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时更多的新功能会加入
  • 经历了大规模的商业应用考验,质量得到验证。

2.2 Netty中的NIO关键组件

2.2.1 Buffer缓冲区

缓冲区本质上是一个可以读写数据的内存块,Netty封装Buffer对象提供了一组方法,可以更轻松地使用内存块,并且跟踪和记录缓冲区的状态变化。从Channel读取或写入的数据都必须经由Buffer。

Buffer的类图结构有:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {
    public ByteBuf() {
    }
    // 存储大小
    public abstract int capacity();
    public abstract ByteBuf capacity(int var1);
    ...
    public abstract int readerIndex();
    public abstract int writerIndex();
    public abstract ByteBuf clear();
    public abstract ByteBuf markReaderIndex();
    public abstract ByteBuf resetReaderIndex();
    public abstract ByteBuf markWriterIndex();
    public abstract ByteBuf resetWriterIndex();
    ...
}

public abstract class AbstractByteBuf extends ByteBuf {
    ...
    int readerIndex;
    int writerIndex;
    private int markedReaderIndex;
    private int markedWriterIndex;
    private int maxCapacity;
    // 可读
    public ByteBuf readerIndex(int readerIndex) {
        if (readerIndex >= 0 && readerIndex <= this.writerIndex) {
            this.readerIndex = readerIndex;
            return this;
        } else {
            throw new IndexOutOfBoundsException(String.format("readerIndex: %d (expected: 0 <= readerIndex <= writerIndex(%d))", readerIndex, this.writerIndex));
        }
    }
    // 可写
    public ByteBuf writerIndex(int writerIndex) {
        if (writerIndex >= this.readerIndex && writerIndex <= this.capacity()) {
            this.writerIndex = writerIndex;
            return this;
        } else {
            throw new IndexOutOfBoundsException(String.format("writerIndex: %d (expected: readerIndex(%d) <= writerIndex <= capacity(%d))", writerIndex, this.readerIndex, this.capacity()));
        }
    }
    // 清理Buffer
    public ByteBuf clear() {
        this.readerIndex = this.writerIndex = 0;
        return this;
    }

    public ByteBuf markReaderIndex() {
        this.markedReaderIndex = this.readerIndex;
        return this;
    }
    public ByteBuf resetReaderIndex() {
        this.readerIndex(this.markedReaderIndex);
        return this;
    }
    public ByteBuf markWriterIndex() {
        this.markedWriterIndex = this.writerIndex;
        return this;
    }
    public ByteBuf resetWriterIndex() {
        this.writerIndex = this.markedWriterIndex;
        return this;
    }
    ...
}

netty的ByteBuffer有几个特性:

  • Netty的ByteBuffer提供两个指针变量用于顺序读和顺序写,ReadIndex和WriteIndex。两个指针将Buffer分成3个区域,读取的数据只能位于readIndex和writeIndex之间,可写数据位于writeIndex和capacity之间。0到readIndex之间的区域是已经读取过的区域,可以调用discardReadBytes来重用这部分区间,以节约内存,防止Buffer的动态扩张。
  • clear操作,并不会清空buffer的存储内容,而是用来充值readIndex和writeIndex,position,Mark和limit的,将他们还原为初始设置值。
  • Mark和reset,Mark操作会将当前的位置指针备份到Mark变量中,调用reset后会将指针恢复到备份的位置,这种操作主要是因为对于某些读写需要回滚。

从存储结构上,buffer分为堆内存和直接内存buffer。

  • 堆内存的特点是分配和回收速度快,也能被JVM自动管理,缺点是做Socket IO 操作时,需要从用户态拷贝到内核态,多一次复制操作。
  • 直接内存buffer在堆外分配,缺点是分配和回收速度慢,但是做Socket IO操作时,会少一次内存拷贝。

结合对内对外内存分配的特点,在IO通信的时候使用直接内存分配,可以减少JVM的内存分配限制,还可以直接使用zeroCopy技术。后端业务编码的时候,可以直接使用HeapByteBuffer。

Netty的容量自动扩展,在小于4M的时候,采用倍增;大于等于4M时,采用每次增加4M。

select, poll和epoll

在OS上,对多个IO事件的监听是通过select/poll和epoll来实现的。他们的区别如下:

select

int select(int n, fd_set *readfds, fd_set *writefds, struct timeval *timeout)

  • fd_set使用数组实现,数组大小使用FD_SETSIZE,所以只能监听少于FD_SETSIZE的描述符
  • timeout为超时参数,调用select会一直阻塞直到描述符事件ready或者超时;
  • 成功调用会返回1,错误返回-1,超时返回0

poll

int poll(struct pollfd *fds, unsigned int nfds, int timeout)

poll的功能和select类似,但poll中描述符是polld类型数组。polld定义如下:

struct pollfd{

        int fd;

        short events;   /* request events */

        short revents;  /* return events */

}

epoll

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * enevts, int maxevents, int timeout);

epoll_ctl()用于向内核注重心的描述符或者改变某个文件描述的状态。已注册的描述符会在内核中维护在一颗红黑树上,通过回调函数内核会将IO准备好的描述符加入到一个链表中管理,进程调用epoll_wait()便可以得到事件完成的描述符。

epoll的描述符事件有两种触发模式:LT(level trigger)和ET(edge trigger)。

比较

  • select和poll的功能基本相同,但是select会修改描述符,poll不会;
  • select的描述符使用数组实现,默然大小不超过1024,而poll没有限制;
  • poll提供了更多的事件类型,并且对描述符重复利用率比select高
  • 如果一个线程对某个描述符调用了select或者poll,另一个线程关闭了改描述符,会导致结果不确定。
  • 几乎所有的OS都支持select,但只有新的OS支持poll。
  • epoll比select和poll更叫灵活而且描述符没有限制,对多线程也更友好,一个线程调用epoll_wait(),另一个线程关闭了这个描述符也不会产生不确定情况,但epoll仅适用于linux OS 

场景:

  • select 的timeout参数是微妙,而poll和epoll在毫秒级别;
  • poll没有最大描述符数量限制,如果平台实时性要求不高,可使用poll;
  • 在Linux OS上并且有大量描述符(大于1000)需要同时轮询,选择epoll。

2.2.2 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类完成的。

2.2.3 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就行,对需要处理的方法进行覆盖。

2.2.4 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操作的结果。

 本章续上一章节没讲完的IO模型。 

2.2.5 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]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值