用完成端口关联文件操作

完成端口”模型是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)
的检测,我们只需要对出错部分,比如没有写全等做个处理即可(重投一个
写操作,写的数据是剩余没有写完的部分),其他我们根本不用考虑,怎么样,
爽吧。^_^。
    当然我们也可以手工实现异步方式,比如把要投递的请求先扔到一个缓存
里面,另外开个线程来一批批的处理这些请求,但跟上面的做法比起来,
要考虑线程同步,要考虑内存管理,这样无疑是上面的做法要优雅的多。
使用上主要就是上面哪几个注意事项,其他的都差不多,欢迎大家讨论。
谢谢
  • 0
    点赞
  • 1
    收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 1

打赏作者

xrbeck

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值