您好,欢迎访问三七文档
当前位置:首页 > IT计算机/网络 > 其它相关文档 > 互联网络程序设计实验-实验4
实验四一、实验目的封装epoll,熟悉epoll的主要操作。按照Reactor模式构建io引擎。二、实验原理1.Epoll封装epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(LevelTriggered)外,还提供了边缘触发(EdgeTriggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。epoll有2种工作方式:LT和ET。LT(leveltriggered)是缺省的工作方式,并且同时支持block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。ET(edge-triggered)是高速工作方式,只支持no-blocksocket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(onlyonce),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。ET和LT的区别就在这里体现,LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你。而ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。2.Reactor模式在Reactor模式中,有5个关键的参与者:描述符(handle):由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。同步事件分离器(demultiplexer):是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,直到分离器分离的描述符集上有事件发生。Linux的select函数是一个经常被使用的分离器。事件处理器接口(eventhandler):是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。具体的事件处理器:是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。Reactor管理器(reactor):定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模板函数来处理这个事件。通过上述分析,我们注意到,是Reactor管理器而不是应用程序负责等待事件、分离事件和调度事件。实际上,Reactor管理器并没有被具体的事件处理器调用,而是管理器调度具体的事件处理器,由事件处理器对发生的事件做出处理。这就是类似Hollywood原则的“反向控制”。应用程序要做的仅仅是实现一个具体的事件处理器,然后把它注册到Reactor管理器中。接下来的工作由管理器来完成。Reactor构架模式图如下:三、实验数据Epoll的主要操作如下:1.首先需要创建线程,以及定义注册事件和用于回传处理的事件的数组等。2.因为epoll是一个文件,设置epoll的专用文件描述符,并且设置要处理的时间相关的文件描述符并且设置其epoll的工作模式。3.处理各种事件,包括注册事件、注销事件、修改事件和等待事件发生等。4.描述的事件,Epoll模型如下图所示:Epoll模型主要负责对大量并发用户的请求进行及时处理,完成服务器与客户端的数据交互。其具体的实现步骤如下:(a)使用epoll_create()函数创建文件描述,设定将可管理的最大socket描述符数目。(b)创建与epoll关联的接收线程,应用程序可以创建多个接收线程来处理epoll上的读通知事件,线程的数量依赖于程序的具体需要。(c)创建一个侦听socket描述符ListenSock;将该描述符设定为非阻塞模式,调用Listen()函数在套接字上侦听有无新的连接请求,在epoll_event结构中设置要处理的事件类型EPOLLIN,工作方式为epoll_ET,以提高工作效率,同时使用epoll_ctl()注册事件,最后启动网络监视线程。(d)网络监视线程启动循环,epoll_wait()等待epoll事件发生。(e)如果epoll事件表明有新的连接请求,则调用accept()函数,将用户socket描述符添加到epoll_data联合体,同时设定该描述符为非阻塞,并在epoll_event结构中设置要处理的事件类型为读和写,工作方式为epoll_ET.(f)如果epoll事件表明socket描述符上有数据可读,则将该socket描述符加入可读队列,通知接收线程读入数据,并将接收到的数据放入到接收数据的链表中,经逻辑处理后,将反馈的数据包放入到发送数据链表中,等待由发送线程发送。Reactor模式1.Reactor负责响应IO事件,一旦发生,广播发送给相应的Handler去处理,这类似于AWT的thread。2.Handler是负责非堵塞行为,类似于AWTActionListeners;同时负责将handlers与event事件绑定,类似于AWTaddActionListener如图:Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。代码如下:publicclassReactorimplementsRunnable{finalSelectorselector;finalServerSocketChannelserverSocket;Reactor(intport)throwsIOException{selector=Selector.open();serverSocket=ServerSocketChannel.open();InetSocketAddressaddress=newInetSocketAddress(InetAddress.getLocalHost(),port);serverSocket.socket().bind(address);serverSocket.configureBlocking(false);//向selector注册该channelSelectionKeysk=serverSocket.register(selector,SelectionKey.OP_ACCEPT);logger.debug(--StartserverSocket.register!);//利用sk的attache功能绑定Acceptor如果有事情,触发Acceptorsk.attach(newAcceptor());logger.debug(--attach(newAcceptor()!);}publicvoidrun(){//normallyinanewThreadtry{while(!Thread.interrupted()){selector.select();Setselected=selector.selectedKeys();Iteratorit=selected.iterator();//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。while(it.hasNext())//来一个事件第一次触发一个accepter线程//以后触发SocketReadHandlerdispatch((SelectionKey)(it.next()));selected.clear();}}catch(IOExceptionex){logger.debug(reactorstop!+ex);}}//运行Acceptor或SocketReadHandlervoiddispatch(SelectionKeyk){Runnabler=(Runnable)(k.attachment());if(r!=null){//r.run();}}}以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。再看看Handler代码:publicclassSocketReadHandlerimplementsRunnable{publicstaticLoggerlogger=Logger.getLogger(SocketReadHandler.class);privateTesttest=newTest();finalSocketChannelsocket;finalSelectionKeysk;staticfinalintREADING=0,SENDING=1;intstate=READING;publicSocketReadHandler(Selectorsel,SocketChannelc)throwsIOException{socket=c;socket.configureBlocking(false);sk=socket.register(sel,0);//将SelectionKey绑定为本Handler下一步有事件触发时,将调用本类的run方法。//参看dispatch(SelectionKeyk)sk.attach(this);//同时将SelectionKey标记为可读,以便读取。sk.interestOps(SelectionKey.OP_READ);sel.wakeup();}publicvoidrun(){try{//test.read(socket,input);readRequest();}catch(Exceptionex){logger.debug(readRequesterror+ex);}}在Handler里面又执行了一次attach,这样,覆盖前面的Acceptor,下次该Handler又有READ事件发生时,将直接触发Handler.从而开始了数据的读处理写发出等流程处理。四、实验心得及体会1.epoll的操作简单,总共不过4个API:epoll_create,epoll_ctl,epoll_wait和close。2.使用linux的epoll模式的水平触发需要向socket写数据的时候才把socket加入epoll,等待可写事件。接受到可写事件后,调用write或者send发送数据。当所有数据都写完后,把socket移出epoll。3.reactor类在做多路分离时需要操纵Event_Handler类的Handle,因此Event_Handler类需要提供get_handle()函数。4.程序不需要再对特定事件响应时,需要把Event
本文标题:互联网络程序设计实验-实验4
链接地址:https://www.777doc.com/doc-5942177 .html