WindowSocket之异步IO----Select

在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;
}









你可能感兴趣的:(socket,select)