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 是负责非堵塞行为,类似于AWT ActionListeners;同时负责将handlers与event事件绑定,类似于AWT addActionListener如图:
Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。代码如下:
public class Reactor implements Runnable{ final Selector selector;
final ServerSocketChannel serverSocket; Reactor(int port) throws IOException { selector = Selector.open();
serverSocket = ServerSocketChannel.open(); InetSocketAddress address = new
InetSocketAddress(InetAddress.getLocalHost(),port); serverSocket.socket().bind(address);
serverSocket.configureBlocking(false); //向selector注册该channel
SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);
logger.debug(\
//利用sk的attache功能绑定Acceptor 如果有事情,触发Acceptor
sk.attach(new Acceptor());
logger.debug(\ }
public void run() { // normally in a new Thread try {
while (!Thread.interrupted()) {
selector.select();
Set selected = selector.selectedKeys(); Iterator it = selected.iterator();
//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while (it.hasNext())
//来一个事件 第一次触发一个accepter线程 //以后触发SocketReadHandler dispatch((SelectionKey)(it.next())); selected.clear(); }
}catch (IOException ex) {
logger.debug(\ } }
//运行Acceptor或SocketReadHandler void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment()); if (r != null){ // r.run(); } } }
以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。再看看Handler代码:
public class SocketReadHandler implements Runnable {
public static Logger logger = Logger.getLogger(SocketReadHandler.class); private Test test=new Test(); final SocketChannel socket; final SelectionKey sk;
static final int READING = 0, SENDING = 1; int state = READING;
public SocketReadHandler(Selector sel, SocketChannel c) throws IOException { socket = c;
socket.configureBlocking(false); sk = socket.register(sel, 0);
//将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
//参看dispatch(SelectionKey k) sk.attach(this);
//同时将SelectionKey标记为可读,以便读取。 sk.interestOps(SelectionKey.OP_READ); sel.wakeup(); }
public void run() { try{
// test.read(socket,input); readRequest() ; }catch(Exception ex){
logger.debug(\ } }
在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_Handler对象从事件
驱动列表中删除,因此reactor类还实现了remove_handler函数。reactor类内部提供一个事件循环:handle_events(),事件循环的代码实现利用了操作系统提供的多路分离函数,WaitForMultipleObjects或者select等,这些多路分离的函数的特点是,可以同时等待多个句柄,在等待过程中所在线程属于挂起状态,不消耗CPU时间,一旦某个句柄被触发,则线程被唤醒,函数将返回,线程可以执行后面的代码,利用多路分离函数的这一特点,根据被激活的句柄对应的特定事件,调用相关的事件处理函数。可以实现事件循环。