




已阅读5页,还剩33页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
Windows完成端口 Linux EPOLL 解析 目录 1. Windows完成端口介绍 2. Linux EPOLL介绍 1. Windows完成端口 同步I/O与异步I/O 说起完成端口,它的实现机制其实是重叠 I/O实现异步I/O操作,下面就结合同步I/O来 解释下什么是异步I/O。 同步I/O 首先我们来看下同步I/O操作,同步I/O操作就是对于同一个I/O对 象句柄在同一时刻只允许一个I/O操作,原理图如下: 由图可知,内核开始处理I/O操作到结束的时间段是T2T3,这个时间 段中用户线程一直处于等待状态,如果这个时间段比较短,则不会有 什么问题,但是如果时间比较长,那么这段时间线程会一直处于挂起 状态,这就会很严重影响效率,所以我们可以考虑在这段时间做些事 情。 异步I/O 异步I/O操作则很好的解决了这个问题,它可以使得内核开始处理 I/O操作到结束的这段时间,让用户线程可以去做其他事情,从而提 高了使用效率。 由图可知,内核开始I/O操作到I/O结束这段时间,用户层可以做其他的操作, 然后,当内核I/O结束的时候,可以让I/O对象或者时间对象通知用户层,而用 户线程GetOverlappedResult来查看内核I/O的完成情况。 异步I/O操作有很多种实现方法,具体分为 以下三类: 1. 重叠I/O操作 2. 异步过程调用(APC),扩展I/O 3. 使用完成端口(IOCP) 下面我们来介绍下这三种实现方法: 使用重叠I/O实现异步I/O 重叠的意思,从资料上查询可以理解为同一个线程对多个I/O对象进行I/O操作,不同的线程也可以 对同一个I/O对象进行操作。 在使用重叠I/O时,线程需要创OVERLAPPED结构以供I/O处理。 OVERLAPPED的结构定义如下: typedef struct _OVERLAPPED ULONG_PTR Internal; /被系统内部赋值,用来表示系统状态 ULONG_PTR InternalHigh; /被系统内部赋值,表示传输的字节数 union struct DWORD Offset; /与OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开 始操作 DWORD OffsetHigh; /如果不是对文件I/O来操作,则Offset必须设定为0 ; PVOID Pointer; ; HANDLE hEvent; /如果不使用,就务必设为0;否则请赋一个有效的Event句柄 OVERLAPPED, *LPOVERLAPPED; 该结构中最重要的成员是hEvent,它是作为一个同步对象而存在,如果hEvent为NULL,那么此时的 同步对象即为文件句柄、管道句柄等I/O操作对象。当I/O完成后,会使这里的同步对象受信,从而 通知用户线程。 由于在进行I/O请求后会立即返回,但有时用户线程需要知道I/O当前的执行情况,此时就可以使用 GetOverlappedResult。如果该函数的bWait参数为true,那么改函数就会阻塞线程直到目标I/O处理完 成为止;如果bWait为false,那么就会立即返回,如果此时的I/O尚未完,调用GetLastError就会返回 ERROR_IO_INCOMPLETE。 下面给出两个示例代码,方便大家理解 DWORD nReadByte ; BYTE bBufBUF_SIZE ; OVERLAPPED ov = 0, 0, 0, 0, NULL ; / hEvent = NULL ; HANDLE hFile = CreateFile ( , FILE_FLAG_OVERLAPPED, ) ; ReadFile ( hFile, bBuf, sizeof(bBuf), / 由于此时hEvent=NULL,所以同步对象为hFile,下面两句的效果一样 WaitForSingleObject ( hFile, INFINITE ) ; /GetOverlappedResult ( hFile, 这段代码在调用ReadFile后会立即返回,但在随后的 WaitForSingleObject或者GetOverlappedResult中阻塞,利用同步对象 hFile进行同步。 这段代码在这里可以实现正常的异步I/O,但存在一个问题,倘若现 在需要对hFile句柄进行多个I/O操作,就会出现问题。 DWORD nReadByte ; BYTE bBuf1BUF_SIZE,bBuf2BUF_SIZE,bBuf3BUF_SIZE ; OVERLAPPED ov1 = 0, 0, 0, 0, NULL ; OVERLAPPED ov2 = 0, 0, 0, 0, NULL ; OVERLAPPED ov3 = 0, 0, 0, 0, NULL ; HANDLE hFile = CreateFile ( , FILE_FLAG_OVERLAPPED, ) ; ReadFile ( hFile, bBuf1, sizeof(bBuf1), ReadFile ( hFile, bBuf2, sizeof(bBuf2), ReadFile ( hFile, bBuf3, sizeof(bBuf3), /假设三个I/O处理的时间比较长,到这里还没有结束 GetOverlappedResult ( hFile, 这里对于hFile有三个重叠的I/O操作,但他们的同步对象却 都为hFile。使用GetOverlappedResult进行等待操作,这里 看似在等待第一个I/O处理的完成,其实只要有任何一个 I/O处理完成,该函数就会返回,相当于忽略了其他两个 I/O操作的结果。 其实,这里有一个很重要的原则:对于一个重叠句柄上有多于 一个I/O操作的时候,应该使用事件对象而不是文件句柄来实现 同步。 DWORD nReadByte ; BYTE bBuf1BUF_SIZE,bBuf2BUF_SIZE,bBuf3BUF_SIZE ; HANDLE hEvent1 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; HANDLE hEvent2 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; HANDLE hEvent3 = CreateEvent ( NULL, FALSE, FALSE, NULL ) ; OVERLAPPED ov1 = 0, 0, 0, 0, hEvent1 ; OVERLAPPED ov2 = 0, 0, 0, 0, hEvent2 ; OVERLAPPED ov3 = 0, 0, 0, 0, hEvent3 ; HANDLE hFile = CreateFile ( , FILE_FLAG_OVERLAPPED, ) ; ReadFile ( hFile, bBuf1, sizeof(bBuf1), ReadFile ( hFile, bBuf2, sizeof(bBuf2), ReadFile ( hFile, bBuf3, sizeof(bBuf3), /此时3个I/O操作的同步对象分别为hEvent1,hEvent2,hEvent3 GetOverlappedResult ( hFile, 这样,这个GetOverlappedResult就可以实现对第一个I/O处理的 等待 异步过程调用实现异步I/O操作 异步过程调用(APC),即在特定的上下文中异步的执行一个调用。 在异步I/O中可以使用APC,即让操作系统的IO系统在完成异步I/O后立 即调用你的程序。 这里需要注意三点: (1) APC总是在调用线程中被调用; (2) 当执行APC时,调用线程会进入可变等待状态; (3) 线程需要使用扩展I/O系列函数,例如 ReadFileEx,WriteFileEx, 另外可变等待函数也是必须的(至少下面其中 之一): WaitForSingleObjectEx WaitForMultipleObjectEx SleepEx SignalObjectAndWait MsgWaitForMultipleObjectsEx 在使用ReadFileEx,WriteFileEx时,重叠结构OVERLAPPED中的hEvent成 员并非一定要指定,因为系统会忽略它。当多个IO操作共用同一个完 成例程时,可以使用hEvent来携带序号等信息,用于区别不同的I/O操 作,因为该重叠结构会传递给完成例程。如果多个IO操作使用的完成 例程都不相同时,则直接把hEvent设置为NULL就可以了。 在系统调用完成例程有两个条件: (1) I/O操作必须完成 (2) 调用线程处于可变等待状态 对于第一个条件比较容易,显然完成例程只有在I/O 操作完成时才调用;至于第二个条件就需要进行认 为的控制,通过使用可变等待函数,让调用线程处 于可变等待状态,这样就可以执行完成例程了。这 里可以通过调节调用可变等待函数的时机来控制完 成例程的执行,即可以确保完成例程不会被过早的 执行。 当线程具有多个完成例程时,就会形成一个队列。 使用可变等待函数使线程进入可变等待状态时有一 个表示超时值的参数,如果使用INFINITE,那么只 有所有排队的完成例程被执行或者句柄获得信号时 该等待函数才返回。 完成端口 n WindowsSockets应 用程序在调用 WSARecv()函数后立即 返回,线程继续运行。 当系统接收数据完成 后,向完成端口发送 通知包(这个过程对 应用程序不可见)。 n应用程序在发起接收数据操作后,在完成端口上等 待操作结果。当接收到I/O操作完成的通知后,应用 程序对数据进行处理。 完成端口其实就是上面两项的联合使用基础上进行了一定的改 进。 一个完成端口其实就是一个通知队列,由操作系统把已经完成 的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某 个可以对该操作结果进行处理的工作者线程就会收到一则通 知。而套接字在被创建后,可以在任何时候与某个完成端口进 行关联。 通常情况下,我们会在应用程序中创建一定数量的工作者线程 来处理这些通知。线程数量取决于应用程序的特定需要。理想 的情况是,线程数量等于处理器的数量,不过这也要求任何线 程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作 ,以免线程阻塞。每个线程都将分到一定的CPU时间,在此期 间该线程可以运行,然后另一个线程将分到一个时间片并开始 执行。如果某个线程执行了阻塞型的操作,操作系统将剥夺其 未使用的剩余时间片并让其它线程开始执行。也就是说,前一 个线程没有充分使用其时间片,当发生这样的情况时,应用程 序应该准备其它线程来充分利用这些时间片。 完成端口 完成端口的特点 Win32重叠I/O(Overlapped I/O)机制允许发起一个操作,并 在操作完成之后接收信息。对于那种需要很长时间才能完 成的操作来说,重叠IO机制尤其有用,因为发起重叠操作 的线程在重 叠请求发出后就可以自由地做别的事情了。 在WinNT和Win2000上,提供的真正可扩展的I/O模型就是 使用完成端口(Completion Port)的重叠I/O。 完成端口-是一种WINDOWS内核对象。完成端口用于异 步方式的重叠I/0情况下,当然重叠I/O不一定非得使用完 成端口不可,同样设备内核对象、事件对象、告警I/0等也 可使用。但是完成端口内部提供了线程池的管理,可以避 免反复创建线程的开销,同时可以根据CPU的个数灵活地 决定线程个数,而且可以减少线程调度的次数从而提高性 能。其实类似于WSAAsyncSelect和select函数的机制更容易 兼容Unix,但是难以实现我们想要的“扩展性”。而且 windows完成端口机制在操作系统的内部已经作了优化, 从而具备了更高的效率。 1)发起操作不一定完成:系统会在完成的时候通知你, 通过用户在完成端口上的等待,处理操作的结果。所以要 有检查完成端口和取操作结果的线程。在完成端口 上守 候的线程系统有优化,除非在执行的线程发生阻塞,不会 有新的线程被激活,以此来减少线程切换造成的性能代 价。所以如果程序中没有太多的阻塞操作,就没 有必要 启动太多的线程,使用CPU数量的两倍,一般这么多线程 就够了。 2)操作与相关数据的绑定方式:在提交数据的时候用户 对数据打上相应的标记,记录操作的类型,在用户处理操 作结果的时候,通过检查自己打的标记和系统的操作结果 进行相应的处理。 3)操作返回的方式:一般操作完成后要通知程序进行后 续处理。但写操作可以不通知用户,此时如果用户写操作 不能马上完成,写操作的相关数据会被暂存到非交 换缓 冲区中,在操作完成的时候,系统会自动释放缓冲区,此 时发起完写操作,使用的内存就可以释放了。但如果占用 非交换缓冲太多会使系统停止响应。 完成端口的使用 首先创建完成端口。 完成端口是一个内核对象,使用时它总是要和至少一个有效的设备句柄相关 联。 HANDLE CreateIoCompletionPort( IN HANDLE FileHandle, IN HANDLE ExistingCompletionPort, IN ULONG_PTR CompletionKey, IN DWORD NumberOfConcurrentThreads ); 下面就例子介绍下使用方式: 首先创建完成端口: HANDLE hIocp; hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)0, 0); if (hIocp = NULL) / Error 完成端口创建后,要把将使用该完成端口的套接字与之关联起来。方法是再 次调用CreateIoCompletionPort ()函数,第一个参数FileHandle设为套接字的句 柄,第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句 柄。 以下代码创建了一个套接字,并把它和前面创建的完成端口关联起来: SOCKET s; s = socket(AF_INET, SOCK_STREAM, 0); if (s = INVALID_SOCKET) / Error if (CreateIoCompletionPort(HANDLE)s, hIocp, (ULONG_PTR)0, 0) = NULL) / Error 这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重 叠操作都将通过完成端口发出完成通知。注意,CreateIoCompletionPort()函数 中的第三个参数用来设置一个与该套接字相关的“完成键(completion key)” 每当完成通知到来时,应用程序可以读取相应的完成键,因此,完成键可用 来给套接字传递一些背景信息。因此可以设置个结构体,将该值至于其中, 将应用程序所需要的信息写在结构体中,使用的时候,直接将结构体中的 completion key传给CreateIoCompletionPort(),就可以了。 在创建了完成端口、将一个或多个套接字与之相 关联之后,我们就要创建若干个线程来处理完成 通知。这些线程不断循环调用 GetQueuedCompletionStatus()函数并返回完成通 知。 下面,我们先来看看应用程序如何跟踪这些重叠 操作。当应用程序调用一个重叠操作函数时,要 把指向一个overlapped结构的指针包括在其参数 中。当操作完成后,我们可以通过 GetQueuedCompletionStatus()函数中拿回这个指 针。不过,单是根据这个指针所指向的overlapped 结构,应用程序并不能分辨究竟完成的是哪个操 作。要实现对操作的跟踪,你可以自己定义一个 OVERLAPPED结构,在其中加入所需的跟踪信息。 无论何时调用重叠操作函数时,总是会通过其 lpOverlapped参数传递一个OVERLAPPEDPLUS结构(例如 WSASend、WSARecv等函数)。这就允许你为每一个重叠调 用操作设置某些操作状态信息,当操作结束后,你可以通 过GetQueuedCompletionStatus()函数获得你自定义结构的 指针。注意OVERLAPPED字段不要求一定是这个扩展后的 结构的第一个字段。当得到了指向OVERLAPPED结构的指 针以后,可以用CONTAINING_RECORD宏取出其中指向扩展 结构的指针。 下面举个例子: OVERLAPPED 结构的定义如下: typedef struct _OVERLAPPEDPLUS OVERLAPPED ol; SOCKET s, sclient; int OpCode; WSABUF wbuf; DWORD dwBytes, dwFlags; / other useful information OVERLAPPEDPLUS; 下面对比重叠I/O的获取操作和完成端口的进行说明: 重叠I/O BOOL GetOverlappedResult ( HANDLE hFile, LPOVERLAPPED lpOverlapped, /接受返回的重叠I/0结构 LPDWORD lpcbTransfer, /成功传输了多少字节数 BOOL fWait /TRUE只有当操作完成才返回,FALSE直接返回,如果操 作没有完成, /通过用GetLastError( )函数会返回ERROR_IO_INCOMPLETE ); 而宏HasOverlappedIoCompleted可以帮助我们测试重叠I/0操作是否完 成,该宏对OVERLAPPED结构的Internal成员进行了测试,查看是否等 于STATUS_PENDING值。 一般来说,一个应用程序可以创建多个工作线程来处理完成端口上的 通知事件。工 作线程的数量依赖于程序的具体需要。但是在理想的 情况下,应该对应一个CPU 创建一个线程。因为在完成端口理想模型 中,每个线程都可以从系统获得一个“原子”性的时间片,轮番运行并 检查完成端口,线程的切换是额外的开销。但在实际 开发的时候, 还要考虑这些线程是否牵涉到其他堵塞操作的情况。如果某线程进行 堵塞操作,系统则将其挂起,让别的线程获得运行时间。因此,如果 有这样的情 况,可以多创建几个线程来尽量利用时间。 完成端口 BOOL GetQueuedCompletionStatus( IN HANDLE CompletionPort, OUT LPDWORD lpNumberOfBytesTransferred, OUT PULONG_PTR lpCompletionKey, OUT LPOVERLAPPED *lpOverlapped, IN DWORD dwMilliseconds ); 这个函数试图从指定的完成端口的I/0完成队列中提取纪录。只有当重叠I/O动作完成的 时候,完成队列中才有纪录。凡是调用这个函数的线程将会被放入到完成端口的等待 线程队列中,因此完成端口就可以在自己的线程池中帮助我们维护这个线程。完成端 口的I/0完成队列中存放了当重叠I/0完成的结果- 一条纪录,该纪录拥有四个字段,前 三项就对应GetQueuedCompletionStatus函数的2、3、4参数,最后一个字段是错误信息 dwError。我们也可以通过调用PostQueudCompletionStatus模拟完成一个重叠I/0操作。 当I/0完成队列中出现了纪录,完成端口将会检查等待线程队列,该队列中的线程都是 通过调用GetQueuedCompletionStatus函数使自己加入队列的。等待线程队列很简单, 只是保存了这些线程的ID。完成端口按照后进先出的原则将一个线程队列的ID放入到释 放线程列表中,同时该线程将从等待GetQueuedCompletionStatus函数返回的睡眠状态 中变为可调度状态等待CPU的调度。所以我们的线程要想成为完成端口管理的线程,就 必须要调用GetQueuedCompletionStatus函数。出于性能的优化,实际上完成端口还维 护了一个暂停线程列表。 线程的数据传输 完成端口线程间传递数据最常用的办法是在_beginthreadex函数中将 参数传递给线程函数,或者使用全局变量。但完成端口也有自己的传 递数据的方法,答案就在于CompletionKey和OVERLAPPED参数。 CompletionKey 被保存在完成端口的设备表中,是和设备句柄一一对 应的,我们可以将与设备句柄相关的数据保存到CompletionKey中,或 者将 CompletionKey表示为结构指针,这样就可以传递更加丰富的内 容。这些内容只能在一开始关联完成端口和设备句柄的时候做,因此 不能在以后动态改变。 OVERLAPPED参数是在每次调用ReadFile这样的支持重叠I/0的函数时传 递给完成端口的。 我们可以看到,如果我们不是对文件设备做操作 ,该结构的成员变量就对我们几乎毫无作用。我们需要附加信息,可 以创建自己的结构,然后将 OVERLAPPED结构变量作为我们结构变量 的第一个成员,然后传递第一个成员变量的地址给ReadFile这样的函 数。因为类型匹配,当然可以通过编 译。当 GetQueuedCompletionStatus函数返回时,我们可以获取到第一个成员 变量的地址,然后一个简单的强制转换,我们就可以把它当 作完整 的自定义结构的指针使用,这样就可以传递很多附加的数据了。太好 了!只有一点要注意,如果跨线程传递,请注意将数据分配到堆上, 并且接收端应该将数 据用完后释放。我们通常需要将ReadFile这样的 异步函数的所需要的缓冲区放到我们自定义的结构中,这样当 GetQueuedCompletionStatus被返回时,我们的自定义结构的缓冲区变 量中就存放了I/0操作的数据。CompletionKey和OVERLAPPED参数,都 可以通过GetQueuedCompletionStatus函数获得。 线程的退出 很多线程为了不止一次地执行异步数据处理,需要 使用如下语句 while (true) GetQueuedCompletionStatus(.); 那么线程如何退出呢,答案就在于上面曾提到过的 PostQueudCompletionStatus函数,我们可以向它发 送一个自定义的包含了OVERLAPPED成员变量的结构 地址,里面含一个状态变量,当状态变量为退出标 志时,线程就执行清除动作然后退出。 2.Linux下的EPOLL epoll是Linux内核为处理大批量句柄而作了改进 的poll,是Linux下多路复用IO接口select/poll的 增强版本,它能显著减少程序在大量并发连接 中只有少量活跃的情况下的系统CPU利用率。 因为它不会复用文件描述符集合来传递结果而 迫使开发者每次等待事件之前都必须重新准备 要被侦听的文件描述符集合,另一点原因就是 获取事件的时候,它无须遍历整个被侦听的描 述符集,只要遍历那些被内核IO事件异步唤醒 而加入Ready队列的描述符集合就行了。 EPOLL的优点 支持一个进程打开大数目的socket描述符(FD) epoll 没有传统select/poll的“一个进程所打开的FD是有一定限制”的这个限制,它所支持的 FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048。举个例子, 在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说 这个数目和系统内存关系很大。 IO效率不随FD数目增加而线性下降 传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使 得任一时间只有部分的socket是“活跃“的,而select/poll每次调用都会线性扫描全部的集合, 导致效率呈现线性下降。但是epoll不存在这个问题,它只会对“活跃“的socket进行操作-这是 因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有“活跃“的socket 才会主动去调用callback函数,其他idle状态的socket则不会,在这点上,epoll实现了一个“伪 “AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃 的-比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl ,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在 select/poll之上了。 使用mmap加速内核与用户空间的消息传递 这点实际上涉及到epoll的具体实现。无论是select,poll还是epoll都需要内核把FD消息通知给用 户空间,如何避免不必要的内存拷贝就显得很重要,在这点上,epoll是通过内核于用户空间 mmap同一块内存实现的。而如果你像我一样从2.5内核就开始关注epoll的话,一定不会忘记 手工mmap这一步的。 内核微调 这一点其实不算epoll的优点,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你 无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff 结构,可以在运行期间动态地调整这个内存pool(skb_head_pool)的大小-通过echo XXXX/proc/sys/net/core/hot_list_length来完成。再比如listen函数的第2个参数(TCP完成3次握 手的数据包队列长度),也可以根据你平台内存大小来动态调整。甚至可以在一个数据包面数 目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。 EPOLL工作模式 LT:水平触发 是缺省的工作方式,并且同时支持block和no- block socket.在这种做法中,内核告诉你一个 文件描述符是否就绪了,然后你可以对这个就 绪的fd进行IO操作。如果你不作任何操作,内 核还是会继续通知你 的,所以,这种模式编 程出错误可能性要小一点。传统的select/poll 都是这种模型的代表。 效率会低于ET触发,尤其在大并发,大流 量的情况下。但是LT对代码编写要求比较低, 不容易出现问题。LT模式服务编写上的表现是 :只要有数据没有被获取,内核就不断通知你 ,因此不用担心事件丢失的情况。 ET:边缘触发 是高速工作方式,只支持no-block socket。在这种模式 下,当描述符从未就绪变为就绪时,内核通过epoll告诉 你。然后它会假设你知道文件描述符已经就绪,并且不 会再为那个文件描述 符发送更多的就绪通知,直到你 做了某些操作导致那个文件描述符不再为就绪状态了( 比如,你在发送,接收或者接收请求,或者发送接收的 数据少于一定量时导致 了一个EWOULDBLOCK 错误)。 但是请注意,如果一直不对这个fd作IO操作(从而导致它 再次变成未就绪),内核不会发送更多的通知(only once) 。 效率非常高,在并发,大流量的情况下,会比LT少很多 epoll的系统调用,因此效率高。但是对编程要求高,需 要细致的处理每个请求,否则容易发生丢失事件的情 况。 在许多测试中我们会看到如果没有大量的idle -connection 或者dead-connection,epoll的效率并不会比select/poll高 很多,但是当我们遇到大量的idle- connection(例如WAN 环境中存在大量的慢速连接),就会发现epoll的效率大 大高于select/poll。 EPOLL事件有两种模型: Edge Triggered (ET) Level Triggered (LT) 假如有这样一个例子: 1. 我们已经把一个用来从管道中读取数据的文 件句柄(RFD)添加到epoll描述符 2. 这个时候从管道的另一端被写入了2KB的数 据 3. 调用epoll_wait(2),并且它会返回RFD,说明 它已经准备好读取操作 4. 然后我们读取了1KB的数据 5. 调用epoll_wait(2) Edge Triggered 工作模式: 如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标 志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩 余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等 待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上 发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步 的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的 剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因 为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。 因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我 们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll 工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个 文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿 死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避 免可能的缺陷。 i 基于非阻塞文件句柄 ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但 这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN 才认为此次事件处理完成,当read()返回的读到的数据长度小于请 求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可 以认为此事读事件已处理完成。 Level Triggered 工作模式 相反的,以LT方式调用epoll接口的时候,它 就相当于一个速度比较快的poll(2),并且无 论后面的数据是否被使用,因此他们具有 同样的职能。因为即使使用ET模式的epoll ,在收到多个chunk的数据的时候仍然会产 生多个事件。调用者可以设定 EPOLLONESHOT标志,在 epoll_wait(2)收到 事件后epoll会与事件关联的文件句柄从 epoll描述符中禁止掉。因此当 EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件 句柄就成为调用者必须作的事情。 另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后, 读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那 么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完 ,所以还需要再次读取: while(rs) buflen = recv(activeeventsi.data.fd, buf, sizeof(buf), 0); if(buflen 0) / 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数 据可读 / 在这里就当作是该次事件已处理处. if(errno = EAGAIN) break; else return; else if(buflen = 0) / 这里表示对端的socket已正常关闭. if(buflen = sizeof(buf) rs = 1; / 需要再次读取 else rs = 0; 还有,假如发送端流量大于接收端的流量(意 思是epoll所在的程序读比转发的socket要快),由 于是非阻塞的socket,那么send()函数虽然返回, 但实际缓冲区的数据并未真正发给接收端,这 样不断的读和发,当缓冲区满后会产生EAGAIN 错误(参考man send),同时,不理会这次请求发 送的数据.所以,需要封装socket_send()的函数 用来处理这种情况,该函数会尽量将数据写完 再返回,返回-1表示出错。在socket_send()内 部,当写缓冲已满(send()返回-1,且errno为 EAGAIN),那么会等待后再重试.这种方式并不 很完美,在理论上可能会长时间的阻塞在 socket_send()内部,但暂没有更好的办法. EPOLL的实现函数介绍: int epoll_create(int size); 创建一个epoll的句柄,size用来告诉内核这 个监听的数目一共有多大。这个参数不同 于select()中的第一个参数,给出最大监听 的fd+1的 值。需要注意的是,当创建好 epoll句柄后,它就是会占用一个fd值,在 linux下如果查看/proc/进程id/fd/,是能够看 到这个fd的,所 以在使用完epoll后,必须 调用close()关闭,否则可能导致fd被耗尽。 int epoll_ctl(int epfd, int op, int fd, struct epoll_even
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 从结绳计数说起(教学设计)-2024-2025学年北师大版数学四年级上册
- 九年级化学下册 7.4 结晶现象教学设计2 (新版)粤教版
- 中学生如何控烟-山东省聊城市高唐县第二实验中学人教部编版七年级道德与法治下册教学设计
- 人教部编版九年级道德与法治上册7.1《促进民族团结》教学设计
- 初中语文人教部编版 (五四制)六年级下册(2018)石灰吟教案及反思
- 二年级品德与社会下册 2 让家园更美好 我们的大地妈妈第2课时教学设计 新人教版
- 大客户销售技巧培训课件
- 超市实物盘点培训
- 岗前培训心得汇报
- 采购管理培训资料
- 2025年驾驶员执照考试科目一复习题库及答案(共250题)
- CCD 上海国际油轮港康得思酒店软装方案册
- 2025-2030年中国玄武岩纤维行业未来发展趋势及投资战略研究报告
- 反铲液压挖掘机 课件 第 5 章 回转平台、回转支承及回转驱动装置
- 园林水电培训课件
- 天津医科大学眼科医院招聘真题
- T-GDCKCJH 090-2024 微生物电化学法水质生物毒性在线自动监测技术规范
- 2024年度武汉大学博士后研究人员聘用合同3篇
- 2024配电网行波故障测距功能规范
- 高二物理学考复习计划
- 如何做好护理质控管理
评论
0/150
提交评论