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
::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
workerthread用到的是完成端口线程池,你这里的代码是怎么样的?
{
// 创建监听套节字,绑定到本地端口,进入监听模式
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死循环,也不发送接收数据
连一会之后,服务器也不报错,感觉像是死住了,客户端关了再开也没法继续建立连接
盼望高手回复
换了几台电脑试,其中还有2003 server的,都不行,应该不是系统的问题
可是我感觉又不像是listen的问题,郁闷死了
就是因为原书的代码会死住,所以我才不断地简化代码,想查找问题,已经简化到这程度了,还是不行
::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0);
好像是它造成的问题,我还没确定,进一步测试一下再上来回复
注意第四个参数如:
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的连接时间,或者心跳包之类的
超时没数据收发的将其关闭
这个IOCP模型有够烂的,这样搞法,系统SCOKET资源很快就耗尽了
这是为了检测是否需要投递更多的AcceptEx
udknight,原书的代码编译后不停地连接断开也会死住的,你没发现问题?
就是因为原书的代码会死住,所以我才不断地简化代码,想查找问题,已经简化到这程度了,还是不行
原书代码我写过客户端进行调试过,同时几百个连接都是没有问题的。虽然他的断开检测那写的不严谨,不过还是能检测出正常关闭和异常关闭的。缺点就是那块写的很粗糙,有时候会莫名其妙被踢。你可以把代码传到CSDN上面,这样别人就可以调试了。这个例子最讨厌的就是线程数量用宏定义。修改起来还很麻烦。
因为有::CreateIoCompletionPort((HANDLE)m_sListen, m_hCompletion, (DWORD)0, 0);
所以在没有数据到达时,客户端关闭了连接,GetQueuedCompletionStatus才能返回,所以才有了workerthread里面的判断
if(pContext == NULL)
{
......
......
.......
ReleaseBuffer(pBuffer);
}
}to fangle6688 客户端closesocket了,服务器也接收到信号,也closesocket,释放资源了,系统资源怎么会耗尽呢?客户端是单线程阻塞式运行,我可以保证服务器程序没有内存泄漏
to udknight 我要的不是连接数多,而是频繁断开连接不要让服务器死住,不知道你的客户端程序是怎么写的,用我的客户端程序,光盘里的服务器代码确实会死住请问如何上传代码到csdn让大家看到?我还真没做过
2 为避免客户端差异带来的问题,你可以用SocketTool进行调试。这样就能知道是客户端问题还是服务器问题
3 放CSDN下载频道里面就行了。你有纳米盘的话,更好。
实际应用的时候肯定会有频繁连接断开的现象吧,你是怎么处理的??现在发现服务器其实并没有死住,如果我把客户端程序关闭,等一会以后,又可以正常连接服务器了
如果一直开着某一个客户端,别的客户端就没法再连接服务器了
每个客户端都是连接之后马上断开服务器的,没有发送任何数据,这样acceptex会有问题?
我试了传入0给acceptex,不让它等待数据到达,结果还是一样的,依然很郁闷
那样AcceptEx不收数据无法返回,就在那死等。他的内存有回收,只在最后的时候真正释放内存。
你的客户端只是连接没有数据发送,AcceptEx还在那死等你的数据呢。如果只是测试连接数的话,这块修改下吧。
争取做个像样的服务器出来,呵呵,再等等高手,稍后上来结贴
感谢udknight
其设计目的是为了防范DDOS攻击:某些人使用工具大量反复连接你的监听端口,导致你的服务器无暇响应正常用户的connect请求
AcceptEx通过捆绑了一个WSARecv,可以让你在得到一个connect请求时立即验证其用户身份信息的需求能够更简洁地实现
如此而已,你也完全可以不依赖AcceptEx自己实现这样的需求使用AcceptEx的缺点是你的监听逻辑复杂化
通常监听操作都会独立运行在独立的线程中,这已经保障了监听操作的响应能力——没有其他可能导致阻塞的操作会影响到监听
所以,实际上你的ListenSocket并不需要关联完成端口——只是为了使用AcceptEx,它才会和IOCP扯上关系
假如,你的服务器不是针对大众用户的,没有防范DDOS攻击的需求
你就没有必要使用AcceptEx来无端增加你的监听逻辑的复杂度,在独立线程中使用Accept足以满足正常的大并发访问需要
找到了testiocpecho无响应的解决办法,spserver还是很好用的
acceptex读了0,wsarecv读了了1,后续数据都是recv完成的,怪不得叫半异步半同步,呵呵
搞清楚了,我自己写了个wsaaccept的代码,效率还不错,频繁断开连接也没死住
问题解决,项目可以开展了,继续搞我的wince喽
感谢各位的回答