Chinaunix首页 | 论坛 | 博客
  • 博客访问: 984210
  • 博文数量: 232
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 2315
  • 用 户 组: 普通用户
  • 注册时间: 2005-11-02 11:43
文章分类

全部博文(232)

文章存档

2009年(6)

2008年(22)

2007年(72)

2006年(85)

2005年(47)

我的朋友

分类: WINDOWS

2006-04-05 17:23:43

完成端口”模型是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

阅读(2005) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~