介绍前面几章我们讲解了kqueue的使用和原理,接下来我们再来看epoll的使用。都是比较高级的IO方法,都需要用native方法实现。不同的是在mac系统中使用的是Kqueue,而在liunx系统中使用的是epoll。epoll的使用详解epoll的使用也很简单,我们还是以常用的聊天室为例来讲解epoll的使用。对于服务器端,您需要创建bossGroup和workerGroup。在NIO中,这两组就是NIOEventLoopGroup。在epoll中,需要使用EpollEventLoopGroup:EventLoopGroupbossGroup=newEpollEventLoopGroup(1);EventLoopGroupworkerGroup=newEpollEventLoopGroup();然后你需要添加传递给ServerBootstrap的bossGroup和workerGroup:ServerBootstrapb=newServerBootstrap();b.group(bossGroup,workerGroup).channel(EpollServerSocketChannel.class).handler(newLoggingHandler(LogLevel.INFO)).childHandler(newNativeChatServerInitializer());注意这里传入的channel是EpollServerSocketChannel,专门用来处理epoll请求的。其他部分与普通NIO服务相同。接下来看epoll的客户端。对于客户端,需要创建一个EventLoopGroup,它使用EpollEventLoopGroup:EventLoopGroupgroup=newEpollEventLoopGroup();然后将这个组传递给Bootstrap:Bootstrapb=newBootstrap();b.group(group).channel(EpollSocketChannel.class).handler(newNativeChatClientInitializer());这里使用的channel是EpollSocketChannel,也就是EpollServerSocketChannel对应的client的channel。EpollEventLoopGroup首先看一下EpollEventLoopGroup的定义:publicfinalclassEpollEventLoopGroupextendsMultithreadEventLoopGroup和KqueueEventLoopGroup一样,EpollEventLoopGroup也是继承自MultithreadEventLoopGroup,也就是说可以开多线程。在使用EpollEventLoopGroup之前,需要保证所有epoll相关的JNI接口都已经准备好:Epoll.ensureAvailability();newChild方法用于生成EpollEventLoopGroup的子EventLoop:protectedEventLoopnewChild(Executorexecutor,Object...args)throwsException{IntegermaxEvents=(Integer)args[0];SelectStrategyFactoryselectStrategyFactory=(SelectStrategyFactory)args[1];RejectedExecutionHandlerrejectedExecutionHandler=(RejectedExecutionHandler)args[2];EventLoopTaskQueueFactorytaskQueueFactory=null;EventLoopTaskQueueFactorytailTask??QueueFactory=null;intargsLength=args.length;如果(argsLength>3){taskQueueFactory=(EventLoopTaskQueueFactory)args[3];}if(argsLength>4){tailTask??QueueFactory=(EventLoopTaskQueueFactory)args[4];}返回新的EpollEventLoop(this,executor.,rejectedExecutionHandler、taskQueueFactory、tailTask??QueueFactory);从方法中可以看出,newChild接受一个executor和多个附加参数,分别是SelectStrategyFactory、RejectedExecutionHandler、taskQueueFactory和tailTask??QueueFactory,最后将这些参数传入EpollEventLoop并返回一个新的EpollEventLoop对象EpollEventLoopEpollEventLoop是由EpollEventLoopGroup使用新的子方法。对于EpollEventLoop本身来说,是一个SingleThreadEventLoop:classEpollEventLoopextendsSingleThreadEventLoop借助原生epollIO的强大功能,EpollEventLoop可以在单线程的情况下快速进行业务处理,非常好。和EpollEventLoopGroup一样,EpollEventLoop在初始化的时候需要检测系统是否支持epoll:static{Epoll.ensureAvailability();在EpollEventLoopGroup调用的EpollEventLoop的构造函数中,初始化了三个FileDescriptor,分别是epollFd、eventFd和timerFd。三个FileDescriptor都是调用Native方法创建的:this.epollFd=epollFd=Native.newEpollCreate();this.eventFd=eventFd=Native.newEventFd();this.timerFd=timerFd=Native.newTimerFd();然后调用Native.epollCtlAdd建立FileDescriptors之间的关联:Native.epollCtlAdd(epollFd.intValue(),eventFd.intValue(),Native.EPOLLIN|Native.EPOLLET);Native.epollCtlAdd(epollFd.intValue(),timerFd.intValue(),Native.EPOLLIN|Native.EPOLLET);在EpollEventLoop的run方法中,会先调用selectStrategy.calculateStrategy方法获取当前select状态。默认情况下,有三种状态,即:intSELECT=-1;int继续=-2;intBUSY_WAIT=-3;kqueue中已经引入了这三种状态,不同的是epoll支持BUSY_WAIT状态,在BUSY_WAIT状态下,会调用Native.epollBusyWait(epollFd,events)方法返回busy等待的事件数。如果处于选择状态,则会调用Native.epollWait(epollFd,events,1000)方法返回等待状态的事件数。接下来会分别调用processReady(events,strategy)和runAllTask??s方法进行事件就绪状态和最终任务执行的回调处理。EpollServerSocketChannel首先看一下EpollServerSocketChannel的定义:publicfinalclassEpollServerSocketChannelextendsAbstractEpollServerChannelimplementsServerSocketChannelEpollServerSocketChannel继承自AbstractEpollServerSocketChannel,实现了ServerSocketChannel接口。EpollServerSocketChannel的构造函数需要传入一个LinuxSocket:EpollServerSocketChannel(LinuxSocketfd){super(fd);config=newEpollServerSocketChannelConfig(this);}LinuxSocket是一个特殊的socket,用来处理与linux原生socket的连接。EpollServerSocketChannelConfig是构造EpollServerSocketChannel的配置。这里用到4个配置选项,分别是SO_REUSEPORT、IP_FREEBIND、IP_TRANSPARENT、TCP_DEFER_ACCEPT和TCP_MD5SIG。每个配置项对应网络协议的特定含义。我们再看一下EpollServerSocketChannel的newChildChannel方法:长度));}newChildChannel和KqueueServerSocketChannel方法一样,同样返回一个EpollSocketChannel,将传入的fd构造成LinuxSocket。EpollSocketChannelEpollSocketChannel由EpollServerSocketChannel创建并返回,首先看EpollSocketChannel的定义:publicfinalclassEpollSocketChannelextendsAbstractEpollStreamChannelimplementsSocketChannel{可以看到EpollSocketChannel继承自AbstractChannelStream接口,实现了AbstractChannelStream接口。回到之前EpollServerSocketChannel创建EpollSocketChannel时调用的newChildChannel方法,该方法会调用EpollSocketChannel的构造函数,如下所示:这);if(EpollServerSocketChannel的父实例){tcpMd5SigAddresses=((EpollServerSocketChannel)parent).tcpMd5SigAddresses();}}从代码逻辑可以看出,如果EpollSocketChannel是从EpollServerSocketChannel创建的,那么默认会开启tcpMd5Sig特性。什么是tcpMd5Sig?tcpMd5Sig简单来说就是在TCP数据报文中加上MD5sig对数据进行校验,从而提示数据传输的安全性。TCPMD5是在RFC2385中提出的,只能在linux内核中启用,也就是说如果要使用tcpMd5Sig,就必须使用EpollServerSocketChannel和EpollSocketChannel。所以,如果是追求性能或者特殊使用场景的朋友,还是有很多时候需要接触这种nativetransport的,可以仔细研究一下配置选项。再看看EpollSocketChannel中非常重要的doConnect0方法:booleandoConnect0(SocketAddressremote)throwsException{if(IS_SUPPORTING_TCP_FASTOPEN_CLIENT&&config.isTcpFastOpenConnect()){;if((curr=outbound.current())instanceofByteBuf){ByteBufinitialData=(ByteBuf)curr;longlocalFlushedAmount=doWriteOrSendBytes(initialData,(InetSocketAddress)remote,true);如果(localFlushedAmount>0){outbounds.removeBytelocalFlushedAmount;返回真;}}}返回super.doConnect0(远程);在这个方法中,它会首先判断是否启用了TcpFastOpen选项。如果启用此选项,最终将调用LinuxSocket的write或sendTo方法。这些方法可以添加初始数据,在建立连接的同时传输数据,从而达到Tcp快开的效果。如果不是tcpfastopen,那么需要调用Socket的connect方法建立传统连接。综上所述,netty中epoll的实现与kqueue非常相似。区别在于运行平台和具体的功能参数。如果追求高性能,可以深入研究。本文代码可以参考:learn-netty4更多内容请参考http://www.flydean.com/53-2-netty-epoll-transport/最通俗的解读,最深刻的干货,最简洁的教程,很多你不知道的小技巧等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!
