投递Accept I/O请求问题

一开始的回忆 2008-11-05 02:13:29
我想问下,
::listen(sListen, 5);
1.是可以同时接收5个连接吗?如果同时有6个连接,会丢掉一个连接.
2.还是只接收5个连接,之后有连接也不接收了.

那么,用IOCP技术,在监听套节字上投递Accept I/O请求上,会有投递的Accept请求不够的问题.
那么,如果我一开始就投了5个,那这5个是并发操作吗,就是,如果我同时有6个连接,
而我只投了5个,所以会不够,是这样吗?

...全文
176 11 打赏 收藏 转发到动态 举报
写回复
用AI写文章
11 条回复
切换为时间正序
请发表友善的回复…
发表回复
scq2099yt 2008-11-05
  • 打赏
  • 举报
回复
分析得很透彻阿
Eleven 2008-11-05
  • 打赏
  • 举报
回复
学习了
Wenxy1 2008-11-05
  • 打赏
  • 举报
回复

我想问下,
::listen(sListen, 5);
1.是可以同时接收5个连接吗?如果同时有6个连接,会丢掉一个连接.
2.还是只接收5个连接,之后有连接也不接收了.
答: 5 是指backlog(积压值,指明backlog队列的长度),见《TCP/IP详解》卷一第195页。5就是说tcp的三次或四次据手连接已完成,等待应用层去接受。一般来说应用层去接受是很快的,就是调用API accept,所以很少会导致这个队列超出5的现象。当然要是达到5了,肯定后面的连接中的tcp握手过程会等待,具本上要看TCP/IP协议栈的实现。





僵哥 2008-11-05
  • 打赏
  • 举报
回复
这些非关键性的东西,完全不需要去考虑它.基本上设计的时候有多少个核给多少个就OK了.
僵哥 2008-11-05
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 SyzCools 的回复:]
那也就是说,一开始也就可以确定需要投多少个Accept请求了吗?
而不需要以后再增加了吗?

那么还有就是,5个核的服务器要投20个Accept请求会怎样?效率低?还是会怎么?
[/Quote]
没有绝对的.Accept不需要太多.理论上来讲,应该是多少个核投多少个,但是毕竟Accept处理还是需要时间的,所以也可以增加.
一开始的回忆 2008-11-05
  • 打赏
  • 举报
回复
那也就是说,一开始也就可以确定需要投多少个Accept请求了吗?
而不需要以后再增加了吗?

那么还有就是,5个核的服务器要投20个Accept请求会怎样?效率低?还是会怎么?
僵哥 2008-11-05
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 SyzCools 的回复:]
那,用IOCP技术,在监听套节字上投递Accept I/O请求上,为什么会有投递的Accept请求不够的问题.
[/Quote]
所谓的不够是指有大量用户同时请求连接,而在服务器还没有达到完全饱和的情况下,还完全可以必要去投递更多的Accept请求.
就好比一台服务器,总有有32个核,那么理论上一次性可以同时接受32个连接,如果你只投递了5个,那么同一时间就只有5个连接,从而并没有达到理论上的饱和值.
一开始的回忆 2008-11-05
  • 打赏
  • 举报
回复
BOOL CIOCPServer::Start(int nPort, int nMaxConnections, 
int nMaxFreeBuffers, int nMaxFreeContexts, int nInitialReads)
{
// 检查服务是否已经启动
if(m_bServerStarted)
return FALSE;

// 保存用户参数
m_nPort = nPort;
m_nMaxConnections = nMaxConnections;
m_nMaxFreeBuffers = nMaxFreeBuffers;
m_nMaxFreeContexts = nMaxFreeContexts;
m_nInitialReads = nInitialReads;

// 初始化状态变量
m_bShutDown = FALSE;
m_bServerStarted = TRUE;


// 创建监听套节字,绑定到本地端口,进入监听模式
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(m_nPort);
si.sin_addr.S_un.S_addr = INADDR_ANY;
if(::bind(m_sListen, (sockaddr*)&si, sizeof(si)) == SOCKET_ERROR)
{
m_bServerStarted = FALSE;
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, _ListenThreadProc, this, 0, NULL);

return TRUE;
}


这个是开始部分

DWORD WINAPI CIOCPServer::_ListenThreadProc(LPVOID lpParam)
{
CIOCPServer *pThis = (CIOCPServer*)lpParam;

// 先在监听套节字上投递几个Accept I/O
CIOCPBuffer *pBuffer;
for(int i=0; i<pThis->m_nInitialAccepts; i++)
{
pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);//为缓冲区对象申请内存
if(pBuffer == NULL)
return -1;
pThis->InsertPendingAccept(pBuffer); // 将一个I/O缓冲区对象插入到m_pPendingAccepts表中
pThis->PostAccept(pBuffer);// 在监听套节字上投递Accept请求
}

// 构建事件对象数组,以便在上面调用WSAWaitForMultipleEvents函数
HANDLE hWaitEvents[2 + MAX_THREAD];
int nEventCount = 0;
hWaitEvents[nEventCount ++] = pThis->m_hAcceptEvent;
hWaitEvents[nEventCount ++] = pThis->m_hRepostEvent;

// 创建指定数量的工作线程在完成端口上处理I/O
for(i=0; i<MAX_THREAD; i++)
{
hWaitEvents[nEventCount ++] = ::CreateThread(NULL, 0, _WorkerThreadProc, pThis, 0, NULL);
}

// 下面进入无限循环,处理事件对象数组中的事件
while(TRUE)
{
int nIndex = ::WSAWaitForMultipleEvents(nEventCount, hWaitEvents, FALSE, 60*1000, FALSE);

// 首先检查是否要停止服务
if(pThis->m_bShutDown || nIndex == WSA_WAIT_FAILED)
{
// 关闭所有连接
pThis->CloseAllConnections();
::Sleep(0); // 给I/O工作线程一个执行的机会
// 关闭监听套节字
::closesocket(pThis->m_sListen);
pThis->m_sListen = INVALID_SOCKET;
::Sleep(0); // 给I/O工作线程一个执行的机会

// 通知所有I/O处理线程退出
for(int i=2; i<MAX_THREAD + 2; i++)
{
::PostQueuedCompletionStatus(pThis->m_hCompletion, -1, 0, NULL);
}

// 等待I/O处理线程退出
::WaitForMultipleObjects(MAX_THREAD, &hWaitEvents[2], TRUE, 5*1000);

for(i=2; i<MAX_THREAD + 2; i++)
{
::CloseHandle(hWaitEvents[i]);
}

::CloseHandle(pThis->m_hCompletion);

pThis->FreeBuffers();
pThis->FreeContexts();
::ExitThread(0);
}

// 1)定时检查所有未返回的AcceptEx I/O的连接建立了多长时间
if(nIndex == WSA_WAIT_TIMEOUT)
{
pBuffer = pThis->m_pPendingAccepts;
while(pBuffer != NULL)
{
int nSeconds;
int nLen = sizeof(nSeconds);
// 取得连接建立的时间
::getsockopt(pBuffer->sClient,
SOL_SOCKET, SO_CONNECT_TIME, (char *)&nSeconds, &nLen);
// 如果超过2分钟客户还不发送初始数据,就让这个客户go away
if(nSeconds != -1 && nSeconds > 2*60)
{
closesocket(pBuffer->sClient);
pBuffer->sClient = INVALID_SOCKET;
}

pBuffer = pBuffer->pNext;
}
}
else
{
//001********************************************************************************************************************
nIndex = nIndex - WAIT_OBJECT_0;
WSANETWORKEVENTS ne;
int nLimit=0;
if(nIndex == 0) // 2)m_hAcceptEvent事件对象受信,说明投递的Accept请求不够,需要增加
{
::WSAEnumNetworkEvents(pThis->m_sListen, hWaitEvents[nIndex], &ne);
if(ne.lNetworkEvents & FD_ACCEPT)
{
nLimit = 50; // 增加的个数,这里设为50个
}
}
//002********************************************************************************************************************
else if(nIndex == 1) // 3)m_hRepostEvent事件对象受信,说明处理I/O的线程接受到新的客户
{
nLimit = InterlockedExchange(&pThis->m_nRepostCount, 0);
}
else if(nIndex > 1) // I/O服务线程退出,说明有错误发生,关闭服务器
{
pThis->m_bShutDown = TRUE;
continue;
}
//003********************************************************************************************************************
// 投递nLimit个AcceptEx I/O请求
int i = 0;
while(i++ < nLimit && pThis->m_nPendingAcceptCount < pThis->m_nMaxAccepts)
{
pBuffer = pThis->AllocateBuffer(BUFFER_SIZE);
if(pBuffer != NULL)
{
pThis->InsertPendingAccept(pBuffer);
pThis->PostAccept(pBuffer);
}
}
//**********************************************************************************************************************
}
}
return 0;
}



001中不是说:// 2)m_hAcceptEvent事件对象受信,说明投递的Accept请求不够,需要增加吗
然后在003中又:投递nLimit个AcceptEx I/O请求
怎么理解?

主要代码给出了,没有给完,太多了.注释也有,先谢谢大家了
jingtan 2008-11-05
  • 打赏
  • 举报
回复
[Quote=引用 2 楼 SyzCools 的回复:]
那,用IOCP技术,在监听套节字上投递Accept I/O请求上,为什么会有投递的Accept请求不够的问题.
[/Quote]
是Accept请求不够?accept返回错误吗? 当套接字标记为非阻塞的时候,accept可能会返回SOCKET_ERROR,GetLastError()取得错误值WSAEWOULDBLOCK.表示当前没有连接请求.
一开始的回忆 2008-11-05
  • 打赏
  • 举报
回复
那,用IOCP技术,在监听套节字上投递Accept I/O请求上,为什么会有投递的Accept请求不够的问题.
僵哥 2008-11-05
  • 打赏
  • 举报
回复
[Quote=引用楼主 SyzCools 的帖子:]
我想问下,
::listen(sListen, 5);
1.是可以同时接收5个连接吗?如果同时有6个连接,会丢掉一个连接.
2.还是只接收5个连接,之后有连接也不接收了.

那么,用IOCP技术,在监听套节字上投递Accept I/O请求上,会有投递的Accept请求不够的问题.
那么,如果我一开始就投了5个,那这5个是并发操作吗,就是,如果我同时有6个连接,
而我只投了5个,所以会不够,是这样吗?
[/Quote]
当服务器来不及accept的时候,在等待accept的队列当中同一时间最多有5个.

如果使用AcceptEx,则在系统底层已经帮你完成了Accept操作,然后才通知应用层.
源码,有注释 // IOCPServer.h: interface for the CIOCPServer class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_) #define AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #pragma once #pragma comment(lib,"Ws2_32.lib") #pragma comment(lib,"Mswsock.lib") #include #include #define BUFFER_SIZE 1024*4//I/O请求的缓冲区大小 #define MAX_THREAD 2 //I/O服务器线程数量 //缓冲区对象,它包含了在套接字上处理I/O操作的必要信息 struct CIOCPBuffer { WSAOVERLAPPED ol; SOCKET sClient; //AcceptEx接收的客户套接字 char *buff; //I/0操作使用的缓冲区 int nLen; //buff缓冲区(使用的)大小 ULONG nSequenceNumber;//此I/O的序列号 int nOperation; //操作类型 CIOCPBuffer *pNext; }; //per-Handle数据,它包含了一个套接字的信息 struct CIOCPContext { SOCKET s; //套接字句柄 SOCKADDR_IN addrLocal; //连接的本地地址 SOCKADDR_IN addrRemote;//连接的远程地址 BOOL bClosing; //套接字是否关闭 int nOutStandingRecv; //此套接字上抛出的重叠操作的数量 int nOutStandingSend; ULONG nReadSequence; //安排给接受的下一个序列号 ULONG nCurrentReadSequence;//当前要读的序列号 CIOCPBuffer *pOutOfOrderReads;//记录没有按顺序完成的读I/O CRITICAL_SECTION Lock; //保护这个结构 CIOCPContext *pNext; }; class CIOCPServer //处理线程 { public: CIOCPServer(void); ~CIOCPServer(void); //开始服务 BOOL Start(int nPort=4567,int nMaxConnections=2000, int nMaxFreeBuffers=200,int nMaxFreeContexts=100,int nInitialReads=4); //停止服务 void Shutdown(); //关闭一个连接和关闭所有连接 void CloseAConnection(CIOCPContext *pContext); void CloseAllConnection(); //取得当前的连接数量 ULONG GetCurrentConnection() { return m_nCurrentConnection; }; //向指定客户发送文本 BOOL SendText(CIOCPContext *pContext,char *pszText,int nLen); protected: //申请和释放缓冲区对象 CIOCPBuffer*AllocateBuffer(int nLen); void ReleaseBuffer(CIOCPBuffer *pBuffer); //申请和释放套接字上下文 CIOCPContext *AllocateContext(SOCKET s); void ReleaseContext(CIOCPContext *pContext); //释放空闲缓冲区对象列表和空闲上下文对象列表 void FreeBuffers(); void FreeContexts(); //向连接列表中添加一个连接 BOOL AddAConnection(CIOCPContext *pContext); //插入和移除未决的接受请求 BOOL InsertPendingAccept(CIOCPBuffer *pBuffer); BOOL RemovePendingAccept(CIOCPBuffer *pBuffer); //取得下一个要读取的 CIOCPBuffer *GetNextReadBuffer(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //投递接受I/O,发送I/0,接受I/O BOOL PostAccept(CIOCPBuffer *pBuffer); BOOL PostSend(CIOCPContext *pContext,CIOCPBuffer *pBuffer); BOOL PostRecv(CIOCPContext *pContext,CIOCPBuffer *pBuffer); //事件通知函数 void HandleIO(DWORD dwKey,CIOCPBuffer *pBuffer,DWORD dwTrans,int nError); //建立一个新的连接 virtual void OnConnectionEstablished(CIOCPContext *pContext,CIOCPBuffer*); //一个连接关闭 virtual void OnConnectionClosing(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上发生错误 virtual void OnConnectionError(CIOCPContext *pContext,CIOCPBuffer*,int nError); //在一个连接上的读操作完成 virtual void OnReadCompleted(CIOCPContext *pContext,CIOCPBuffer*); //在一个连接上写操作完成 virtual void OnWriteCompleted(CIOCPContext *pContext,CIOCPBuffer*); protected: //记录空闲结构信息 CIOCPBuffer *m_pFreeBufferList; CIOCPContext *m_pFreeContextList; int m_nFreeBufferCount; int m_nFreeContextCount; CRITICAL_SECTION m_FreeBufferListLock; CRITICAL_SECTION m_FreeContextListLock; //记录抛出的Accept请求 CIOCPBuffer *m_pPendingAccepts; long m_nPendingAcceptCount; CRITICAL_SECTION m_PendingAcceptsLock; //记录连接列表 CIOCPContext *m_pConnectionList; int m_nCurrentConnection; CRITICAL_SECTION m_ConnectionListLock; //用于投递Accept请求 HANDLE m_hAcceptEvent; HANDLE m_hRepostEvent; LONG m_nRepostCount; //服务器监听端口 int m_nPort; int m_nInitialAccepts; int m_nInitialReads; int m_nMaxAccepts; int m_nMaxSends; int m_nMaxFreeBuffers; int m_nMaxFreeContexts; int m_nMaxConnections; //监听线程 HANDLE m_hListenThread; //完成端口句柄 HANDLE m_hCompletion; //监听套接字句柄 SOCKET m_sListen; //AcceptEx函数地址 LPFN_ACCEPTEX m_lpfnAcceptEx; //GetAcceptExSockaddrs函数地址 LPFN_GETACCEPTEXSOCKADDRS m_lpfnGetAcceptExSockaddrs; //用于通知监听线程退出 BOOL m_bShutDown; //记录服务是否启动 BOOL m_bServerStarted; private://线程函数 static DWORD WINAPI _ListenThreadProc(LPVOID lpParam); static DWORD WINAPI _WorkerThreadProc(LPVOID lpParam); }; #endif // !defined(AFX_IOCPSERVER_H__3BBFA68A_31AC_42BB_806B_0D858AF0A861__INCLUDED_)
IOCP全称I/O Completion Port,中文译为I/O完成端口。IOCP是一个异步I/O的API,它可以高效地将I/O事件通知给应用程序。与使用select()或是其它异步方法不同的是,一个套接字[socket]与一个完成端口关联了起来,然后就可继续进行正常的Winsock操作了。然而,当一个事件发生的时候,此完成端口就将被操作系统加入一个队列中。然后应用程序可以对核心层进行查询以得到此完成端口。 大体上来讲,使用完成端口只用遵循如下几个步骤: (1) 调用 CreateIoCompletionPort() 函数创建一个完成端口,而且在一般情况下,我们需要且只需要建立这一个完成端口,把它的句柄保存好,我们今后会经常用到它…… (2) 根据系统中有多少个处理器,就建立多少个工作者(为了醒目起见,下面直接说Worker)线程,这几个线程是专门用来和客户端进行通信的,目前暂时没什么工作; (3) 下面就是接收连入的Socket连接了,这里有两种实现方式:一是和别的编程模型一样,还需要启动一个独立的线程,专门用来accept客户端的连接请求;二是用性能更高更好的异步AcceptEx()请求。 (4) 每当有客户端连入的时候,我们就还是得调用CreateIoCompletionPort()函数,这里却不是新建立完成端口了,而是把新连入的Socket(也就是前面所谓的设备句柄),与目前的完成端口绑定在一起。 至此,我们其实就已经完成了完成端口的相关部署工作了,嗯,是的,完事了,后面的代码里我们就可以充分享受完成端口带给我们的巨大优势,坐享其成了,是不是很简单呢? (5) 例如,客户端连入之后,我们可以在这个Socket上提交一个网络请求,例如WSARecv(),然后系统就会帮咱们乖乖的去执行接收数据的操作,我们大可以放心的去干别的事情了; (6) 而此时,我们预先准备的那几个Worker线程就不能闲着了, 我们在前面建立的几个Worker就要忙活起来了,都需要分别调用GetQueuedCompletionStatus() 函数在扫描完成端口的队列里是否有网络通信的请求存在(例如读取数据,发送数据等),一旦有的话,就将这个请求从完成端口的队列中取回来,继续执行本线程中后面的处理代码,处理完毕之后,我们再继续投递下一个网络通信的请求就OK了,如此循环。
///////////////////////////////////////////////////////////////// // 初始化Socket bool CIOCPModel::_InitializeListenSocket() { // AcceptEx 和 GetAcceptExSockaddrs 的GUID,用于导出函数指针 GUID GuidAcceptEx = WSAID_ACCEPTEX; GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; // 服务器地址信息,用于绑定Socket struct sockaddr_in ServerAddress; // 生成用于监听的Socket的信息 m_pListenContext = new PER_SOCKET_CONTEXT; // 需要使用重叠IO,必须得使用WSASocket来建立Socket,才可以支持重叠IO操作 m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (INVALID_SOCKET == m_pListenContext->m_Socket) { this->_ShowMessage("初始化Socket失败,错误代码: %d.\n", WSAGetLastError()); return false; } else { TRACE("WSASocket() 完成.\n"); } // 将Listen Socket绑定至完成端口中 if( NULL== CreateIoCompletionPort( (HANDLE)m_pListenContext->m_Socket, m_hIOCompletionPort,(DWORD)m_pListenContext, 0)) { this->_ShowMessage("绑定 Listen Socket至完成端口失败!错误代码: %d/n", WSAGetLastError()); RELEASE_SOCKET( m_pListenContext->m_Socket ); return false; } else { TRACE("Listen Socket绑定完成端口 完成.\n"); } // 填充地址信息 ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress)); ServerAddress.sin_family = AF_INET; // 这里可以绑定任何可用的IP地址,或者绑定一个指定的IP地址 //ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); ServerAddress.sin_addr.s_addr = inet_addr(m_strIP.GetString()); ServerAddress.sin_port = htons(m_nPort); // 绑定地址和端口 if (SOCKET_ERROR == bind(m_pListenContext->m_Socket, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress))) { this->_ShowMessage("bind()函数执行错误.\n"); return false; } else { TRACE("bind() 完成.\n"); } // 开始进行监听 if (SOCKET_ERROR == listen(m_pListenContext->m_Socket,SOMAXCONN)) { this->_ShowMessage("Listen()函数执行出现错误.\n"); return false; } else { TRACE("Listen() 完成.\n"); } // 使用AcceptEx函数,因为这个是属于WinSock2规范之外的微软另外提供的扩展函数 // 所以需要额外获取一下函数的指针, // 获取AcceptEx函数指针 DWORD dwBytes = 0; if(SOCKET_ERROR == WSAIoctl

18,356

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 网络编程
c++c语言开发语言 技术论坛(原bbs)
社区管理员
  • 网络编程
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧