为了在IOCP中使SOCKET句柄复用,我使用了DisconnectExt函数,一般情况下客户端与服务端建立连接后,服务端会投递一个WSARecv例程给IOCP以接收来自客户端的数据,而服务端主动断开连接时会投DisconnectEx例程。问题描述:
当服务端主动提交DisconnectEx例程后正常情况是先收到WSARecv完成事件 然后收到DisconnectEx完成事件并且在DisconnectEx事件中做一些资源清理工作然后把此句柄提交给AcceptEx队列中。但问题是有时候先接收到的是DisconnectEx完成事件然后再接收到是WSARecv完成事件,这种情况造成一些问题比如当清理工作完成后IOCP会把这个SOCKET分配给一个新的连接,这个时候可能收到的WSARecv完成事件是原来旧的连接上的。我在DisconnectEx之前也用过了CancelEx来取消IO但无济于世,问教大家有什么办法能在接收到断开事件前让WSARecv完成事件先达到,这样才能顺序条理的释放句柄上的资源。

解决方案 »

  1.   

    你不能在WSARecv投递以后然后响应接收到数据,再这之后再投递DisconnectEx吗?
      

  2.   

    估计你没有明白我的意思,一般情况下客户端与服务端建立连接后,服务端总是会提交WSARecv例程来等待客户端发数据过来,服务端也不知道哪一个包是客户端发的最后一个包,所以正常情况下服务收到一个包之后会立即投递下一个WSARecv例程,只有客户端或服务端主动断开时才能收到最后的WSARecv的完成事件。我所描述的问题是DisconnectEx后,WSARecv完成事件是在DiscconectEx完成事件之后到达的,这个不符合我的预期,我期望的是DisconnectEx后,WSARecv完成事件先到达DisconnectEx完成事件后达到。希望各位能帮忙,谢谢!
      

  3.   

    自己弄个变量记录一下,收到不期望的WSARecv完成事件时忽略之?
      

  4.   

     zhao4zhong1-----------------------------------------------------
    我目前确实是这样做的,我的想法就是有没有其它更好的方法来解决这个问题!希望大家畅所欲言!
      

  5.   

     在WSASend或WSARecv成功后,使用InterlockedIncrement计数
    在处理完一个IO请求后,使用InterlockedDecrement计数
    当要断开连接时,标记一个状态Disconnecting=True,投递一个IO通知断开连接(PostDisconnectEx),同时计数+1
    收到DisconnectEx的IO时,触发OnDisconnect后,计数-1所有可能触发断线的地方,判断Disconnecting是否=True,若Disconnecting=True时,判断计数是否=0
    计数=0时,在一个统一的接口(ReleaseClient)回收相关资源有过程触发ReleaseClient时,表示所有IO返回完成,可以安全回收上下文资源
      

  6.   

    在WSARecv完成事件接收后,在其数据处理后调用DisconnectEx,不要无序调用
      

  7.   

    今天正好在重新设计这一套东西,虽然这是个坟,为了以后别人少走弯路,我说下我的处理方法。首先回答核心问题:不管你的套接字上投递了多少个事件,一旦你投递了DisconnectEx,那么所有事件都是并发返回,除非你使用了单线程IOCP,否则他们没有顺序,只有并发。楼上的观点取决于你的服务端是不是应答式的,一问一答得机制,确实可以保证“每一个套接字上任何时间只存在一个投递事件”,这也是C1M以上并发量的做法。但如果要求网络IO必须是异步的,就不可能满足“一个套接字上任何时间只存在一个事件” 这一条基本要求,甚至从这个问题延申出去的发送数据积压,也会同时体现出来,当然这里先不说多余的问题。那么为什么我们还需要这种方式呢?答:请参考游戏服务端。我使用了一个IO控制类(class ioctl)来管理三个 扩展的重叠结构(OVERLAPPEDEX)对象
    一个AcceptEx和WSArecv, 
    一个DisconnectEX,
    一个WSASend.结构大致如下:
    struct OVERLAPPEDEX{
        OVERLAPPED ol;
        ioctl *ctl;
        char *buf;
        unsigned long len;
        int opt;//表示用来IO操作的模式:
        //FD_READ=WSARecv事件
        //FD_WRITE=WSASend事件
        //FD_ACCEPT=AcceptEx事件
        //FD_CLOSE=DisconnectEx事件
    };同时在class ioctl中,有一个int opts; 它可以是 FD_READ,  FD_WRITE,  FD_ACCEPT, FD_CLOSE的任意组合。
    在投递操作之前,OVERLAPPEDEX.opt=具体操作;时,ioctl.opts |= 具体操作;
    在事件完成时,ioctl.opts ^= OVERLAPPEDEX.opt;
    读写都加锁。这样子处理之后,那么服务端只要满足以下几个条件,就可以正确的处理这种并发问题:
    1、投递AcceptEx时,必须保证ioctl.opts为0,也就是所有事件都已经返回。
    2、FD_ACCEPT是一个独立的值,绝对不会包含其他操作共存,当ioctl.opts==FD_ACCEPT时,拒绝之后的所有操作。
    3、FD_CLOSE可以与FD_READ,FD_WRITE共存,但是一个套接字在其有效期内,只允许投递一次FD_CLOSE。
    4、一个投递过FD_CLOSE的套接字,拒绝之后的FD_READ与FD_WRITE操作。accept:192,168,2,11:65405
    2019/09/21 06:40:50 ioctl::read ok!
    2019/09/21 06:40:50 IO完成:1, bytes:7
    接收完毕!
    收到客户端数据:啦啦啦
    2019/09/21 06:40:50 ioctl::_write ok!
    2019/09/21 06:40:50 IO完成:2, bytes:13
    发送完毕!
    2019/09/21 06:40:50 ioctl::read ok!
    2019/09/21 06:40:50 ioctl::disconnect ok!
    2019/09/21 06:40:50 IO完成:1, bytes:0
    2019/09/21 06:40:50 IO完成:32, bytes:0
    2019/09/21 06:40:50 ioctl::accept ok!看日志可以知道,ioctl::read与ioctl::disconnect在一起调用了,之后也是并发完成,到最后一个事件完成时,才去把套接字acceptex回去。
    题外话1:异步网络IO会带来发送数据积压的问题,这个问题没有什么好办法来解决,只能限制最大积压数,超过这个数量,要么断开连接,要么就把数据抛弃掉。题外话2:异步网络IO模型并不适合IOCP来处理,应该考虑去linux使用epoll,因为异步网络IO追求的不是并发量。题外话3:C1M以上的并发量时,IOCP将全面超越epoll,因为应答式服务端上层可以不需要任何读写锁处理,数据可以不需要任何多余的拷贝。
      

  8.   

    没这么复杂吧,我记得DisconnectEx引发的WSARecv收不到任何东西或者有其他判断条件可以判断。WSARecv完成例程里要先判断这些条件的,不符合直接rerurn就行了吧。