[C++]-Windows下Socket連接之服務端

輔助函數

TCP服務端

 

Socket即套接字,用於網絡通訊,有三種模式:

  • 流式套接字(SOCK_STREAM)
  • 數據報套接字(SOCK_DGRAM)
  • 原始套接字(SOCK_RAW)

輔助函數

Windows下提供了WSAXXX系列函數來輔助Socket的開發。爲了使用這些函數需要:

#include <WinSock2.h>
#include <WS2tcpip.h>
#include <Mswsock.h>

#pragma comment(lib, "Ws2_32.lib")

初始化

在使用Socket接口前,需要先做初始化,並在完成時做清理工作:

void  initSocket() {
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        int nError = WSAGetLastError();
        throw XuException("WSAStartup fail", nError);
    }
}

void  uninitSocket() {
    WSACleanup();
}

地址與端口

通過Socket句柄,可以方便地獲取本地與對端的連接地址與端口:

std::string  peerAddress(SOCKET sock_, int *pPort_) {
    struct sockaddr_in addr;
    int addrLen = sizeof(addr);
    if (getpeername(sock_, (struct sockaddr*)&addr, &addrLen) == SOCKET_ERROR) {
        int nError = WSAGetLastError();
        throw XuException("getpeername fail", nError);
    }

    char szBuff[INET6_ADDRSTRLEN] = { 0 };
    if (inet_ntop(addr.sin_family, &addr.sin_addr, szBuff, INET6_ADDRSTRLEN) == nullptr) {
        int nError = WSAGetLastError();
        throw XuException("inet_ntop fail", nError);
    }
    if(nullptr!= pPort_)
        *pPort_ = ntohs(addr.sin_port);

    return szBuff;
}

std::string  localAddress(SOCKET sock_, int *pPort_) {
    struct sockaddr_in addr;
    int addrLen = sizeof(addr);
    if (getsockname(sock_, (struct sockaddr*)&addr, &addrLen) == SOCKET_ERROR) {
        int nError = WSAGetLastError();
        throw XuException("getpeername fail", nError);
    }

    char szBuff[INET6_ADDRSTRLEN] = { 0 };
    if (inet_ntop(addr.sin_family, &addr.sin_addr, szBuff, INET6_ADDRSTRLEN) == nullptr) {
        int nError = WSAGetLastError();
        throw XuException("inet_ntop fail", nError);
    }
    if (nullptr != pPort_)
        *pPort_ = ntohs(addr.sin_port);

    return szBuff;
}

連接時長

通過Socket句柄,也可以方便地獲取已連接的時長:

int  connectedSeconds(SOCKET sock_){
    DWORD dwSeconds = 0;
    int nLen = sizeof(dwSeconds);
    if (SOCKET_ERROR == getsockopt(sock_, SOL_SOCKET, SO_CONNECT_TIME, (char*)&dwSeconds, &nLen)) {
        return -1;
    }

    return (int)dwSeconds;
}

TCP服務端

TCP是面向連接的,使用的是流式套接字。爲了完成實現連接,服務端需要:

  • 建立Socket;
  • 綁定端口與地址;
  • 偵聽,並等待連接;

所有客戶端連接通過map來保存,以下是相關定義:

bool g_bStop = false;
SOCKET g_sockListen = INVALID_SOCKET;
map<int, SOCKET> g_mapConnected;

偵聽等待

爲了建立通訊連接,服務端需要先偵聽,等待客戶端連接的到來。
若要在指定地址上偵聽,則需要明確設定要偵聽的地址;若要在本機所有地址上偵聽,則只需設定偵聽地址爲INADDR_ANY即可。

void acceptClientConnect(unsigned short nPort, 
    function<void(const string&, int)> funError, 
    function<void(int, const string&)> funMsg) {
    g_mapConnected.clear();

    string strError;
    SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == sockListen) {
        strError = "socket";
        goto FUN_CLEAN_UP;
    }

    SOCKADDR_IN sockAddr;
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = htons(nPort);
    // 偵聽迴環地址
    //string strIP = "127.0.0.1";
    //inet_pton(AF_INET, strIP.c_str(), &(sockAddr.sin_addr));
    sockAddr.sin_addr.s_addr = INADDR_ANY;  // 偵聽所有(0.0.0.0)
    if (::bind(sockListen, (SOCKADDR*)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
        strError = "bind";
        goto FUN_CLEAN_UP;
    }
    if (listen(sockListen, 1) == SOCKET_ERROR) {
        strError = "listen";
        goto FUN_CLEAN_UP;
    }
    g_sockListen = sockListen;

    // to accept
    {
        string strAddr =  localAddress(sockListen);
        cout << "Listen at " << strAddr << ":" << nPort << endl;
        while (!g_bStop) {
            SOCKET sockConn = accept(sockListen, NULL, NULL);
            if (INVALID_SOCKET == sockConn) {
                strError = "accept";
                goto FUN_CLEAN_UP;
            }

            // to start sock handler
            thread thrClient(handleClientSocket, sockConn, funError, funMsg);
            thrClient.detach();
        }
    }

FUN_CLEAN_UP:
    int nError = WSAGetLastError();
    //cout << strError << " fail: " << nError << endl;
    if (nullptr != funError) {
        funError(strError, nError);
    }
    return;
}

接收消息

客戶端連接成功後,啓動一個獨立的線程來接收消息;並把Socket句柄保存到map中,方便需要時(如給指定客戶端發消息)獲取。

void handleClientSocket(SOCKET sock,
    function<void(const string&, int)> funError,
    function<void(int, const string&)> funMsg) {
    int nPort = 0;
    string strAddr =  peerAddress(sock, &nPort);
    if (nullptr != funMsg) {
        funMsg(nPort, "New connection from " + strAddr + ":" + std::to_string(nPort));
    }


    const int MaxBuffSize = 255;
    char szBuffer[MaxBuffSize + 1] = { 0 };
    g_mapConnected[nPort] = sock;
    while (!g_bStop) {
        int nRet = recv(sock, szBuffer, MaxBuffSize, 0);
        if (nRet > 0) {
            string strRecv(szBuffer, nRet);
            sendMessage(sock, MSG_Ack);
            if (nullptr != funMsg) {
                funMsg(nPort, strRecv);
            }
        }
        else {
            int nError = 0;
            string strError = "Connection " + strAddr + ":" + std::to_string(nPort);
            if (0 == nRet) {
                strError += " gracefully closed";
            }
            else {
                strError += " recv fail";
                nError = WSAGetLastError();
            }

            if (nullptr != funError) {
                funError(strError, nError);
            }
            break;
        }
    }

    g_mapConnected[nPort] = INVALID_SOCKET;
}

發送消息

通過send可以直接發送消息到指定客戶端:

void sendMessage(SOCKET sock, string strMsg) {
    strMsg += MSG_Delim;
    int nLen = send(sock, strMsg.c_str(), strMsg.length(), 0);
    if (SOCKET_ERROR == nLen) {
        int nError = WSAGetLastError();
        cout << "!!!Send " << strMsg << " fail: " << nError << endl;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章