现在的位置: 首页 > 综合 > 正文

化繁为简系列原创教程

2013年06月05日 ⁄ 综合 ⁄ 共 4303字 ⁄ 字号 评论关闭

这是一个小巧的客户端套接字类,类名、函数名和变量名均采用匈牙利命名法。小写的x代表我的姓氏首字母(谢欣能),个人习惯而已,如有雷同,纯属巧合。

CxClientSocket的定义如下:

class XIOCTRL_CLASS CxClientSocket : public CxSocket
{
public:
    CxClientSocket();
    virtual ~CxClientSocket();
    void operator=(SOCKET s) { m_socket = s; }

public:
    virtual BOOL Connect(LPCSTR lpszIPAddr, int nPort);
    virtual BOOL IsConnected();
    BOOL DisConnect();

    BOOL Send(LPBYTE lpbtData, DWORD dwSize);
    BOOL Recv(LPBYTE lpbtData, DWORD dwSize);
    BOOL GetRemoteHost(LPSTR lpszIPAddr);

protected:
    BOOL Listen();
    void DisListen();
    static DWORD WINAPI ListenProc(LPVOID lpParam);
    void OnListen();

protected:
    HANDLE m_hThreadListen;
    BOOL m_bExitThreadListen;
};

由于这个类被封装在动态库里面,所以类名前使用了导出标志XIOCTRL_CLASS,读者在使用时完全可以去掉。这个类的定义被放在一个包含很多类定义的头文件中,我没有单独为它写头文件,所以它的定义部分代码看上去没有上下文。

CxClientSocket的实现如下:

CxClientSocket::CxClientSocket()
    : m_hThreadListen(NULL)
    , m_bExitThreadListen(TRUE)
{
    
}

CxClientSocket::~CxClientSocket()
{

}

BOOL CxClientSocket::GetRemoteHost(LPSTR lpszIPAddr)
{
    sockaddr_in sa = {0};
    int nSize = sizeof(sa);
    int iResult = getpeername(m_socket, (sockaddr*)&sa, &nSize);
    if (iResult == SOCKET_ERROR)
        return FALSE;
    else
    {
        strcpy(lpszIPAddr, inet_ntoa(sa.sin_addr));
        return TRUE;
    }
}

BOOL CxClientSocket::Listen()
{
    if (m_hThreadListen != NULL)
        return FALSE;

    m_bExitThreadListen = FALSE;
    DWORD dwThreadID;
    m_hThreadListen = CreateThread(NULL, 0, ListenProc, this, 0, &dwThreadID);
    
    return (m_hThreadListen != NULL);
}

void CxClientSocket::DisListen()
{
    if (m_hThreadListen != NULL)
    {
        m_bExitThreadListen = TRUE;

        WaitForSingleObject(m_hThreadListen, INFINITE);
        CloseHandle(m_hThreadListen);
        m_hThreadListen = NULL;
    }
}

DWORD WINAPI CxClientSocket::ListenProc(LPVOID lpParam)
{
    DWORD dwID = ::GetCurrentThreadId();
    char szDebug[MAX_PATH];
    sprintf(szDebug, "The thread 0x%X(CxClientSocket::ListenProc) has entered.\n", dwID);
    OutputDebugString(szDebug);

    CxClientSocket* pThis = (CxClientSocket*)lpParam;
    if (pThis != NULL)
        pThis->OnListen();
    
    sprintf(szDebug, "The thread 0x%X(CxClientSocket::ListenProc) has exited.\n", dwID);
    OutputDebugString(szDebug);

    return 0;
}

void CxClientSocket::OnListen()
{
    fd_set fd;
    FD_ZERO(&fd);
    FD_SET(m_socket, &fd);
    
    /*struct timeval timet;
    timet.tv_sec = 1;
    timet.tv_usec = 0;*/
    
    while (!m_bExitThreadListen)
    {
        int i = select((int)m_socket, &fd, NULL, NULL, NULL);
        if (i > 0)
        {
            if (m_wndproc != NULL)
            {
                m_wndproc(m_msg.hwnd, m_msg.message,
                (WPARAM)m_socket, (LPARAM)MAKELPARAM(FD_READ, 0));
            }
        }
    }
}

BOOL CxClientSocket::Connect(LPCSTR lpszIPAddr, int nPort)
{
    DisConnect();

    if (m_socket == INVALID_SOCKET)
        m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_socket == INVALID_SOCKET)
        return FALSE;
    
    sockaddr_in service = {0};
    service.sin_family = AF_INET;
    service.sin_addr.s_addr = inet_addr(lpszIPAddr);
    service.sin_port = htons(nPort);

    int iResult = connect(m_socket, (SOCKADDR*)&service, sizeof(service));
    if (iResult == SOCKET_ERROR)
    {
        DisConnect();
        DWORD dwError = WSAGetLastError();
        return FALSE;
    }
    
    long lEvent = FD_WRITE | FD_CLOSE;
    if (m_wndproc == NULL)
        lEvent |= FD_READ;
    else
        Listen();

    SelectEvent(lEvent);

    return TRUE;
}

BOOL CxClientSocket::DisConnect()
{
    if (m_socket == INVALID_SOCKET)
        return TRUE;

    if (IsConnected())
    {
        int iResult = shutdown(m_socket, SD_BOTH);
        if (iResult == SOCKET_ERROR)
            return FALSE;
    }

    DisListen();

    int iResult = closesocket(m_socket);
    if (iResult == SOCKET_ERROR)
        return FALSE;
    
    m_socket = INVALID_SOCKET;
    return TRUE;
}

BOOL CxClientSocket::IsConnected()
{
    sockaddr_in saCur = {0};
    int nLen = sizeof(saCur);
    int iResult = getpeername(m_socket, (SOCKADDR*)&saCur, &nLen);
    return (iResult != SOCKET_ERROR);
}

BOOL CxClientSocket::Send(LPBYTE lpbtData, DWORD dwSize)
{
    DWORD nCount = 0, nToSend;
    int iRet;
    LPBYTE lpbtIterator;
    DWORD dwError;

    while (nCount != dwSize)
    {
        nToSend = dwSize - nCount;
        lpbtIterator = &lpbtData[nCount];
        iRet = send(m_socket, (const char*)lpbtIterator, nToSend, 0);
        if (iRet != SOCKET_ERROR)
            nCount += iRet;
        else // something wrong
        {
            dwError = WSAGetLastError();
            if (dwError != WSAEWOULDBLOCK)
                break;
        }
    }
    
    return (nCount == dwSize);
}

BOOL CxClientSocket::Recv(LPBYTE lpbtData, DWORD dwSize)
{
    DWORD nCount = 0;
    int nToReceive, iRet;
    LPBYTE lpbtIterator;
    DWORD dwError;

    while (nCount != dwSize)
    {
        nToReceive = dwSize - nCount;
        lpbtIterator = &lpbtData[nCount];
        iRet = recv(m_socket, (char*)lpbtIterator, nToReceive, 0);
        if (iRet != SOCKET_ERROR)
        {
            if (iRet == 0)    // disconnect by remote host
            {
                //dwError = WSAGetLastError();    // No error
                WSASetLastError(WSAENOTCONN);
                break;
            }
            else
                nCount += iRet;
        }
        else // something wrong
        {
            dwError = WSAGetLastError();
            if (dwError != WSAEWOULDBLOCK)
                break;
        }
    }
    
    return (nCount == dwSize);
}

这个类的实现被放在一个包含很多类实现的CPP文件中,没有单独为它写CPP文件,所以它的实现部分代码看上去没有上下文(比如头文件包含、宏定义等等)。这个类的实现部分的代码不多,总共210多行。实现了(断开)连接服务端、发送接收数据以及侦听接收缓存区数据的功能(以消息响应或回调函数的方式通知上层程序处理接收缓存区数据)。

我写的很多实用类都非常简洁,一般都没有注释,有也是中英文混搭两句,大家习惯就好。To be continued...

抱歉!评论已关闭.