异步(非阻塞)客户端套接字包装器类,无需 MFC
一个 Win32 API 非阻塞套接字实现,客户端端。
引言
我在尝试避免在简单的客户端应用程序中使 recv
挂起/无限期阻塞时创建了这个类,而没有使用 MFC。recv
可能会因为各种原因而挂起;最常见的原因是在不恰当的时候调用 recv
,或者在远程服务器没有发送它应该发送的数据时调用它,例如:因为请求格式不正确。
Using the Code
#include "XSocket.h"
....
CXSocket mySock;
if (!mySock.Init()) //initalize winsocks
return false;
//////////////////////////////////////////////////////////////////////////
if (!mySock.Connect(pHostName, nPort))
{
int nError = mySock.GetLastError();
return false;
}
/////////////////////////////////////////////////////////////////////////
// Send a buffer, 5 seconds timeout
// further error checking omitted for previty
int nLen = 0;
if (mySock.Send(szBuff, strlen(szBuff), nLen, 5000) != E_XSOCKET_SUCCESS)
return false;
/////////////////////////////////////////////////////////////////////////
// Receive server's response, 5 seconds time out
// last argument is optional, if not used Rec will return immediately
do
{
if (mySock.Recv(szBuff, sizeof(szBuff) - 1, nLen, 5000)
!= E_XSOCKET_SUCCESS)
{
break;
}
}
while (nLen == sizeof(szBuff));
//////////////////////////////////////////////////////////////////////////
// Optional: explicitly close the socket, if not called socket will be closed
// auto on destruction
mySock.Close();
取消请求,无论超时与否
mySock.Abort();
检查是否有可用于读取的数据;这可以在成功 Connect
之后随时调用
long lLen = mySocket.GetLenDataAvail();
关注点
我们为套接字启用非阻塞模式,并同时通过调用将一个事件对象附加到它
WSAEventSelect(hSocket, hEvent, FD_READ | FD_WRITE | FD_CLOSE)
这将把 hEvent
附加到 hSocket
,以便当有新的读取、写入和关闭事件时,hEvent
将被发出信号。 此外,WSAEventSelect
会自动将 hSocket
设置为非阻塞模式。
发送数据
数据发送代码如下所示
int CXSocket::Send(const char* pBuff, int nLen, int& nLenSent, DWORD dwTimeOut)
{
_BEGIN:
int nRet = 0;
m_nLastError = 0;
if ((nRet = send(m_hSocket, pBuff, nLen, 0)) > 0 ||
(m_nLastError = WSAGetLastError()) != WSAEWOULDBLOCK)
return E_XSOCKET_SUCCESS;
/////////////////////////////////////////////////////////////
HANDLE arrHandles[2];
arrHandles[0] = m_eventNet.GetEvent();
arrHandles[1] = m_eventStop.GetEvent();
DWORD dwWaitRes =
WaitForMultipleObjects(2, arrHandles, FALSE, dwTimeOut);
if (dwWaitRes == WAIT_OBJECT_0 + 1)
return E_XSOCKET_ABORTED;
else if (dwWaitRes != WAIT_OBJECT_0)
return E_XSOCKET_TIMEDOUT;
/////////////////////////////////////////////////////////////
WSANETWORKEVENTS myNetEvents;
if (WSAEnumNetworkEvents(m_hSocket, m_eventNet.GetEvent(),
&myNetEvents) != 0)
{
m_nLastError = WSAGetLastError();
return E_XSOCKET_SOCKERR;
}
if ((myNetEvents.lNetworkEvents & FD_WRITE) != FD_WRITE)
{
goto _BEGIN;
}
if (myNetEvents.iErrorCode[FD_WRITE_BIT] != 0)
return E_XSOCKET_SOCKERR;
///////////////////////////////////////////////////////////
nLenSent = send(m_hSocket, pBuff, nLen, 0);
return E_XSOCKET_SUCCESS;
}
我们首先发出一个 Send
请求;如果我们的请求可以得到满足,数据将立即发送,并且 Send
的返回值将是实际发送的数据长度。 否则,我们的请求将被排队,并且 Send
将返回 WSAEWOULDBLOCK
。 Winsock 子系统然后会通过发出先前附加到套接字的事件来通知我们何时可以发送我们的数据。 此时,我们调用 WSAEnumNetworkEvents
来确保事件是 FD_WRITE
;如果不是,我们继续等待 FD_WRITE
(如果 dwTimeOut
大于零)。
接收数据
数据接收代码如下所示
int CXSocket::Recv(char* pBuff, int nLen, int& nLenReceived, DWORD dwTimeOut)
{
_BEGIN:
/////////////////////////////////////////////////////////////////
HANDLE arrHandles[2];
arrHandles[0] = m_eventNet.GetEvent();
arrHandles[1] = m_eventStop.GetEvent();
DWORD dwWaitRes =
WaitForMultipleObjects(2, arrHandles, FALSE, dwTimeOut);
if (dwWaitRes == WAIT_OBJECT_0 + 1)
return E_XSOCKET_ABORTED;
else if (dwWaitRes != WAIT_OBJECT_0)
return E_XSOCKET_TIMEDOUT;
//////////////////////////////////////////////////////////////////
WSANETWORKEVENTS myNetEvents;
if (WSAEnumNetworkEvents(m_hSocket,
m_eventNet.GetEvent(), &myNetEvents) != 0)
{
m_nLastError = WSAGetLastError();
return E_XSOCKET_SOCKERR;
}
if ((myNetEvents.lNetworkEvents & FD_READ) != FD_READ)
goto _BEGIN;
if (myNetEvents.iErrorCode[FD_READ_BIT] != 0)
return E_XSOCKET_SOCKERR;
///////////////////////////////////////////////////////////////////
if ((nLenReceived =
recv(m_hSocket, pBuff, nLen, 0)) == WSAEWOULDBLOCK)
{
nLenReceived = 0;
return E_XSOCKET_NOMOREDATA;
}
return E_XSOCKET_SUCCESS;
}
我们所做的是等待一个 FD_READ
事件。 如果我们收到它,我们调用 recv
来读取可用数据。
历史
- 03 - Dec - 10: 首次发布。
最新版本可在 此处 获取。