[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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值