在accept、read、write等I/O操作的时候,如果没有符合条件的资源就一直等待下去,直到有资源为止。这就会造成堵塞,甚至使程序死掉;
解决堵塞,可以使用异步I/O模型解决,下面列举5种,
1) 选择模型(select)(主要用于服务端)
2) 异步选择(WSAAsyncSelect)
3) 事件选择(WSAEventSelect)
4) 重叠I/O(Overlapped I/O)
5) 完成端口(Completion Port)(+线程池,效率最高)
1)
select函数原型:
int select(
__in int nfds,
__in_out fd_set* readfds,
__in_out fd_set* writefds,
__in_out fd_set* exceptfds,
__in const timeval* timeout
)
功能:
这个函数可以去监视socket集合,判断socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在socket处于阻塞模型中时,在send、recv或accept等I/O操作过程中被迫进入“锁定状态”;同时防止在套接字处于非堵塞模型中时,产生WSAWOULDBLOCK错误。
参数说明:
nfds:这个参数是为了兼容Berkeley套接字,一般可以忽略,设为0
readfds:检测socket可读性,有数据可以读取
writefds:检测socket可写性,可向socket发送数据
exceptfds:检测异常socket
timeout:监视时间,超过设定时间,则返回
返回值:
如果select成功完成,会在fdset结构中,返回为返程的I/O操作的套接字句柄的总量
若超过timeval设定的时间,遍返回0,若调用select失败,返回SOCKET_ERROE,则应调用WSAGetLastError获取错误码
函数使用:
当要测试一个套接字是否“可读”,必须将自己的套接字添加到readfds中,然后调用select函数等待完成返回,然后再次判断自己的套接字是否在readfds集合中,如果在,则表明套接字可读,就可立即从它上面读取数据
这里将select函数封装起来了
BOOL Socket_Select(SOCKET hSocket,int nTimeout,BOOL bRead)
{
fd_set fdset;
timeval tv;
FD_ZERO(&fdset); //初始化
FD_SET(hSocket,&fdset); //将套接字hSocket加入到fdset中
nTimeout = (nTimeout > 1000 || nTimeout <= 0) ? 1000 : nTimeout;
tv.tv_sec = 0;
tv.tv_usec = nTimeout;
int nRet=0;
if (bRead)
{
nRet = select(0,&fdset,NULL,NULL,&tv);
}
else
{
nRet = select(0,NULL,&fdset,NULL,&tv);
}
if (0 >= nRet)
{
return FALSE;
}
else if (FD_ISSET(hSocket,&fdset)) //检查hSocket是否存在fdset中
{
return TRUE;
}
return FALSE;
}
到这里的时候我似乎有点明白堵塞的时候为什么程序会卡死,监听的时候如果不开启一个新的线程,在堵塞的时候就是堵塞主线程,然后就使界面卡死,而开启一个新的线程的话,即使堵塞也只是堵塞这个子线程,不会堵塞主线程,从而界面不会卡死。(不知猜想是否正确,如有错误请指出)
DWORD WINAPI ListenThreadProc(PVOID lParam)
{
CCheatRoomDlg *pMainWnd = (CCheatRoomDlg *)lParam;
ASSERT(pMainWnd != NULL);
pMainWnd -> m_ListenSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if (pMainWnd -> m_ListenSocket == INVALID_SOCKET)
{
MessageBox(NULL,TEXT("新建socket失败"),TEXT("Error"),MB_OK);
return FALSE;
}
int nPort = pMainWnd -> GetDlgItemInt(IDC_EDIT_HOST_PORT);
if ( nPort < 0 || nPort > 65535)
{
MessageBox(NULL,TEXT("端口号超出0~64435"),TEXT("Error"),MB_OK);
return FALSE;
}
sockaddr_in service;
service.sin_port = htons(nPort);
service.sin_addr.S_un.S_addr = INADDR_ANY;
service.sin_family = AF_INET;
if (SOCKET_ERROR == bind(pMainWnd -> m_ListenSocket,(sockaddr *)&service,sizeof(sockaddr_in)))
{
MessageBox(NULL,TEXT("绑定端口失败"),TEXT("Error"),MB_OK);
goto __ERROR_END;
}
if (SOCKET_ERROR == listen(pMainWnd -> m_ListenSocket,5)) //设定最大监听数位5
{
MessageBox(NULL,TEXT("监听失败"),TEXT("Error"),MB_OK);
goto __ERROR_END;
}
while (TRUE)
{
if (Socket_Select(pMainWnd -> m_ListenSocket,100,TRUE)) //可读性socket检测
{
sockaddr_in ClientAddr;
int Len = sizeof(sockaddr_in);
SOCKET accSock = accept(pMainWnd -> m_ListenSocket,(sockaddr *)&ClientAddr,&Len);
if (SOCKET_ERROR == accSock)
{
continue;
}
//Do Something
Sleep(100);
}
}
__ERROR_END:
closesocket(pMainWnd -> m_ListenSocket);
return TRUE;
}