完成端口”模型是Window平台最复杂同时也是效率最高的的一种I/O模型,
在Socket服务器上得到了广泛的应用,“从本质上说,完成端口模型要求
我们创建一个Win32完成端口对象,通过指定数量的线程,对重叠I/O请求进行管理,
以便为已经完成的重叠I/O请求提供服务。”(引自《Window网络编程》)
笔者最近在做Socket服务器时,由于要对频繁发生的网络事件记录
日志,也就是文件操作,在做性能测试的时候,发现有可能在极短的时间
内涉及到大量的I/O文件操作,因此就想有没有什么比较好的方式来进行管理,
由于同步读写肯定不适合这种场景,因此考虑选择异步方式,关于文件的异步
操作常用的是重叠I/O(可参考笔者的
OVERLAP文章),在这种方式下,需要使用
针对每个OVERLAP结构进行GetOverlappedResult或者WaitForSingleObject,
从程序的结构而言控制起来要麻烦一些,因此自然而然的想起用IOCP来
替代一下,毕竟是“最强大”的嘛。:)
关于在Socket上使用IOCP,讨论的文章非常多,而且很多代码也开源了,
这里不多讨论,使用IOCP来关联文件操作的话,其实也差不多,不过有
几个地方要稍微注意一下,下面用代码来简单的说明一下,毕竟代码是最能
说明问题的。^_^。
//-------------------------------------------------------------------
首先介绍一个数据结构,具体里面的含义同大多数IOCP的应用一样,
在传递Overlapped可以带些user data过去
enum IOCode
{
IORead,
IOWrite
};
typedef struct
{
OVERLAPPED Overlapped;
CHAR Buf[DATA_BUFSIZE];
IOCode CmdCode ;
DWORD nNumberOfBytesToSend;
DWORD nNumberOfBytesSent;
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;
//-------------------------------------------------------------------
1。 初始化IOCP并用文件句柄关联:
HANDLE ThreadHandle;
DWORD ThreadID;
hFile = ::CreateFile(filename,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
return false;
if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
{
return false;
}
if (CreateIoCompletionPort((HANDLE) hFile, CompletionPort, (DWORD)hFile,
0) == NULL)
{
int errorcode = GetLastError();
return false;
}
要注意的就是CreateFile要用FILE_FLAG_OVERLAPPED的方式打开
//-------------------------------------------------------------------
2。 启动Woker Thread来获取IOCP上的事件
if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, this,
0, &ThreadID)) == NULL)
{
return false;
}
CloseHandle(ThreadHandle);
that's simple..
//-------------------------------------------------------------------
3。 在Worker Thread上等待事件
DWORD WINAPI ServerWorkerThread(LPVOID file)
{
CIOCPFile * iofile = (CIOCPFile * ) file;
HANDLE CompletionPort = (HANDLE) iofile->CompletionPort;
while (true)
{
DWORD BytesTransferred = 0;
DWORD CompleteKey = 0;
LPPER_IO_OPERATION_DATA PerIoData;
int ret = GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
(LPDWORD)&CompleteKey, (LPOVERLAPPED *) &PerIoData, INFINITE);
if (ret == 0)
{
// error handling
}
if (PerIoData->CmdCode == IOWrite)
{
iofile->position = iofile->position + BytesTransferred;
if (BytesTransferred == PerIoData->nNumberOfBytesToSend)
{
// this means ok
}
else
{
// error handling
}
GlobalFree(PerIoData);
continue;
}
..
}
}
也比较简单,就一个扫描线程等事件而已,并且对一些带过来的用户
数据进行业务处理,其中positiond稍晚讲到
//-------------------------------------------------------------------
4。 写数据(日志记录主要是写操作)
bool CIOCPFile::WriteFileData(char * buf, int len)
{
DWORD Written = 0;
LPPER_IO_OPERATION_DATA PerIoData;
if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)
{
return false;
}
ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
ZeroMemory(PerIoData->Buf, DATA_BUFSIZE);
memcpy(PerIoData->Buf, buf, len);
PerIoData->Overlapped.Offset = position;
PerIoData->CmdCode = IOWrite;
PerIoData->nNumberOfBytesToSend = len;
PerIoData->nNumberOfBytesSent = 0;
if (!WriteFile(hFile,
PerIoData->Buf,
len,
NULL,
&(PerIoData->Overlapped)))
{
if (ERROR_IO_PENDING != GetLastError())
{
return false;
}
}
return true;
}
这边有几个地方要注意:
1。由于进行overlap操作的时候,系统不会对文件指针进行管理,
因此需要自己去手工的设置文件指针(When FILE_FLAG_OVERLAPPED
is specified, the system does not maintain the file pointer)
这边假设不超过64K,所以OffsetHigh没有用到
2。不需要通过CreateEvent的方式去设置Overlapped结构,IOCP会自动管理
3。投递读写请求的时候不要用那个扩展函数,“After an instance of
an open file is associated with an I/O completion port,
it cannot be used in the ReadFileEx or WriteFileEx function.”
//-------------------------------------------------------------------
由上可知,IOCP的使用还是比较简单的,不过由于目前对于文件操作的
相关说明比较少,所以验证了一下,算是填补偶自己的一个技术空白吧。
呵呵。在使用上,由于现在是异步操作,对于我们要发出的比如记录日志
的请求,一个异步马上返回,根本不用考虑效率,而对于另一个线程(Worker)
的检测,我们只需要对出错部分,比如没有写全等做个处理即可(重投一个
写操作,写的数据是剩余没有写完的部分),其他我们根本不用考虑,怎么样,
爽吧。^_^。
当然我们也可以手工实现异步方式,比如把要投递的请求先扔到一个缓存
里面,另外开个线程来一批批的处理这些请求,但跟上面的做法比起来,
要考虑线程同步,要考虑内存管理,这样无疑是上面的做法要优雅的多。
使用上主要就是上面哪几个注意事项,其他的都差不多,欢迎大家讨论。
谢谢
Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=650091