
前言本文从操作系统实际调用角度(以操作系统为示例),力求追根溯源看IO的每一步操作到底发生了什么。关于如何查看系统调用,Linux可以使用strace来查看任何软件的系统调动(这是个很好的分析学习方法......
本文从操作系统实际调用角度(以操作系统为示例),力求追根溯源看IO的每一步操作到底发生了什么。
关于如何查看系统调用,Linux可以使用strace来查看任何软件的系统调动(这是个很好的分析学习方法):strace-ff-o./outjavaTestJava
一BIO/***(c)2004-2020AllRightsReserved.*/packageio;*;;;/***@*@version$Id:,年08月02日20:56$*/publicclassBIOSocket{publicstaticvoidmain(String[]args)throwsIOException{ServerSocketserverSocket=newServerSocket(8090);("step1:newServerSocket");while(true){Socketclient=();("step2:client\t"+());newThread(()-{try{InputStreamin=();BufferedReaderreader=newBufferedReader(newInputStreamReader(in));while(true){(());}}catch(IOExceptione){();}}).start();}}}1发生的系统调用
启动时
socket(AF_INET,SOCK_STREAM,IPPROTO_IP)=5bind(5,{sa_family=AF_INET,sin_port=htons(8090),sin_addr=inet_addr("0.0.0.0")},16)=0listen(5,50)=0poll([{fd=5,events=POLLIN|POLLERR}],1,-1)=1([{fd=5,revents=POLLIN}])poll函数会阻塞直到其中任何一个fd发生事件。
有客户端连接后
accept(5,{sa_family=AF_INET,sin_port=htons(10253),sin_addr=inet_addr("42.120.74.252")},[16])=6clone(child_stack=0x7f013e5c4fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7f013e5c59d0,tls=0x7f013e5c5700,child_tidptr=0x7f013e5c59d0)=13168poll([{fd=5,events=POLLIN|POLLERR}],1,-1抛出线程(即我们代码里的newThread())后,继续poll阻塞等待连接。
clone出来的线程
recvfrom(6,"hello,bio\n",8192,0,NULL,NULL)=
关于对recvfrom函数的说明,其中第四个参数0表示这是一个阻塞调用。
客户端发送数据后
recvfrom(6,"hello,bio\n",8192,0,NULL,NULL)=10
2优缺点
优点
代码简单,逻辑清晰。
缺点
由于stream的read操作是阻塞读,面对多个连接时每个连接需要每线程。无法处理大量连接(C10K问题)。
误区:可见中对于最初的BIO,在LinuxOS下仍然使用的poll,poll本身也是相对比较高效的多路复用函数(支持非阻塞、多个socket同时检查event),只是限于JDK最初的streamAPI限制,无法支持非阻塞读取。
二NIO(nonblock)改进:使用NIOAPI,将阻塞变为非阻塞,不需要大量线程。
/***(c)2004-2020AllRightsReserved.*/packageio;;;;;;;/***@*@version$Id:,年08月09日11:25$*/publicclassNIOSocket{privatestaticLinkedListSocketChannelclients=newLinkedList();privatestaticvoidstartClientChannelHandleThread(){newThread(()-{while(true){ByteBufferbuffer=(4096);//处理客户端连接for(SocketChannelc:clients){//非阻塞,0表示读取到的字节数量,0或-1表示未读取到或读取异常intnum=0;try{num=(buffer);}catch(IOExceptione){();}if(num0){();byte[]clientBytes=newbyte[()];//从缓冲区读取到内存中(clientBytes);(().getPort()+":"+newString(clientBytes));//清空缓冲区();}}}}).start();}publicstaticvoidmain(String[]args)throwsIOException{//newsocket,开启监听ServerSocketChannelsocketChannel=();(newInetSocketAddress(9090));//设置阻塞接受客户端连接(true);//开始client处理线程startClientChannelHandleThread();while(true){//接受客户端连接;非阻塞,无客户端返回null(操作系统返回-1)SocketChannelclient=();if(client==null){//("noclient");}else{//设置读非阻塞(false);intport=().getPort();("clientport:"+port);(client);}}}}1发生的系统调用
主线程
socket(AF_INET,SOCK_STREAM,IPPROTO_IP)=4bind(4,{sa_family=AF_INET,sin_port=htons(9090),sin_addr=inet_addr("0.0.0.0")},16)=0listen(4,50)=0fcntl(4,F_SETFL,O_RDWR|O_NONBLOCK)=0accept(4,0x7fe26414e680,0x7fe26c376710)=-1EAGAIN(Resourcetemporarilyunavailable)有连接后,子线程
read(6,0x7f3f415b1c50,4096)=-1EAGAIN(Resourcetemporarilyunavailable)read(6,0x7f3f415b1c50,4096)=-1EAGAIN(Resourcetemporarilyunavailable)
资源使用情况:
2优缺点
优点
线程数大大减少。
缺点
需要程序自己扫描每个连接read,需要O(n)时间复杂度系统调用(此时可能只有一个连接发送了数据),高频系统调用(导致CPU用户态内核态切换)高。导致CPU消耗很高。
三多路复用器(select、poll、epoll)改进:不需要用户扫描所有连接,由kernel给出哪些连接有数据,然后应用从有数据的连接读取数据。
1epoll
;;;;;;;;;/***多路复用socket**@*@version$Id:,年08月09日12:19$*/publicclassMultiplexingSocket{staticByteBufferbuffer=(4096);publicstaticvoidmain(String[]args)throwsException{LinkedListSocketChannelclients=newLinkedList();//1.启动server//newsocket,开启监听ServerSocketChannelsocketChannel=();(newInetSocketAddress(9090));//设置非阻塞,接受客户端(false);//多路复用器(JDK包装的代理,select/poll/epoll/kqueue)Selectorselector=();//java自动代理,默认为epoll//Selectorselector=().openSelector();//指定为poll//将服务端socket注册到多路复用器(selector,_ACCEPT);//2.轮训多路复用器//先询问有没有连接,如果有则返回数量以及对应的对象(fd)while(()0){();SetSelectionKeyselectionKeys=();IteratorSelectionKeyiter=();while(()){SelectionKeykey=();();//2.1处理新的连接if(()){//接受客户端连接;非阻塞,无客户端返回null(操作系统返回-1)SocketChannelclient=();//设置读非阻塞(false);//同样,把client也注册到(selector,_READ);("newclient:"+());}//2.2处理读取数据elseif(()){readDataFromSocket(key);}}}}protectedstaticvoidreadDataFromSocket(SelectionKeykey)throwsException{SocketChannelsocketChannel=(SocketChannel)();//非阻塞,0表示读取到的字节数量,0或-1表示未读取到或读取异常//请注意:这个例子降低复杂度,不考虑报文大于buffersize的情况intnum=(buffer);if(num0){();byte[]clientBytes=newbyte[()];//从缓冲区读取到内存中(clientBytes);(().getPort()+":"+newString(clientBytes));//清空缓冲区();}}}2发生的系统调用
启动
socket(AF_INET,SOCK_STREAM,IPPROTO_IP)=4bind(4,{sa_family=AF_INET,sin_port=htons(9090),sin_addr=inet_addr("0.0.0.0")},16)=0listen(4,50)fcntl(4,F_SETFL,O_RDWR|O_NONBLOCK)=0epoll_create(256)=7epoll_ctl(7,EPOLL_CTL_ADD,5,{EPOLLIN,{u32=5,u64=4324783852322029573}})=0epoll_ctl(7,EPOLL_CTL_ADD,4,{EPOLLIN,{u32=4,u64=6}})=0epoll_wait(7关于对epoll_create(对应着Java的Selectorselector=())的说明,本质上是在内存的操作系统保留区,创建一个epoll数据结构。用于后面当有client连接时,向该epoll区中添加监听。
有连接
epoll_wait(7,[{EPOLLIN,{u32=4,u64=6}}],8192,-1)=1accept(4,{sa_family=AF_INET,sin_port=htons(29597),sin_addr=inet_addr("42.120.74.252")},[16])=8fcntl(8,F_SETFL,O_RDWR|O_NONBLOCK)=0epoll_ctl(7,EPOLL_CTL_ADD,8,{EPOLLIN,{u32=8,u64=3212844375897800712}})=0关于epoll_ctl(对应着Java的(selector,_READ))。其中EPOLLIN恰好对应着Java的_READ即监听数据到达读取事件。
客户端发送数据
epoll_wait(7,[{EPOLLIN,{u32=8,u64=3212844375897800712}}],8192,-1)=1read(8,"hello,multiplex\n",4096)=16epoll_wait(7,poll和epoll对比
根据“1.BIO”中的poll函数调用和epoll函数对比如下:
poll和epoll本质上都是同步IO,区别于BIO的是多路复用充分降低了systemcall,而epoll更进一步,再次降低了systemcall的时间复杂度。
3优缺点
优点
线程数同样很少,甚至可以把acceptor线程和worker线程使用同一个。
时间复杂度低,Java实现的Selector(在LinuxOS下使用的epoll函数)支持多个clientChannel事件的一次性获取,且时间复杂度维持在O(1)。
CPU使用低:得益于Selector,我们不用向“2.NIO”中需要自己一个个ClientChannel手动去检查事件,因此使得CPU使用率大大降低。
缺点
数据处理麻烦:目前读取数据完全是基于字节的,当我们需要需要作为HTTP服务网关时,对于HTTP协议的处理完全需要自己解析,这是个庞大、烦杂、容易出错的工作。
性能现有socket数据的读取((buffer))全部通过一个buffer缓冲区来接受,一旦连接多起来,这无疑是一个单线程读取,性能无疑是个问题。那么此时buffer我们每次读取都重新new出来呢?如果每次都new出来,这样的内存碎片对于GC无疑是一场灾难。如何平衡地协调好buffer的共享,既保证性能,又保证线程安全,这是个难题。
四Netty1研究的目标源码(netty提供的入门example)
TelnetServer
packagetelnet;;;;;;;;;;/***Simplistictelnetserver.*/publicfinalclassTelnetServer{staticfinalbooleanSSL=("ssl")!=null;staticfinalintPORT=(("port",SSL?"8992":"8023"));publicstaticvoidmain(String[]args)throwsException{//;if(SSL){SelfSignedCertificatessc=newSelfSignedCertificate();sslCtx=((),()).build();}else{sslCtx=null;}EventLoopGroupbossGroup=newNioEventLoopGroup(1);EventLoopGroupworkerGroup=newNioEventLoopGroup();try{ServerBootstrapb=newServerBootstrap();(bossGroup,workerGroup).channel().handler(newLoggingHandler()).childHandler(newTelnetServerInitializer(sslCtx));(PORT).sync().channel().closeFuture().sync();}finally{();();}}}TelnetServerHandler
packagetelnet;
;;;;;;;/***Handlesaserver-sidechannel.*/@SharablepublicclassTelnetServerHandlerextsSimpleChannelInboundHandlerString{@OverridepublicvoidchannelActive(ChannelHandlerContextctx)throwsException{//("Welcometo"+().getHostName()+"!\r\n");("Itis"+newDate()+"now.\r\n");();}@OverridepublicvoidchannelRead0(ChannelHandlerContextctx,Stringrequest)throwsException{//;booleanclose=false;if(()){response="Pleasetypesomething.\r\n";}elseif("bye".equals(())){response="Haveagoodday!\r\n";close=true;}else{response="Didyousay'"+request+"'?\r\n";}//WedonotneedtowriteaChannelBufferhere.//Wek=(response);//Closetheconnectionaftersing'Haveagoodday!'//iftheclienthassent'bye'.if(close){();}}@OverridepublicvoidchannelReadComplete(ChannelHandlerContextctx){();}@OverridepublicvoidexceptionCaught(ChannelHandlerContextctx,Throwablecause){();();}}TelnetServerInitializer
packagetelnet;;;;;;;;;/***Createsanewlyconfigured{@linkChannelPipeline}foranewchannel.*/publicclassTelnetServerInitializerextsChannelInitializerSocketChannel{privatestaticfinalStringDecoderDECODER=newStringDecoder();privatestaticfinalStringEncoderENCODER=newStringEncoder();privatestaticfinalTelnetServerHandlerSERVER_HANDLER=newTelnetServerHandler();privatefinalSslContextsslCtx;publicTelnetServerInitializer(SslContextsslCtx){=sslCtx;}@OverridepublicvoidinitChannel(SocketChannelch)throwsException{ChannelPipelinepipeline=();if(sslCtx!=null){((()));}//Addthetextlinecodeccombinationfirst,(newDelimiterBasedFrameDecoder(8192,()));//(DECODER);(ENCODER);//(SERVER_HANDLER);}}2启动后的系统调用
主线程(23109)
256无实际作用,这里只为了兼容旧版kernelapiepoll_create(256)=7epoll_ctl(7,EPOLL_CTL_ADD,5,{EPOLLIN,{u32=5,u64=5477705356928876549}})=0epoll_create(256)=10epoll_ctl(10,EPOLL_CTL_ADD,8,{EPOLLIN,{u32=8,u64=081853448}})=0epoll_create(256)=13epoll_ctl(13,EPOLL_CTL_ADD,11,{EPOLLIN,{u32=11,u64=409573899}})=0epoll_create(256)=16epoll_ctl(16,EPOLL_CTL_ADD,14,{EPOLLIN,{u32=14,u64=737294350}})=0epoll_create(256)=19epoll_ctl(19,EPOLL_CTL_ADD,17,{EPOLLIN,{u32=17,u64=368827409}})=0epoll_create(256)=10socket(AF_INET,SOCK_STREAM,IPPROTO_IP)=20clone(child_stack=0x7fc3c509afb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7fc3c509b9d0,tls=0x7fc3c509b700,child_tidptr=0x7fc3c509b9d0)=23130概括为:
向OS新建socket,并开启cloneboss线程23130。
为BOSS创建了一个epoll(论证参见下面“boss”),每个worker创建一个epoll数据结构(本质上是在kernel内存区创建了一个数据结构,用于后续监听)。
创建boss线程监听的socket(本质上在kernel中创建一个数据结构)。
boss(23130)
bind(20,{sa_family=AF_INET,sin_port=htons(8023),sin_addr=inet_addr("0.0.0.0")},16)=0listen(20,128)=0getsockname(20,{sa_family=AF_INET,sin_port=htons(8023),sin_addr=inet_addr("0.0.0.0")},[16])=0getsockname(20,{sa_family=AF_INET,sin_port=htons(8023),sin_addr=inet_addr("0.0.0.0")},[16])=0将fd为7号epoll和fd为20号的socket绑定,事件:epoll_ctl_add和epoll_ctl_modepoll_ctl(7,EPOLL_CTL_ADD,20,{EPOLLIN,{u32=20,u64=132817428}})=0epoll_ctl(7,EPOLL_CTL_MOD,20,{EPOLLIN,{u32=20,u64=20}})=0epoll_wait(7,[{EPOLLIN,{u32=5,u64=149058053}}],8192,1000)=1epoll_wait(7,[],8192,1000)=0(不断轮训,1S超时一次)概括为:
将上一步中main线程创建的fd:20绑定端口8023,并开启监听(网卡负责监听和接受连接和数据,kernel则负责路由到具体进程,具体参见:关于socket和bind和listen,TODO)。
将7号socket对应的fd绑定到20号对应的epoll数据结构上去(都是操作kernel中的内存)。
开始1S中一次阻塞等待epoll有任何连接或数据到达。
3客户端连接
boss(23130)
accept(20,{sa_family=AF_INET,sin_port=htons(11144),sin_addr=inet_addr("42.120.74.122")},[16])=24getsockname(24,{sa_family=AF_INET,sin_port=htons(8023),sin_addr=inet_addr("192.168.0.120")},[16])=0getsockname(24,{sa_family=AF_INET,sin_port=htons(8023),sin_addr=inet_addr("192.168.0.120")},[16])=0setsockopt(24,SOL_TCP,TCP_NODELAY,[1],4)=0getsockopt(24,SOL_SOCKET,SO_SNDBUF,[87040],[4])=0getsockopt(24,SOL_SOCKET,SO_SNDBUF,[87040],[4])=0抛出work线程clone(child_stack=0x7fc3c4c98fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7fc3c4c999d0,tls=0x7fc3c4c99700,child_tidptr=0x7fc3c4c999d0)=2301worker(2301)
writev(24,[{"WelcometoiZbp14e1g9ztpshfrla9m",37},{"ItisSunAug2315:44:14CST20",41}],2)=78epoll_ctl(13,EPOLL_CTL_ADD,24,{EPOLLIN,{u32=24,u64=24}})=0epoll_ctl(13,EPOLL_CTL_MOD,24,{EPOLLIN,{u32=24,u64=221450264}})=0epoll_wait(13,[{EPOLLIN,{u32=11,u64=409573899}}],8192,1000)=1read(11,"\1",128)=1开始无限loopepoll_wait(13,[],8192,1000)=0epoll_wait(13,[{EPOLLIN,{u32=24,u64=24}}],8192,1000)=1概括:
当BOSS轮训epoll_wait等到了连接后,首先accept得到该socket对应的fd。
连接建立后BOSS立马抛出一个线程(clone函数)。
worker(即新建的线程)写入了一段数据(这里是业务逻辑)。
worker将该client对应的fd绑定到了13号epoll上。
worker继续轮训监听13号epoll。
4客户端主动发送数据
worker(2301)
read(24,"iamdaojian\r\n",1024)=14write(24,"Didyousay'iamdaojian'?\r\n",29)=29继续无限loopepoll_wait(13,[],8192,1000)=0
概括为:
wait到数据后,立即read到用户控件内存中(读取1024个字节到用户控件某个buff中)。
继续轮训等待13号epoll。
5客户端发送bye报文,服务器断开TCP连接
worker(2301)
read(24,"bye\r\n",1024)=5write(24,"Haveagoodday!\r\n",18)=18getsockopt(24,SOL_SOCKET,SO_LINGER,{onoff=0,linger=0},[8])=0dup2(25,24)=24从epoll数据结构中(OS)中删除fd为24的socketepoll_ctl(13,EPOLL_CTL_DEL,24,0x7f702dd531e0)=-1ENOENT关闭24socketclose(24)=0继续等待13epoll数据epoll_wait(13,[],8192,1000)=0断开客户端连接概括为:
从epoll中删除该客户端对应的fd(这里触发源头没找到,可能是boss)。
close关闭客户端24号fd。
继续轮训epoll。
6五个客户端同时连接
boss线程(23130)
accept(20,{sa_family=AF_INET,sin_port=htons(1846),sin_addr=inet_addr("42.120.74.122")},[16])=24clone(child_stack=0x7f702cc51fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7f702cc529d0,tls=0x7f702cc52700,child_tidptr=0x7f702cc529d0)=10035accept(20,{sa_family=AF_INET,sin_port=htons(42067),sin_addr=inet_addr("42.120.74.122")},[16])=26clone(child_stack=0x7f702cb50fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7f702cb519d0,tls=0x7f702cb51700,child_tidptr=0x7f702cb519d0)=10067woker线程(10035,第一个连接)
epoll_ctl(13,EPOLL_CTL_ADD,24,{EPOLLIN,{u32=24,u64=24}})=0epoll_ctl(13,EPOLL_CTL_MOD,24,{EPOLLIN,{u32=24,u64=3226004877247250456}})=0epoll_wait(13,[{EPOLLIN,{u32=11,u64=409573899}}],8192,1000)=1=1epoll_wait(13,[],8192,1000)=0worker线程(10067,第二个连接)
epoll_ctl(16,EPOLL_CTL_ADD,26,{EPOLLIN,{u32=26,u64=26}})=0epoll_ctl(16,EPOLL_CTL_MOD,26,{EPOLLIN,{u32=26,u64=32235546}})=0epoll_wait(16,[{EPOLLIN,{u32=14,u64=737294350}}],8192,1000)=1epoll_wait(16,[],8192,1000)=0epoll_wait(16,[],8192,1000)=0worker线程(10067,第二个连接)
epoll_ctl(19,EPOLL_CTL_ADD,27,{EPOLLIN,{u32=27,u64=27}})=0epoll_ctl(19,EPOLL_CTL_MOD,27,{EPOLLIN,{u32=27,u64=3216966479350071323}})=0worker线程(8055,第四个连接)
epoll_ctl(10,EPOLL_CTL_ADD,28,{EPOLLIN,{u32=28,u64=28}})=0epoll_ctl(10,EPOLL_CTL_MOD,28,{EPOLLIN,{u32=28,u64=3302604828697427996}})=0worker线程(10035,第五个连接,不在clone线程,而是复用了第一个epoll对应的worker)
epoll_ctl(13,EPOLL_CTL_ADD,29,{EPOLLIN,{u32=29,u64=29}})=0epoll_ctl(13,EPOLL_CTL_MOD,29,{EPOLLIN,{u32=29,u64=29}})=0概括为:
epoll和boss、worker之间的关系:一共有4个worker对应着4个epoll对象,boss和每个worker都有对应自己的epoll。
boss根据epoll数量,平衡分配连接到每个worker对应的epoll中。
7总结
下图通过对系统调用的调查得出netty和kernel交互图:
初始化直接创建5个epoll,其中7号为boss使用,专门用于处理和客户端连接;其余4个用来给worker使用,用户处理和客户端的数据交互。
work的线程数量,取决于初始化时创建了几个epoll,worker的复用本质上是epoll的复用。
work之间为什么要独立使用epoll?为什么不共享?
为了避免各个worker之间发生争抢连接处理,netty直接做了物理隔离,避免竞争。各个worker只负责处理自己管理的连接,并且后续该worker中的每个client的读写操作完全由该线程单独处理,天然避免了资源竞争,避免了锁。
worker单线程,性能考虑:worker不仅仅要epoll_wait,还是处理read、write逻辑,加入worker处理了过多的连接,势必造成这部分消耗时间片过多,来不及处理更多连接,性能下降。
8优缺点
优点
数据处理:netty提供了大量成熟的数据处理组件(ENCODER、DECODER),HTTP、POP3拿来即用。
编码复杂度、可维护性:netty充分使得业务逻辑与网络处理解耦,只需要少量的BootStrap配置即可,更多的集中在业务逻辑处理上。
性能:netty提供了的ByteBuf(底层Java原生的ByteBuffer),提供了池化的ByteBuf,兼顾读取性能和ByteBuf内存分配(在后续文档中会再做详解)。
缺点
入门有一定难度。
五AIO1启动
main线程
epoll_create(256)=5epoll_ctl(5,EPOLL_CTL_ADD,6,{EPOLLIN,{u32=6,u64=184482566}})=0创建BOSS线程(Proactor)clone(child_stack=0x7f340ac06fb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7f340ac079d0,tls=0x7f340ac07700,child_tidptr=0x7f340ac079d0)=22704socket(AF_INET6,SOCK_STREAM,IPPROTO_IP)=8setsockopt(8,SOL_IPV6,IPV6_V6ONLY,[0],4)=0setsockopt(8,SOL_SOCKET,SO_REUSEADDR,[1],4)=0bind(8,{sa_family=AF_INET6,sin6_port=htons(9090),inet_pton(AF_INET6,"::",sin6_addr),sin6_flowinfo=0,sin6_scope_id=0},28)=0listen(8,50)accept(8,0x7f67d01b3120,0x7f67d9246690)=-1epoll_ctl(5,EPOLL_CTL_MOD,8,{EPOLLIN|EPOLLONESHOT,{u32=8,u64=025362440}})=-1ENOENT(Nosuchfileordirectory)epoll_ctl(5,EPOLL_CTL_ADD,8,{EPOLLIN|EPOLLONESHOT,{u32=8,u64=025362440}})=0read(0,22704(BOSS线程(Proactor))
epoll_wait(5,unfinished
2请求连接
**22704(BOSS线程(Proactor))处理连接**epoll_wait(5,[{EPOLLIN,{u32=9,u64=4294967305}}],512,-1)=1accept(8,{sa_family=AF_INET6,sin6_port=htons(55320),inet_pton(AF_INET6,"::ffff:36.24.32.140",sin6_addr),sin6_flowinfo=0,sin6_scope_id=0},[28])=9clone(child_stack=0x7ff35c99ffb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7ff35c9a09d0,tls=0x7ff35c9a0700,child_tidptr=0x7ff35c9a09d0)=26241epoll_wait(5,unfinished26241
#数据处理交给其他线程,这里由于线程池为空,需要先clone线程clone(child_stack=0x7ff35c99ffb0,flags=CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGHAND|CLONE_THREAD|CLONE_SYSVSEM|CLONE_SETTLS|CLONE_PARENT_SETTID|CLONE_CHILD_CLEARTID,parent_tidptr=0x7ff35c9a09d0,tls=0x7ff35c9a0700,child_tidptr=0x7ff35c9a09d0)=26532
复制线程处理,线程号26532
write(1,"pool-1-thread-2-10received:dao",41)=41write(1,"\n",1)accept(8,0x7f11c400b5f0,0x7f11f42fd4d0)=-1EAGAIN(Resourcetemporarilyunavailable)epoll_ctl(5,EPOLL_CTL_MOD,8,{EPOLLIN|EPOLLONESHOT,{u32=8,u64=8}})=04总结
从系统调用角度,Java的AIO事实上是以多路复用(Linux上为epoll)等同步IO为基础,自行实现了异步事件分发。
BOSSThread负责处理连接,并分发事件。
WORKERThread只负责从BOSS接收的事件执行,不负责任何网络事件监听。
5优缺点
优点
相比于前面的BIO、NIO,AIO已经封装好了任务调度,使用时只需关心任务处理。
缺点
事件处理完全由ThreadPool完成,对于同一个channel的多个事件可能会出现并发问题。
相比netty,bufferAPI不友好容易出错;编解码工作复杂。
作者|道坚
本文为阿里云原创内容,未经允许不得转载。