65.9K
CodeProject 正在变化。 阅读更多。
Home

异步(非阻塞)客户端套接字包装器类,无需 MFC

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (6投票s)

2010 年 12 月 4 日

GPL3

2分钟阅读

viewsIcon

49782

downloadIcon

3923

一个 Win32 API 非阻塞套接字实现,客户端端。

AsyncSocketWin32.png

引言

我在尝试避免在简单的客户端应用程序中使 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: 首次发布。

最新版本可在 此处 获取。

© . All rights reserved.