作者:Frank
众所周知,I/O完成端口(IOCP)是目前性能最好的一种I/O模型。其大体的思路如下:在程序处理的过程中,阻塞类型的操作有很多,如(Socket Send/Recv), 磁盘读写,外部硬件接口(如打印机,扫描仪)等;以往的模型在处理阻塞事件时,为了提高程序在阻塞同时的并发性,经常使用多线程,这样就有很多可调度的线程并行在系统中,OS内核会花费大量的时间在线程的Context切换中,线程本身工作处理的时间会很少(可称工作饱和度很低),极端的情况下线程切换的时间甚至可能会大于本身线程的运行周期。
IOCP模型是windows提供的一种阻塞事件异步处理的模型,应用程序可以根据CPU的核数预先开N个线程,存储在线程池中。然后将所有的I/O请求都投递到一个完成端口上,与此同时N个工作线程逐一地从完成端口中读取完成的阻塞事件并加以处理,这样就极大程度的提高了线程的工作饱和度。
目前在性能上号称数倍优于Apache的服务器模型nginx,其实使用的也是这种机制,只是windows内部支持完成端口,而linux的支持为epool,所有的请求简化为阻塞操作和非阻塞操作,所有需要阻塞请求的部分全部由epool触发相应事件,非阻塞(处理耗时短)部分用主线程一直执行,直到遇到阻塞部分就停止,交由阻塞部分监听异步完成事件,这样就构成了nginx中的事件驱动模型。
平时讨论的比较多的是网络服务模型,即在server端使用IOCP管理socket链接,其实IOCP是可以应用于所有支持异步执行的阻塞操作中,下面介绍一个用来监测文件状态的实例:
应用场景之一:很多网盘应用提供同步功能,由于大家时常进行本地文件和网盘文件的upload/download操作,这样很不方便,设定同步功能可以自动将其关联,实现文件的实时更新,用户也体验大大提高。
代码使用VS2008实现,提供一个cFileUpdateChecker类,可以指定监测路径,每当其路径发生变化都会获取到通知事件,如copy, paste, create, remove, renaming, property modification等等,使用Windows API : ReadDirectoryChangesW的异步方式进行监测,所有请求有IOCP方式处理。
被观察文件的结构定义:
typedef struct directory_info { HANDLE hDir; CHAR szDirName[260]; CHAR szBuffer[4096]; DWORD dwBufLength; OVERLAPPED Overlapped; // 此处需要定义overlapp结构 }DIRECTORY_INFO, *LPDIRECTORY_INFO; #define DIRECTORY_PATH L"C://TestFolder" |
实现观察的具体类定义:
class cFileUpdateChecker { public: cFileUpdateChecker():hThread(NULL), dwThread(0), hComp(NULL), pdir(NULL) {} ~cFileUpdateChecker() { if (hThread) CloseHandle(hThread); if (hComp) CloseHandle(hComp); if (pdir) { PostQueuedCompletionStatus(hComp, 0, NULL, NULL); CloseHandle(pdir->hDir); HeapFree(GetProcessHeap(), 0, pdir); } }
// 定义IOCP的工作线程 static DWORD WINAPI ThreadFunc(void* p) { cFileUpdateChecker* pHost = (cFileUpdateChecker*)p;
LPDIRECTORY_INFO lpdir = NULL; DWORD nbytes; LPOVERLAPPED lpOverlapped = NULL; PFILE_NOTIFY_INFORMATION fni = NULL; DWORD wait = 1000;
while (true) { BOOL bRet = GetQueuedCompletionStatus(pHost->hComp, &nbytes, (PULONG_PTR)&lpdir, &lpOverlapped, wait); if ( FALSE == bRet && NULL == lpOverlapped && NULL != pHost->hComp ) { wait = INFINITE; continue; } else { if (lpdir) { fni = (PFILE_NOTIFY_INFORMATION)lpdir->szBuffer;
switch(fni->Action) { case FILE_ACTION_ADDED: //The file was added to the directory. break; case FILE_ACTION_REMOVED: //The file was removed from the directory. break; case FILE_ACTION_MODIFIED: //The file was modified. This can be a change in the time stamp or attributes. break; case FILE_ACTION_RENAMED_OLD_NAME: //The file was renamed and this is the old name. break; case FILE_ACTION_RENAMED_NEW_NAME: //The file was renamed and this is the new name. break; default: break; }
ZeroMemory(&(pHost->pdir->Overlapped), sizeof(OVERLAPPED)); ZeroMemory(pHost->pdir->szBuffer, 4096); ZeroMemory(pHost->pdir->szDirName, 260);
ReadDirectoryChangesW(pHost->pdir->hDir, pHost->pdir->szBuffer, sizeof(pHost->pdir->szBuffer), TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY | FILE_NOTIFY_CHANGE_LAST_WRITE, &(pHost->pdir->dwBufLength), (OVERLAPPED*)(&(pHost->pdir->Overlapped)), NULL ); } } }
return 0; }
bool StartChecker() { hComp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
pdir = (LPDIRECTORY_INFO)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DIRECTORY_INFO)); if (NULL == pdir) { return false; }
pdir->hDir = CreateFile(DIRECTORY_PATH, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL); if (pdir->hDir == INVALID_HANDLE_VALUE) { return false; }
// 启动工作线程 hThread = CreateThread(NULL, 0, ThreadFunc, this,0, &dwThread); if (NULL == hThread) { return false; }
// 此处投递IO请求的时候必须清零,开始因为没有清零总是收不到完成事件 ZeroMemory(&(pdir->Overlapped), sizeof(OVERLAPPED)); ZeroMemory(pdir->szBuffer, 4096); ZeroMemory(pdir->szDirName, MAX_PATH);
// 投递I/O请求到完成端口 HANDLE hRet = CreateIoCompletionPort(pdir->hDir, hComp, (ULONG_PTR)pdir, 0); if (NULL == hComp) { return false; }
BOOL bRet = ReadDirectoryChangesW(pdir->hDir, pdir->szBuffer, 4096, TRUE, FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_ACCESS | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY | FILE_NOTIFY_CHANGE_LAST_WRITE, &pdir->dwBufLength, &(pdir->Overlapped), NULL );
if (FALSE == bRet) { return false; } return true; }
private: HANDLE hThread; DWORD dwThread; HANDLE hComp; LPDIRECTORY_INFO pdir; }; |
参考资料:
1. MSDN 网站 - http://msdn.microsoft.com/en-us/library/aa365198%28VS.85%29.aspx