vc6写的服务器代码,很简单一个iocp模型,贴点主要代码
::listen(m_sListen, 200);
m_hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0);
::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0);
WSAEventSelect(m_sListen, m_hAcceptEvent, FD_ACCEPT);
m_hListenThread = ::CreateThread(NULL, 0, _ListenThreadProc, this, 0, NULL);_ListenThreadProc里面启动一个workerthread,之后循环用WSAWaitForMultipleEvents和WSAEnumNetworkEvents等待
ne.lNetworkEvents为FD_ACCEPT的时候申请内存 调用accpetex,每次buf大小1kWorkerThread里面,GetQueuedCompletionStatus,dwKey为空,pBuffer->nOperation == OP_ACCEPT, dwTrans == 0时
::closesocket(pBuffer->sClient); 释放内存客户端就一个死循环调用 conect close 连上就断,什么数据也不发测试发现服务器程序运行一会就会死住
改为listen(,5)的时候服务器程序恢复正常就两个线程,而且什么数据都不发送,居然会死住,太奇怪了,请高手帮忙分析一下是否是listen的问题
操作系统为xp sp2

解决方案 »

  1.   

    _ListenThreadProc里面启动一个workerthread
    workerthread用到的是完成端口线程池,你这里的代码是怎么样的?
      

  2.   

    BOOL CIOCPServer::start()
    {
    // 创建监听套节字,绑定到本地端口,进入监听模式
    m_sListen = ::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    SOCKADDR_IN si;
    si.sin_family = AF_INET;
    si.sin_port = ::ntohs(6000)
    si.sin_addr.S_un.S_addr = INADDR_ANY;
    if(::bind(m_sListen, (sockaddr*)&si, sizeof(si)) == SOCKET_ERROR)
    {
    return FALSE;
    }
    ::listen(m_sListen, 200); // 创建完成端口对象
    m_hCompletion = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, 0); // 加载扩展函数AcceptEx
    GUID GuidAcceptEx = WSAID_ACCEPTEX;
    DWORD dwBytes;
    ::WSAIoctl(m_sListen, 
    SIO_GET_EXTENSION_FUNCTION_POINTER, 
    &GuidAcceptEx, 
    sizeof(GuidAcceptEx),
    &m_lpfnAcceptEx, 
    sizeof(m_lpfnAcceptEx), 
    &dwBytes, 
    NULL, 
    NULL);

    // 加载扩展函数GetAcceptExSockaddrs
    GUID GuidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
    ::WSAIoctl(m_sListen,
    SIO_GET_EXTENSION_FUNCTION_POINTER,
    &GuidGetAcceptExSockaddrs,
    sizeof(GuidGetAcceptExSockaddrs),
    &m_lpfnGetAcceptExSockaddrs,
    sizeof(m_lpfnGetAcceptExSockaddrs),
    &dwBytes,
    NULL,
    NULL
    );


    // 将监听套节字关联到完成端口,注意,这里为它传递的CompletionKey为0
    ::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0); // 注册FD_ACCEPT事件。
    // 如果投递的AcceptEx I/O不够,线程会接收到FD_ACCEPT网络事件,说明应该投递更多的AcceptEx I/O
    WSAEventSelect(m_sListen, m_hAcceptEvent, FD_ACCEPT); // 创建监听线程
    m_hListenThread = ::CreateThread(NULL, 0, ListenThread, this, 0, NULL);
    }
    DWORD WINAPI CIOCPServer::ListenThread(LPVOID lpParam)
    {
    CIOCPServer *pThis = (CIOCPServer*)lpParam; // 先在监听套节字上投递几个Accept I/O
    CIOCPBuffer *pBuffer;
    for(int i=0; i<10; i++)
    {
    pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);
    if(pBuffer == NULL)
    return -1;
    pThis->InsertPendingAccept(pBuffer);
    pThis->PostAccept(pBuffer);
    } // 构建事件对象数组,以便在上面调用WSAWaitForMultipleEvents函数
    HANDLE hWaitEvents[2];
    int nEventCount = 0;
    hWaitEvents[nEventCount ++] = pThis->m_hAcceptEvent;
    hWaitEvents[nEventCount ++] = ::CreateThread(NULL, 0, WorkerThread, pThis, 0, NULL); // 下面进入无限循环,处理事件对象数组中的事件
    while(TRUE)
    {
    int nIndex = ::WSAWaitForMultipleEvents(nEventCount, hWaitEvents, FALSE, 5*1000, FALSE);
            nIndex = nIndex - WAIT_OBJECT_0;
    WSANETWORKEVENTS ne;
    int rtn = 0;
                    int nLimit=0;
    if(nIndex == 0) m_hAcceptEvent事件对象受信,说明投递的Accept请求不够,需要增加
    {
    rtn = ::WSAEnumNetworkEvents(pThis->m_sListen, hWaitEvents[nIndex], &ne);
    if(!rtn)
    {
    if(ne.lNetworkEvents & FD_ACCEPT)
    {
    nLimit = 5;  // 增加的个数,这里设为5个
    }
    } // 投递nLimit个AcceptEx I/O请求
    int i = 0;
    while(i++ < nLimit && pThis->m_nPendingAcceptCount < 100)
    {
    pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);
    if(pBuffer != NULL)
    {
    pThis->InsertPendingAccept(pBuffer);
    pThis->PostAccept(pBuffer);
    }
    }
    }
    }
    return 0;
    }
    CIOCPBuffer *CIOCPServer::AllocateBuffer(int nLen)
    {
    CIOCPBuffer *pBuffer = NULL;
    if(nLen > BUFFER_SIZE)
    return NULL; // 为缓冲区对象申请内存
    ::EnterCriticalSection(&m_FreeBufferLock); pBuffer = (CIOCPBuffer *)::HeapAlloc(GetProcessHeap(), 
    HEAP_ZERO_MEMORY, sizeof(CIOCPBuffer) + nLen); // 初始化新的缓冲区对象
    if(pBuffer != NULL)
    {
    pBuffer->buff = (char*)(pBuffer + 1);
    pBuffer->nLen = nLen;
    }        ::LeaveCriticalSection(&m_FreeBufferLock); return pBuffer;
    }void CIOCPServer::ReleaseBuffer(CIOCPBuffer *pBuffer)
    {
    ::EnterCriticalSection(&m_FreeBufferLock);
    ::HeapFree(::GetProcessHeap(), 0, pBuffer);
    ::LeaveCriticalSection(&m_FreeBufferLock);
    }BOOL CIOCPServer::InsertPendingAccept(CIOCPBuffer *pBuffer)
    {
    ::EnterCriticalSection(&m_PendingAcceptsLock);
    m_nPendingAcceptCount ++;
    ::LeaveCriticalSection(&m_PendingAcceptsLock); return TRUE;
    }BOOL CIOCPServer::RemovePendingAccept(CIOCPBuffer *pBuffer)
    {
    ::EnterCriticalSection(&m_PendingAcceptsLock);
    m_nPendingAcceptCount --;
    ::LeaveCriticalSection(&m_PendingAcceptsLock); return  bResult;
    }BOOL CIOCPServer::PostAccept(CIOCPBuffer *pBuffer) // 在监听套节字上投递Accept请求
    {
    // 设置I/O类型
    pBuffer->nOperation = OP_ACCEPT; // 投递此重叠I/O  
    DWORD dwBytes;
    pBuffer->sClient = ::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
    BOOL b = m_lpfnAcceptEx(m_sListen, 
    pBuffer->sClient,
    pBuffer->buff, 
    pBuffer->nLen - ((sizeof(sockaddr_in) + 16) * 2),
    sizeof(sockaddr_in) + 16, 
    sizeof(sockaddr_in) + 16, 
    &dwBytes, 
    &pBuffer->ol);
    if(!b && ::WSAGetLastError() != WSA_IO_PENDING)
    {
    return FALSE;
    }
    return TRUE;
    }
    DWORD WINAPI CIOCPServer::WorkerThread(LPVOID lpParam)
    {
    CIOCPServer *pThis = (CIOCPServer*)lpParam; CIOCPBuffer *pBuffer;
    DWORD dwKey;
    DWORD dwTrans;
    LPOVERLAPPED lpol;
    while(TRUE)
    {
    // 在关联到此完成端口的所有套节字上等待I/O完成
    BOOL bOK = ::GetQueuedCompletionStatus(pThis->m_hCompletion, 
    &dwTrans, (LPDWORD)&dwKey, (LPOVERLAPPED*)&lpol, WSA_INFINITE); if(dwTrans == -1) // 用户通知退出
    {
    #ifdef _DEBUG
    ::OutputDebugString(" WorkerThread 退出 \n");
    #endif // _DEBUG
    ::ExitThread(0);
    } pBuffer = CONTAINING_RECORD(lpol, CIOCPBuffer, ol);
    int nError = NO_ERROR;
    if(!bOK) // 在此套节字上有错误发生
    {
    SOCKET s;
    if(pBuffer->nOperation == OP_ACCEPT)
    {
    s = pThis->m_sListen;
    }
    else
    {
    if(dwKey == 0)
    break;
    s = ((CIOCPContext*)dwKey)->s;
    }
    DWORD dwFlags = 0;
    if(!::WSAGetOverlappedResult(s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags))
    {
    nError = ::WSAGetLastError();
    } } pThis->HandleIO(dwKey, pBuffer, dwTrans, nError);
    } return 0;
    }void CIOCPServer::HandleIO(DWORD dwKey, CIOCPBuffer *pBuffer, DWORD dwTrans, int nError)
    {
    CIOCPContext *pContext = (CIOCPContext *)dwKey;
    if(pContext == NULL)
    {
    RemovePendingAccept(pBuffer);
    } if(nError != NO_ERROR)
    {
    //这里从来都不进
    if(pBuffer->nOperation != OP_ACCEPT)
    {
    OnConnectionError(pContext, pBuffer, nError);
    CloseAConnection(pContext);
    if(pContext->nOutstandingRecv == 0 && pContext->nOutstandingSend == 0)
    {
    ReleaseContext(pContext);
    }
    }
    else // 在监听套节字上发生错误,也就是监听套节字处理的客户出错了
    {
    // 客户端出错,释放I/O缓冲区
    if(pBuffer->sClient != INVALID_SOCKET)
    {
    ::closesocket(pBuffer->sClient);
    pBuffer->sClient = INVALID_SOCKET;
    }
    } ReleaseBuffer(pBuffer);
    return;
    }
    // 我连接上就断开,每次都走这里面
    if(pBuffer->nOperation == OP_ACCEPT)
    {
    if(dwTrans == 0)
    {
    if(pBuffer->sClient != INVALID_SOCKET)
    {
    ::closesocket(pBuffer->sClient);
    pBuffer->sClient = INVALID_SOCKET;
    }
    } ReleaseBuffer(pBuffer);
    }
    }服务器的主要代码都在这了
    客户端就开了个线程,conect closesocket死循环,也不发送接收数据
    连一会之后,服务器也不报错,感觉像是死住了,客户端关了再开也没法继续建立连接
    盼望高手回复
      

  3.   

    改listen(,5)的之后服务器程序运行正常,连了半个多小时都没死住,真是奇怪了
    换了几台电脑试,其中还有2003 server的,都不行,应该不是系统的问题
    可是我感觉又不像是listen的问题,郁闷死了
      

  4.   

    你的代码应该是 <<Windows网络与通信程序设计 >>上面的代码,原书代码我调试过,加为200没有任何问题。
      

  5.   

    listen的backlog 是有大小限制的.太大的话有问题.
      

  6.   

    udknight,原书的代码编译后不停地连接断开也会死住的,你没发现问题?
    就是因为原书的代码会死住,所以我才不断地简化代码,想查找问题,已经简化到这程度了,还是不行
      

  7.   

    那是WinSock1, WinSock2对这个没有限制。200个不算大。
      

  8.   

    // 将监听套节字关联到完成端口,注意,这里为它传递的CompletionKey为0
    ::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0);
    好像是它造成的问题,我还没确定,进一步测试一下再上来回复
      

  9.   

    你的连接量达到多少,会死。我测试的时候没有死掉。他的GetQueuedCompletionStatus我改过。
      

  10.   

    是AcceptEx这个函数用法错误AcceptEx除了accept接受socket外,还会接收第一次发来的数据
    注意第四个参数如:
    m_lpfnAcceptEx(m_sListen, 
    pBuffer->sClient, 
    pBuffer->buff, 
    pBuffer->nLen - ((sizeof(sockaddr_in) + 16) * 2), //提供了缓冲的话,就等待第一次发来的数据,不想等的话设为0
    sizeof(sockaddr_in) + 16, 
    sizeof(sockaddr_in) + 16, 
    &dwBytes, 
    &pBuffer->ol); 
    conect 后不发送接收数据,这不就是DOS攻击么所以服务器里要检查AcceptEx后每个socket的连接时间,或者心跳包之类的
    超时没数据收发的将其关闭
      

  11.   

    你统计下closesocket的返回值应该就明白了偶觉得你的closesocket不一定总能成功
    这个IOCP模型有够烂的,这样搞法,系统SCOKET资源很快就耗尽了
      

  12.   

    完成端口了怎么还WSAEventSelect啊
      

  13.   

    原书例子是客户端发送数据才算第一次连接,你说的超时检测在GetQueuedCompletionStatus调用处.不过这样的做法确实不好。
      

  14.   

    完成端口了怎么还WSAEventSelect啊
    这是为了检测是否需要投递更多的AcceptEx
      

  15.   


    udknight,原书的代码编译后不停地连接断开也会死住的,你没发现问题? 
    就是因为原书的代码会死住,所以我才不断地简化代码,想查找问题,已经简化到这程度了,还是不行

    原书代码我写过客户端进行调试过,同时几百个连接都是没有问题的。虽然他的断开检测那写的不严谨,不过还是能检测出正常关闭和异常关闭的。缺点就是那块写的很粗糙,有时候会莫名其妙被踢。你可以把代码传到CSDN上面,这样别人就可以调试了。这个例子最讨厌的就是线程数量用宏定义。修改起来还很麻烦。
      

  16.   

    我的连接量根本没上去,因为客户端程序conect之后马上就closesocket了to stjay,我故意把超时去掉了,因为我客户端不是长连接,连上马上就断开了
    因为有::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0);
    所以在没有数据到达时,客户端关闭了连接,GetQueuedCompletionStatus才能返回,所以才有了workerthread里面的判断
    if(pContext == NULL) 

    ......
    ......
    .......
    ReleaseBuffer(pBuffer); 
    }
    }to fangle6688 客户端closesocket了,服务器也接收到信号,也closesocket,释放资源了,系统资源怎么会耗尽呢?客户端是单线程阻塞式运行,我可以保证服务器程序没有内存泄漏
    to udknight 我要的不是连接数多,而是频繁断开连接不要让服务器死住,不知道你的客户端程序是怎么写的,用我的客户端程序,光盘里的服务器代码确实会死住请问如何上传代码到csdn让大家看到?我还真没做过
      

  17.   

    1 因为那个AcceptEx指定了要接收第一次到达数据,所有会一直等待数据到来。如果你不发数据的话,这个例子是不知道有连接的。这样就会出现连接无提示,断开有提示的实现。
    2 为避免客户端差异带来的问题,你可以用SocketTool进行调试。这样就能知道是客户端问题还是服务器问题
    3 放CSDN下载频道里面就行了。你有纳米盘的话,更好。
      

  18.   

    udknight 连接无提示,断开有提示,这样会有问题吗?acceptex没有正常返回?pbuffer被内核锁定着?
             实际应用的时候肯定会有频繁连接断开的现象吧,你是怎么处理的??现在发现服务器其实并没有死住,如果我把客户端程序关闭,等一会以后,又可以正常连接服务器了
    如果一直开着某一个客户端,别的客户端就没法再连接服务器了
    每个客户端都是连接之后马上断开服务器的,没有发送任何数据,这样acceptex会有问题?
    我试了传入0给acceptex,不让它等待数据到达,结果还是一样的,依然很郁闷
      

  19.   

    这样肯定是不行的。你连在线量都无法准确得到。不知道原书作者这个地方是怎么考虑的。这样写的话,那个连接提示的虚函数就简直没有用了。
    那样AcceptEx不收数据无法返回,就在那死等。他的内存有回收,只在最后的时候真正释放内存。
    你的客户端只是连接没有数据发送,AcceptEx还在那死等你的数据呢。如果只是测试连接数的话,这块修改下吧。
     
      

  20.   

    还是不明白AcceptEx应对连上就断开不发任何数据应该如何解决正在看spserver的代码,学习一下高手的东西
    争取做个像样的服务器出来,呵呵,再等等高手,稍后上来结贴
    感谢udknight
      

  21.   

    spserver的testiocpecho,无法响应客户端发送的数据,没有回声,这是为什么呢?udknight试了吗?
      

  22.   

    你确定你服务端closesocket的返回值不是ERROR_IO_PEDDING?首先你要搞清楚你的需求,再决定是否要使用AcceptExAcceptEx的作用基本上等于WSAAccept+WSARecv的捆绑
    其设计目的是为了防范DDOS攻击:某些人使用工具大量反复连接你的监听端口,导致你的服务器无暇响应正常用户的connect请求
    AcceptEx通过捆绑了一个WSARecv,可以让你在得到一个connect请求时立即验证其用户身份信息的需求能够更简洁地实现
    如此而已,你也完全可以不依赖AcceptEx自己实现这样的需求使用AcceptEx的缺点是你的监听逻辑复杂化
    通常监听操作都会独立运行在独立的线程中,这已经保障了监听操作的响应能力——没有其他可能导致阻塞的操作会影响到监听
    所以,实际上你的ListenSocket并不需要关联完成端口——只是为了使用AcceptEx,它才会和IOCP扯上关系
    假如,你的服务器不是针对大众用户的,没有防范DDOS攻击的需求
    你就没有必要使用AcceptEx来无端增加你的监听逻辑的复杂度,在独立线程中使用Accept足以满足正常的大并发访问需要
      

  23.   

    源码下了,还没有看。你在连接和数据接受那下断看看,echo应该和这个代码很类似。仔细看看AcceptEx看看是不是和这个情况一样。
      

  24.   

    连接上就断开在GetQueuedCompletionStatus能检测出来的啊。不管你是正常断开还是异常断开
      

  25.   

    http://groups.google.com/group/spserver
    找到了testiocpecho无响应的解决办法,spserver还是很好用的
    acceptex读了0,wsarecv读了了1,后续数据都是recv完成的,怪不得叫半异步半同步,呵呵
    搞清楚了,我自己写了个wsaaccept的代码,效率还不错,频繁断开连接也没死住
    问题解决,项目可以开展了,继续搞我的wince喽
    感谢各位的回答