[C++]-Windows下Socket連接之客戶端

連接

接收

發送

 

在《[C++]-Windows下Socket連接之服務端》中介紹了Windows下Socket編程的一些基本知識與服務端實現,現在介紹一下客戶端的實現。相比於服務端,客戶端流程相對簡單些,主要就是:

  • 連接服務端;

  • 收發消息

此客戶端實現,除發送接口外,其他的都使用IOCP(I/O Completion Port,I/O完成端口)接口WSAXXX。IOCP是性能良好的I/O模型,可以支持大併發(通過完成端口,避免大量線程的創建),更適合在服務端使用。

等待服務端應答及退出事件,都是通過C++條件變量實現的(參見《條件變量condition_variable存在的一些問題》)

XuEvent g_evtQuit;
XuEvent g_evtAck;

連接

與服務端相似,在使用socket前,需要先通過initSocket初始化,並在退出前做清理工作。
連接時,先設定要連接的地址與端口,然後發起連接,成功後進行收發處理即可。

SOCKET openConnection(const string &strHost, unsigned short nPort,
    function<void(const string&, int)> funError,
    function<void(const string&)> funMsg) {
    string strError;
    SOCKADDR_IN sockAddr;
    SOCKET sockClient = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    if (INVALID_SOCKET == sockClient) {
        goto FUN_CLEANUP;
    }

    // to connect
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = htons(nPort);
    inet_pton(AF_INET, strHost.c_str(), &(sockAddr.sin_addr));
    if (SOCKET_ERROR == WSAConnect(sockClient, (SOCKADDR*)&sockAddr, sizeof(sockAddr), nullptr, nullptr, nullptr, nullptr)) {
        goto FUN_CLEANUP;
    }

    g_bStop = false;
    {
        thread thrRecv(receiveMsg, sockClient, funError, funMsg);
        thrRecv.detach();

        thread thrAlive(heartBeat, sockClient, funError);
        thrAlive.detach();
    }
    return sockClient;

FUN_CLEANUP:
    int nError = WSAGetLastError();
    if (nullptr != funError) {
        funError(strError, nError);
    }
    return INVALID_SOCKET;
}

接收

接收消息使用的完成端口WSARecv實現的,接受者通過通過事件等待,在有消息到達時系統會主動通知。

接收返回WSA_IO_PENDING錯誤時,不是真正的錯誤,只是告訴上層當前未收到消息,需要等待,在有消息到達時會觸發通知。

void receiveMsg(SOCKET sock,
    function<void(const string&, int)> funError,
    function<void(const string&)> funMsg) {
    int nPort = 0;
    string strAddr = XuNet::peerAddress(sock, &nPort);
    cout << "Connected " << strAddr << ":" << nPort << endl;

    string strError;
    const int MaxBufSize = 255;
    char szBuff[MaxBufSize + 1] = { 0 };
    int nRet = 0, nRecvError = 0;
    WSABUF wsaBuf;
    WSAOVERLAPPED overLapped;
    ZeroMemory(&overLapped, sizeof(overLapped));
    overLapped.hEvent = WSACreateEvent();
    if(NULL == overLapped.hEvent){
        nRecvError = WSAGetLastError();
        strError = "WSACreateEvent";
        goto FUN_CLEANUP;
    }
    wsaBuf.buf = szBuff;
    wsaBuf.len = MaxBufSize;
    while (!g_bStop) {
        DWORD dwFlag = 0;
        DWORD dwRecved = 0;
        nRet = WSARecv(sock, &wsaBuf, 1, &dwRecved, &dwFlag, &overLapped, NULL);
        if (SOCKET_ERROR == nRet) {
            nRecvError = WSAGetLastError();
            if (WSA_IO_PENDING != nRecvError) {
                strError = "WSARecv";
                goto FUN_CLEANUP;
            }
            nRecvError = 0;
        }

        nRet = WSAWaitForMultipleEvents(1, &overLapped.hEvent, TRUE, INFINITE, FALSE);
        if (WSA_WAIT_FAILED == nRet) {
            nRecvError = WSAGetLastError();
            strError = "WSAWaitForMultipleEvents";
            goto FUN_CLEANUP;
        }

        nRet = WSAGetOverlappedResult(sock, &overLapped, &dwRecved, FALSE, &dwFlag);
        if(FALSE == nRet){
            nRecvError = WSAGetLastError();
            strError = "WSAGetOverlappedResult";
            goto FUN_CLEANUP;
        }

        if (dwRecved > 0) {
            string strMsg(szBuff, dwRecved);
            if (strMsg.substr(0, 3) == MSG_Ack) {
                g_evtAck.notifyAll();
            }           
            else if (nullptr != funMsg) {
                funMsg(strMsg);
            }
        }

        WSAResetEvent(overLapped.hEvent);
    }


FUN_CLEANUP:
    WSACloseEvent(overLapped.hEvent);
    if (nullptr != funError) {
        funError(strError, nRecvError);
    }
}

發送

通過定時向服務端發送心跳包,及時發現連接問題(服務端會根據心跳,做超時處理)。發送完消息後,會等待服務端做一個應答,只有接收到服務端應答後纔會真正確認發送成功。

bool sendMessage(SOCKET sock, string strMsg, int nWaitSecs) {
    strMsg += MSG_Delim;

    g_evtAck.reset();
    int nLen = send(sock, strMsg.c_str(), strMsg.length(), 0);
    if (SOCKET_ERROR == nLen) {
        int nError = WSAGetLastError();
        cout << "!!!Send " << strMsg << " fail: " << nError << endl;
        return false;
    }

    return g_evtAck.wait(nWaitSecs);
}

心跳包:

void heartBeat(SOCKET sock, function<void(const string&, int)> funError) {
    int nLostAck = 0;
    int nCount = 0;

    while (!g_bStop) {
        if (sendMessage(sock, MSG_Alive + std::to_string(++nCount), 1)) {
            nLostAck = 0;
        }
        else {
            if (++nLostAck > HeartBeat_MaxLost) {
                if (nullptr != funError) {
                    funError("too many Ack of heartBeat lost", -1);
                }
                break;
            }
        }

        if (g_evtQuit.wait(HeartBeat_Seconds)) {
            cout << "evtQuit got signal" << endl;
            break;
        }
    }

    closesocket(sock);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章