在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)

356 views
Skip to first unread message

stephen.nil

unread,
May 10, 2008, 9:16:15 AM5/10/08
to dev4s...@googlegroups.com
Hi,all

关于 IOCP 中如何安全释放资源,我想关键点是在于如何等待异步 IO 完成,被取消也暂且认为是一种完成。

在使用多线程来运行 IOCP 的 GQCS 函数的时候,当某个一个线程(线程A)执行了 closesocket 之后,将引发在这个 socket 发出的所有异步 IO 被取消。
这些被取消的 IO 仍然会被 GQCS 取得并返回,并且通常这是在另外一个线程中(线程B)。

一个可能的执行顺序如下:
1.线程 A 执行了 closesocket
2.线程 B 取得了一个被取消的 异步 IO 操作
3.线程 B 通过访问 PER_HANDLE_DATA 知道这是一个通过 closesocket 触发的 取消操作,因此简单地忽略
4.线程 A 释放 PER_HANDLE_DATA

上面的四个步骤,如果按顺序执行,那么就没有问题。
如果 步骤4 在 步骤2,或者3 之前执行,就会导致 线程B 访问已经被释放的内存。
如果有办法使得 线程A 等待 线程B 完成 步骤2和3 ,然后再进行 步骤4 ,这样就没有问题。

查了 MSDN ,没有发现有实现这种等待功能的函数。不知道大家平时是怎么处理这个问题的?

下面的 3 篇文章是讲关于 IOCP 中如何安全释放资源的,但可惜都没有提供完整的例子,很多细节的地方不知道是怎么实现的。

http://blog.csdn.net/sodme/archive/2006/04/17/666062.aspx
IOCP中的socket错误和资源释放处理方法

http://blog.csdn.net/SeaWave/archive/2006/05/21/747863.aspx
狗尾续貂:利用引用计数在多线程中安全释放资源

http://libo.deng.blog.163.com/blog/static/40157422200772132422119/
编写大容量和健壮的服务器系列—处理IOCP资源释放


--------------
stephen.nil
2008-05-10

stephen.nil

unread,
May 11, 2008, 1:55:40 AM5/11/08
to stephen.nil, dev4s...@googlegroups.com
Hi, all

最终采用了下面这篇文章种提到的做法
>http://libo.deng.blog.163.com/blog/static/40157422200772132422119/
>编写大容量和健壮的服务器系列—处理IOCP资源释放

方式二
使用一个Hash表,对每个连接定义一个连接上下文,按socket值索引,把一些资源,例如信令缓存指针置于hash中的上下文。

HANDLE CreateIoCompletionPort(HANDLE FileHandle, HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);

一个关键的地方是在把 client socket 关联到 完成端口,调用 CreateIoCompletionPort 的时候,
CompletionKey 不要按常见的做法,把 PER_HANDLE_DATA 的指针传进行去,而是只把 client socket 按值传进去。
然后把 CompletionKey 和 PER_HANDLE_DATA 保存到一个 Hash 表中。

CreateIoCompletionPort:

ULONG_PTR completionKey = client;
PER_HANDLE_DATA * perHandleData = GlobalAlloc(xxx);
sessionManager->put( completionKey, perHandleData );
CreateIoCompletionPort( client, completionPort, completionKey, 0 )

GetQueuedCompletionStatus:

ULONG_PTR completionKey = 0;
GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, ... );
PER_HANDLE_DATA * perHandleData = sessionManager->get( completionKey );
if( NULL != perHandleData ) {
}

closesocket:

sessionManager->remove( completionKey );
closesocket( perHandleData->socket );
GlobalFree( perHandleData );

>一个可能的执行顺序如下:
>1.线程 A 执行了 closesocket
>2.线程 B 取得了一个被取消的 异步 IO 操作
>3.线程 B 通过访问 PER_HANDLE_DATA 知道这是一个通过 closesocket 触发的 取消操作,因此简单地忽略
>4.线程 A 释放 PER_HANDLE_DATA

重新回到这四个步骤,在 步骤1 中,在 closesocket 之前,先根据 completionKey 从 sessionManager 中删除掉 perHandleData 。
之后,在 线程B 中得到了 completionKey ,但是根据 completionKey 无法从 sessionManager 中找到 perHandlerData 。
这样,无论上面的四个步骤按什么顺序执行,都不会导致访问已经被释放内存。

Best regards,

stephen.nil
2008-05-11

stephen.nil

unread,
May 11, 2008, 3:44:41 AM5/11/08
to dev4s...@googlegroups.com
Hi, all

刚好在这里看到了一个类似的想法

http://blog.codingnow.com/2007/07/robust.html#comment-12142

对大家的讨论不置可否,但是使用ID代替指针确实在某些方面是有益处的。
而某些时候我们无法使用智能指针。举个例子,完成端口中

GetQueuedCompletionStatus(
*pTHIS,
&dwNumberBytes,
(PULONG_PTR)&lpKey,
&lpEvent,
INFINITE
);

其中的lpKey是一个非常关键的变量,我们一般把它置为一个对象的指针。这样我们就可以通过这个指针去访问该对象。

但是事实上很多时候这个指针的生命周期是不固定的,而由于其传入的是PULONG_PTR类型的值,我们又不能直接使用智能指针。要么就要繁琐的执行addref 和 release 这样的操作而完全丢失智能指针的优越性。

可是,如果这里传入的是一个ID或者HANDLE,为其对象的访问增加一层间接性。那么我们就可以预防非法的指针访问,从而达到安全的生命周期管理。

Best regards,

stephen.nil
2008-05-11

6spring

unread,
May 11, 2008, 6:18:11 AM5/11/08
to dev4s...@googlegroups.com
个人感觉,使用引用计数还是hash,效果都差不多
hash表在这里,插入、删除、查找时都需要线程锁,不见得就比引用计数的InterlockedIncrement 和InterlockedDecrement开销小。
我目前是使用了hash id来作为后面逻辑层的通讯handle,在socket 的IOCP操作上,则使用了引用计数
在IOCP操作里,投递的OVERLAPPED结构(空闲列表重分配型分配器)、每连接的session(固定数量的对象池分配器,便于快速hash)都做了一个封装
每次投递的生命期已一个OVERLAPPED结构release到0时结束,而一个连接,则以session的release到0为终结。
逻辑层的使用则类似 send(hash id , packet &),与IOCP的工作线程隔离,不必关心连接无效导致的异常
 
 
2008-05-11

6spring

发件人: stephen.nil
发送时间: 2008-05-11  15:44:58
抄送:
主题: Re: 在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)
Hi, all
刚好在这里看到了一个类似的想法
对大家的讨论不置可否,但是使用ID代替指针确实在某些方面是有益处的。
而某些时候我们无法使用智能指针。举个例子,完成端口中
GetQueuedCompletionStatus(
*pTHIS,
&dwNumberBytes,
(PULONG_PTR)&lpKey,
&lpEvent,
INFINITE
);
其中的lpKey是一个非常关键的变量,我们一般把它置为一个对象的指针。这样我们就可以通过这个指针去访问该对象。
但是事实上很多时候这个指针的生命周期是不固定的,而由于其传入的是PULONG_PTR类型的值,我们又不能直接使用智能指针。要么就要繁琐的执行addref 和 release 这样的操作而完全丢失智能指针的优越性。
可是,如果这里传入的是一个ID或者HANDLE,为其对象的访问增加一层间接性。那么我们就可以预防非法的指针访问,从而达到安全的生命周期管理。
Best regards,
stephen.nil
2008-05-11
>>>
>Hi, all
>
>最终采用了下面这篇文章种提到的做法
>>编写大容量和健壮的服务器系列--处理IOCP资源释放  
>>编写大容量和健壮的服务器系列--处理IOCP资源释放
>>
>> 
>>--------------
>>stephen.nil
>>2008-05-10
>
>

stephen.nil

unread,
May 11, 2008, 6:26:36 AM5/11/08
to dev4s...@googlegroups.com
Hi, 6spring

假设引用计数为 1 的时候,同时触发了两个事件,有两个线程同时持有了这个指针,
又假设两个线程同时准备把计数减去一,然后就准备释放。

在做减法的时候,通过 InterlockedIncrement 这些方法可以保证不会有问题。
但是当一个线程释放了内存,而另外一个线程却正准备去访问这个计数器,检查计数器的值是不是 0 。
这种情况下,是不是就访问了已经被释放的内存?

Best regards,

stephen.nil
2008-05-11

>>>
>个人感觉,使用引用计数还是hash,效果都差不多
>hash表在这里,插入、删除、查找时都需要线程锁,不见得就比引用计数的InterlockedIncrement 和InterlockedDecrement开销小。
>我目前是使用了hash id来作为后面逻辑层的通讯handle,在socket 的IOCP操作上,则使用了引用计数
>在IOCP操作里,投递的OVERLAPPED结构(空闲列表重分配型分配器)、每连接的session(固定数量的对象池分配器,便于快速hash)都做了一个封装
>每次投递的生命期已一个OVERLAPPED结构release到0时结束,而一个连接,则以session的release到0为终结。
>逻辑层的使用则类似 send(hash id , packet &),与IOCP的工作线程隔离,不必关心连接无效导致的异常
>
>
>2008-05-11
>
>
>
>6spring

stephen.nil

unread,
May 11, 2008, 6:26:36 AM5/11/08
to dev4s...@googlegroups.com
Hi, 6spring

假设引用计数为 1 的时候,同时触发了两个事件,有两个线程同时持有了这个指针,
又假设两个线程同时准备把计数减去一,然后就准备释放。

在做减法的时候,通过 InterlockedIncrement 这些方法可以保证不会有问题。
但是当一个线程释放了内存,而另外一个线程却正准备去访问这个计数器,检查计数器的值是不是 0 。
这种情况下,是不是就访问了已经被释放的内存?

Best regards,

stephen.nil
2008-05-11

>>>
>个人感觉,使用引用计数还是hash,效果都差不多
>hash表在这里,插入、删除、查找时都需要线程锁,不见得就比引用计数的InterlockedIncrement 和InterlockedDecrement开销小。
>我目前是使用了hash id来作为后面逻辑层的通讯handle,在socket 的IOCP操作上,则使用了引用计数
>在IOCP操作里,投递的OVERLAPPED结构(空闲列表重分配型分配器)、每连接的session(固定数量的对象池分配器,便于快速hash)都做了一个封装
>每次投递的生命期已一个OVERLAPPED结构release到0时结束,而一个连接,则以session的release到0为终结。
>逻辑层的使用则类似 send(hash id , packet &),与IOCP的工作线程隔离,不必关心连接无效导致的异常
>
>
>2008-05-11
>
>
>
>6spring

6spring

unread,
May 11, 2008, 6:30:53 AM5/11/08
to dev4s...@googlegroups.com
补一句,那个hash值,用int64,就可以基本保证唯一性了,例如我现在使用的
 
高8,每次分配出来时一个随机值(类似干扰码)
次高24,对象池内坐标(一般应用来说这个量级足够了)
低32,特征值(在session那里用了个接入人次数)
 
这样,基本上不太可能出现重复的hash,而且寻址很快速
 
 
2008-05-11

6spring

发件人: stephen.nil
发送时间: 2008-05-11  15:44:58
抄送:
主题: Re: 在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)
Hi, all
刚好在这里看到了一个类似的想法
对大家的讨论不置可否,但是使用ID代替指针确实在某些方面是有益处的。
而某些时候我们无法使用智能指针。举个例子,完成端口中
GetQueuedCompletionStatus(
*pTHIS,
&dwNumberBytes,
(PULONG_PTR)&lpKey,
&lpEvent,
INFINITE
);
其中的lpKey是一个非常关键的变量,我们一般把它置为一个对象的指针。这样我们就可以通过这个指针去访问该对象。
但是事实上很多时候这个指针的生命周期是不固定的,而由于其传入的是PULONG_PTR类型的值,我们又不能直接使用智能指针。要么就要繁琐的执行addref 和 release 这样的操作而完全丢失智能指针的优越性。
可是,如果这里传入的是一个ID或者HANDLE,为其对象的访问增加一层间接性。那么我们就可以预防非法的指针访问,从而达到安全的生命周期管理。
Best regards,
stephen.nil
2008-05-11
>>>
>Hi, all
>
>最终采用了下面这篇文章种提到的做法
>>编写大容量和健壮的服务器系列--处理IOCP资源释放  
>>编写大容量和健壮的服务器系列--处理IOCP资源释放
>>
>> 
>>--------------
>>stephen.nil
>>2008-05-10
>
>

6spring

unread,
May 11, 2008, 6:33:53 AM5/11/08
to dev4s...@googlegroups.com
汗,引用计数的作用,就是减到0时释放资源,自己这次减到0了,就是表示,除了自己没有任何使用者了,可以释放
 
 
2008-05-11

6spring

发件人: stephen.nil
发送时间: 2008-05-11  18:26:51
抄送:
主题: Re: 在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)
Hi, 6spring

6spring

unread,
May 11, 2008, 6:48:31 AM5/11/08
to dev4s...@googlegroups.com
关于这里的引用计数,在详细一点说下
引用计数在这里,一次加减对,是以一次IO操作为单位,不是以某个线程持有为单位,例如
读请求投递 +
读投递返回 -
这里的加减,很可能在2个线程中完成,但是对于这次IO操作来说,是一个操作
同理,写请求,甚至是一个事件投递都可以看作一个加减单位,一次IO操作,基本上也就是一个OVERLAPPED的生命期。对于连接来说,在活跃列表中加了一个引用,而在判定断开时减,其他的加减,全是IO操作
而资源的释放,通通在引用计数到0的那次release,也就是说,释放的控制权在被引用对象身上,这也是为什么OVERLAPPED和session都需要做一次再封装的原因
 
 
2008-05-11

6spring

发件人: stephen.nil
发送时间: 2008-05-11  18:26:51
抄送:
主题: Re: 在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)
Hi, 6spring

stephen.nil

unread,
May 11, 2008, 9:55:17 AM5/11/08
to dev4s...@googlegroups.com
Hi, 6spring

我之前的描述有问题,应该说是

假设引用计数为 2 的时候,同时触发了两个事件,有两个线程同时持有了这个指针,
又假设两个线程同时准备把计数减去一,然后就准备释放。
在做减法的时候,通过 InterlockedIncrement 这些方法可以保证不会有问题。
假设把计数器递减为 0 的线程先执行 free 操作,
然后才是把计数器递减为 1 的线程执行检查引用计数器是否为 0 。

这里的一个问题是,这个引用计数器是不是在 free 操作的范围呢?
如果在 free 的时候,把引用计数器也 free 掉了,那么后面这个线程的检查就会访问已经被释放的内存。

PS:所有用 OVERLAPPED 投递的异步操作,是否最后一定会被 GQCS 得到?
无论是正常完成操作,或者出现异常,或者被取消?
从 MSDN 的文档来看,特别是在提到取消操作的时候,常常看起来好像是不确定的。
就是被取消的操作,不一定会被 GQCS 得到。不知道有没有哪份文档明确说明了这个问题?

如果被取消的操作有可能不被 GQCS 得到,也就是在静悄悄中就被取消了,
那么就可能造成某一个计数器总是不为 0 ,导致长期占用资源。


Best regards,

stephen.nil
2008-05-11

>>>
>汗,引用计数的作用,就是减到0时释放资源,自己这次减到0了,就是表示,除了自己没有任何使用者了,可以释放
>
>
>2008-05-11
>
>
>
>6spring
>
>
>
>发件人: stephen.nil
>发送时间: 2008-05-11 18:26:51

6spring

unread,
May 11, 2008, 10:38:44 AM5/11/08
to dev4s...@googlegroups.com
既然查了MSDN,就顺便看看InterlockedDecrement嘛,写的很清楚,The function returns the resulting decremented value. 也就是说往往用
if(0==InterlockedDecrement(&m_lRef))
OnDestroy();
至少在实际项目中,我还没有遇到这个地方出错的。
 
正确投递出去的请求,在实际项目中,我没有遇到没有返回的,但是注意投递时即错误的情况
pSession->AddRef();
pBuffer->AddRef();
pBuffer->SetOperation(Net_IO_Read_Completed); 
pBuffer->SetupRead();
pBuffer->SetUserPtr(pSession);
 
DWORD dwNumBytes = 0;
DWORD dwFlags = 0;
CCriticalSection::COwner lock(pSession->GetCrit());
if(SOCKET_ERROR == ::WSARecv(pSession->m_hSocket,pBuffer->GetWSABUF(),1, &dwNumBytes,&dwFlags,pBuffer,NULL))
{
DWORD lastError = ::WSAGetLastError();
if(ERROR_IO_PENDING != lastError)
{
pSession->ConnectionError(Net_ReadError,pBuffer,lastError);
pSession->Release();
pBuffer->Release();
}
}
 
至于怕引用计数无法正确到0,一般是代码逻辑问题,理清逻辑,这个也加减关系也就很清晰。
 
对于socket IOCP,取消操作,基本只有closesocket导致,其他的不太可能去CancelIO,实际运行过程中,这个没返回的情况微乎其微
对于异常造成的,我一般是
try
....
process
....
catch()
{
out_err
}
->Release();
这样即使中间处理过程异常,也一般能正确释放
 
 
2008-05-11

6spring

发件人: stephen.nil
发送时间: 2008-05-11  21:55:35

stephen.nil

unread,
May 12, 2008, 9:34:31 AM5/12/08
to dev4s...@googlegroups.com
Hi, 6spring

从这些描述来看,使用 引用计数 看起来应该是没有问题的。

在使用 Hash 表的实现中,用 BoundsChecker 进行了检查,报了一些这样的错误:
OutDbStr: HEAP[testiocpecho.exe]:
OutDbStr: HEAP: Free Heap block 123c1a0 modified at 123c1d4 after it was freed

在 closesocket 的描述中提到:
Thus, an application should not cleanup any resources (WSAOVERLAPPED structures, for example)
referenced by the outstanding I/O requests until the I/O requests are indeed completed.

看来像下面的这段代码是存在问题的。
sessionManager->remove( completionKey );
closesocket( perHandleData->socket );
GlobalFree( perHandleData );

如果还有未完成的异步操作在 IOCP 中,一旦 closesocket ,会导致 OS 往 OVERLAPPED 写一些信息。
如果 closesocket 之后,立即 free 掉 PER_HANDLE_DATA ,就会导致上面的错误。

之前一直困扰在如何等待所有这些异步 IO 操作完成(包括被取消),现在想到了一个办法。
IOCP 的内部实现是遵循 FIFO 的,在 closesocket 之后,不要立即 free ,而是重新
PostQueuedCompletionStatus 一个完成特殊事件给 GQCS ,通过 GQCS 得到这个事件,接着在进行 free 。

这样就相当于变相地等待所有这些异步 IO 完成了。
因为 closesocket 之后,所有 IO 都会被取消,这些取消的操作将在 GQCS 的队列中等待被处理,
而用 PostQueuedCompletionStatus 投递的特殊事件,应该排在这些被取消的操作之后。

Best regards,

stephen.nil
2008-05-12
>6spring
>

6spring

unread,
May 12, 2008, 11:17:01 PM5/12/08
to dev4s...@googlegroups.com
你想法不对,不能依赖于最后再Post一个IO出去,虽然IOCP是FIFO,但是不代表工作线程执行过程中也保证此次序(只有1个工作线程除外)
一般通行做法,随时保持一个投递请求在IOCP上(一般是Read),closesocket时,此请求返回read到0 byte,可以判定session为break,这样移除额外保持的session引用(活跃列表所持有),其他各个投递在结束时,引用计数就到0了,那时即可释放资源。
至于OVERLAPPED结构,每次IO操作的(请求-》结束)就是一块OVERLAPPED的生命期了,做个对象重用体系即可很好工作
 
2008-05-13

6spring

发件人: stephen.nil
发送时间: 2008-05-12  21:35:10
抄送:
主题: Re: 在 windows IOCP 中,如何等待异步 IO 操作完成(包括被取消)

lau stephen

unread,
May 12, 2008, 11:32:51 PM5/12/08
to dev4s...@googlegroups.com
工作进程不是按顺序执行,这个说法是正确的。
 
我之前描述的做法,执行 closesocket 的线程已经把 PER_HANDLE_DATA 从 Hash 表中删掉了,所以其他的线程从那一刻开始,就不可能再获得这个 PER_HANDLE_DATA 的引用了。所以无论工作线程如何执行,只要其他线程不可能再获得这个 PER_HANDLE_DATA 的引用,就不会存在问题。
 
只是虽然工作线程不会访问 PER_HANDLE_DATA ,但是系统内核却可能会访问 OVERLAPPED ,而这个 OVERLAPPED  正好在 PER_HANDLE_DATA 结构中,所以在 closesocket 之后,如果立即释放 PER_HANDLE_DATA  就会有下面的错误
OutDbStr: HEAP: Free Heap block 123c1a0 modified at 123c1d4 after it was freed
 
重新投递一个特殊事件,是指下面这样。昨天用了 BoundsChecker 进行了大压力的测试,没有再发现有内存访问错误了。
 
void close( ... )
{
......
        closesocket( ... );
        memset( &( iocpSession->mFreeEvent ), 0, sizeof( OVERLAPPED ) );
        PostQueuedCompletionStatus( eventArg->getCompletionPort(), 0,
                        SP_IocpEventCallback::eKeyFree, &( iocpSession->mFreeEvent ) );
}
 
void eventLoop( .... )
{
......
        BOOL isSuccess = GetQueuedCompletionStatus( completionPort, &bytesTransferred,
                        &completionKey, &overlapped, INFINITE );
        if( eKeyFree == completionKey ) {
                assert( NULL == iocpSession );
                iocpSession = CONTAINING_RECORD( overlapped, SP_IocpSession_t, mFreeEvent );
                delete iocpSession->mSession;
                free( iocpSession );
                return TRUE;
        }
......
}
 
在08-5-13,6spring <6sp...@gmail.com> 写道:

lijie

unread,
May 13, 2008, 1:39:14 AM5/13/08
to dev4s...@googlegroups.com


2008/5/11 stephen.nil <steph...@gmail.com>:


假设引用计数为 2 的时候,同时触发了两个事件,有两个线程同时持有了这个指针,
又假设两个线程同时准备把计数减去一,然后就准备释放。
在做减法的时候,通过 InterlockedIncrement 这些方法可以保证不会有问题。
假设把计数器递减为 0  的线程先执行 free 操作,
然后才是把计数器递减为 1  的线程执行检查引用计数器是否为 0 。

这里的一个问题是,这个引用计数器是不是在 free 操作的范围呢?
如果在 free 的时候,把引用计数器也 free 掉了,那么后面这个线程的检查就会访问已经被释放的内存。

 
检查InterlockedIncrement的返回值而不是访问已经释放的内存,这个是安全的。

tao yuan

unread,
May 13, 2008, 2:23:31 AM5/13/08
to dev4s...@googlegroups.com
实际上, 还可以实现一种方法,不需要加引用计数的。
每个socket 在closesocket的时候,设置一个OnDestroy的标志位。
设置一个超时时间, 然后开一个单独的线程做这种dead socket的delete处理。


2008/5/13 lijie <cpu...@gmail.com>:

lau stephen

unread,
May 13, 2008, 2:47:13 AM5/13/08
to dev4s...@googlegroups.com
这个方法和我之前提到的做法类似,可以把这个 free 的动作放到 GQCS 线程去执行。
需要重新 PostQueuedCompletionStatus 方法投递一个特殊事件,使得 GQCS 能够得到这个事件,并作相应的处理。
这样就不需要一个特殊的线程。
 


在08-5-13,tao yuan <yty...@gmail.com> 写道:

stephen.nil

unread,
Jun 1, 2008, 9:52:18 AM6/1/08
to dev4s...@googlegroups.com
今天重新看了一些 DisconnectEx 的说明

BOOL DisconnectEx(
__in SOCKET hSocket,
__in LPOVERLAPPED lpOverlapped,
__in DWORD dwFlags,
__in DWORD reserved
);

When lpOverlapped is not NULL, overlapped I/O might not finish before DisconnectEx returns, resulting in the DisconnectEx function returning FALSE and a call to the WSAGetLastError function returning ERROR_IO_PENDING. This design enables the caller to continue processing while the disconnect operation completes. Upon completion of the request, Windows sets either the event specified by the hEvent member of the OVERLAPPED structure, or the socket specified by hSocket, to the signaled state.

其实这个就是用于彻底解决等待异步 IO 操作完成的方案。
只需要给 lpOverlapped 传递一个类似 WSASend 或者 WSARecv 的 OVERLAPPED 参数。然后再 GQCS 中得到它,再进行释放和 closesocket 。
因为在调用 DisconnectEx 的时候,如果还有 WSASend 和 WSARecv 请求在 pending 的话,DisconnectEx 实际的工作应该是还没有完成的。
如果紧接着 closesocket ,按现在的测试情况,似乎会造成在改 socket 没有发送 FIN 包,导致对端会认为连接还正常。

stephen.nil

unread,
Jun 1, 2008, 9:52:18 AM6/1/08
to dev4s...@googlegroups.com
今天重新看了一些 DisconnectEx 的说明

BOOL DisconnectEx(
__in SOCKET hSocket,
__in LPOVERLAPPED lpOverlapped,
__in DWORD dwFlags,
__in DWORD reserved
);

When lpOverlapped is not NULL, overlapped I/O might not finish before DisconnectEx returns, resulting in the DisconnectEx function returning FALSE and a call to the WSAGetLastError function returning ERROR_IO_PENDING. This design enables the caller to continue processing while the disconnect operation completes. Upon completion of the request, Windows sets either the event specified by the hEvent member of the OVERLAPPED structure, or the socket specified by hSocket, to the signaled state.

其实这个就是用于彻底解决等待异步 IO 操作完成的方案。
只需要给 lpOverlapped 传递一个类似 WSASend 或者 WSARecv 的 OVERLAPPED 参数。然后再 GQCS 中得到它,再进行释放和 closesocket 。
因为在调用 DisconnectEx 的时候,如果还有 WSASend 和 WSARecv 请求在 pending 的话,DisconnectEx 实际的工作应该是还没有完成的。
如果紧接着 closesocket ,按现在的测试情况,似乎会造成在改 socket 没有发送 FIN 包,导致对端会认为连接还正常。

>>>
Reply all
Reply to author
Forward
0 new messages