[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);
}

 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值