封装Server类和Client类

服务器端:

EasyTcpServer.hpp

#ifndef _EasyTcpServer_hpp_
#define _EasyTcpServer_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<WinSock2.h>
#include<Windows.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET (SOCKET)(-0)
#define SOCKET_ERROR           (-1)
#endif
#include<stdio.h>
#include<vector>
#include "MessageHeader.hpp"

class EasyTcpServer
{
private:
    SOCKET _sock;
    std::vector<SOCKET> g_client;  //容器里装的是所有的客户端

public :
    EasyTcpServer()
    {
        _sock = INVALID_SOCKET;//将Socket设置为一个无效的套接字
    }
    virtual ~EasyTcpServer()
    {
        Close();
    }
    //初始化Socket
    SOCKET InitSock()
    {
        //启动Win Socket2.x环境
#ifdef _WIN32
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
#endif
        //1.建立一个socket
        if (INVALID_SOCKET != _sock)  // 如果当前的socket并不是无效的,则将其关闭
        {
            printf("<socket=%d>关闭旧连接......
", _sock);
            Close();
        }
        _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 新建一个socket,此时的Socket为有效
        if (INVALID_SOCKET == _sock)
        {
            printf("ERROR:建立失败!
");
        }
        else{
            printf("客户端绑定成功......
");
        }
        return _sock;
    }
    //绑定端口号
    int Bind(const char* ip,unsigned short port)
    {
        /*if (_sock == INVALID_SOCKET)
        {
            InitSock();
        }*/
        sockaddr_in _sin = {};     //创建网络地址
        _sin.sin_family = AF_INET; //IPV4
        _sin.sin_port = htons(port); //Host to Network Short
#ifdef _WIN32
        if (ip) //验证IP是否存在
        {
            _sin.sin_addr.S_un.S_addr = inet_addr(ip); // IP地址 
        }
        else{
            _sin.sin_addr.S_un.S_addr = INADDR_ANY;
        }
        
#else 
        _sin.sin_addr.s_addr=INADDR_ANY;
#endif
        int ret = bind(_sock, (sockaddr *)&_sin, sizeof(_sin));
        if (SOCKET_ERROR == ret)
        {
            printf("错误:绑定网络端口<%d>失败......
",port);
        }
        else
        {
            printf("绑定网络窗口<%d>成功......
", port);
        }
        return ret;
    }
    //监听端口号
    int Listen(int n)
    {
        //3.监听网络端口
        int ret = listen(_sock, n);//第二个参数为最大等待多少人可以同时连接
        if (SOCKET_ERROR == ret)
        {
            printf("<Socket=%d>错误:监听网络端口失败......
",_sock);
        }
        else
        {
            printf("<Socket=%d>服务器端监听成功......
",_sock);
        }
        return ret;
    }
    //接受客户端连接
    SOCKET Accept()
    {
        //4.等待接收客户端连接
        sockaddr_in clientAddr = {}; //创建客户端网络地址
        int nAddrLen = sizeof(sockaddr_in);
        SOCKET _cSOCK = INVALID_SOCKET; //将SOCKET的对象_cSOCK初始化无效的Socket
#ifdef _WIN32
        _cSOCK = accept(_sock, (sockaddr *)&clientAddr, &nAddrLen);//接收来自客户端传来的SOCKET
#else
        _cSOCK = accept(_sock, (sockaddr *)&clientAddr, (socklen_t *)&nAddrLen);
#endif
        if (_cSOCK == INVALID_SOCKET)
        {
            printf("<Socket=%d>错误,接收到无效客户端SOCKET!
",_sock);
        }
        else
        {
            //向客户端发送新来的用户
            NewUserJoin userJoin;
            SendDataToAll(&userJoin);

            //将服务端收到的socket存入容器中
            g_client.push_back(_cSOCK);
            printf("<Socket=%d>新客户端加入:Socket=%d,IP = %s
", _sock,(int)_cSOCK, inet_ntoa(clientAddr.sin_addr));//inet_ntoa(clientAddr.sin_addr)将接收到的IP地址转化为字符串
        }
        return _cSOCK;
    }
    //关闭socket

    void Close()
    {
        if (_sock != INVALID_SOCKET)
        {
#ifdef _WIN32
            for (int n = (int)g_client.size(); n >= 0; n--)
            {
                //8.关闭自身的socket
                closesocket(g_client[n]);
            }

            //8.关闭自身的socket
            closesocket(_sock);
            //清除Windows socket环境
            WSACleanup();
#else
            for (int n = (int)g_client.size(); n >= 0; n--)
            {
                //8.关闭自身的socket
                closesocket(g_client[n]);
            }
            close(_sock);
#endif
        }


    }
    //处理网络消息
    bool onRun()
    {
        if (isRun())
        {
            fd_set fd_Read;
            fd_set fd_Write;
            fd_set fd_Exp;

            FD_ZERO(&fd_Read);//FD_ZERO 清空集合里的数据
            FD_ZERO(&fd_Write);
            FD_ZERO(&fd_Exp);

            FD_SET(_sock, &fd_Read);//FD_SET 可以进行操作的宏
            FD_SET(_sock, &fd_Write);
            FD_SET(_sock, &fd_Exp);
            SOCKET maxSock = _sock;
            for (int n = g_client.size() - 1; n >= 0; n--)
            {
                FD_SET(g_client[n], &fd_Read);
                if (maxSock < g_client[n])
                {
                    maxSock = g_client[n];
                }
            }

            /*
            select(
            _In_ int nfds,
            _Inout_opt_ fd_set FAR * readfds,
            _Inout_opt_ fd_set FAR * writefds,
            _Inout_opt_ fd_set FAR * exceptfds,
            _In_opt_ const struct timeval FAR * timeout
            );
            */

            //nfds是一个整数值,是指fd_set集合所有的描述符(select里的第一个参数)的范围(而不是数量)
            //既是所有文件描述符最大值+1
            timeval t = { 1, 0 };

            int ret = select(_sock + 1, &fd_Read, &fd_Write, &fd_Exp, &t); //系统提供select函数来实现多路复用输入/输出模型
            if (ret < 0)
            {
                printf("select任务结束!
");
                Close();
                return false;
            }
            if (FD_ISSET(_sock, &fd_Read))
            {
                FD_CLR(_sock, &fd_Read);
                Accept();
            }
            for (int n = (int)g_client.size() - 1; n >= 0; n--)
            {
                if (FD_ISSET(g_client[n], &fd_Read))
                {
                    if (RecvData(g_client[n]) == -1)
                    {
                        auto iter = g_client.begin() + n;
                        if (iter != g_client.end())
                        {
                            g_client.erase(iter);
                        }
                    }
                }

            }
            return true;
        }
        return false;
    }
    //是否工作中
    bool isRun()
    {
        return _sock != INVALID_SOCKET;
    }
    //接收数据 处理粘包和拆包
    int RecvData(SOCKET _cSOCK)
    {
        //增加一个缓冲区
        char szRecv[1024] = {};
        //5.接收客户端新数据
        int nLen = recv(_cSOCK, szRecv, sizeof(DataHeader), 0);
        DataHeader *header = (DataHeader*)szRecv;

        if (nLen <= 0)
        {
            printf("客户端已退出!任务结束!");
            return -1;
        }
        recv(_cSOCK, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
        OnNetMsg(_cSOCK, header);
        return 0;
    }
    //响应网络消息
    virtual void OnNetMsg(SOCKET _cSOCK, DataHeader* header)
    {
        switch (header->cmd){
        case CMD_Login:
        {
            Login *login = (Login*)header;
            printf("收到客户端<Socket=%d>请求:CMD_Login,数据长度:%d
UserName:%s
PassWord:%s
", _cSOCK, login->dataLength, login->username, login->password);
            //忽略判断用户密码是否正确的过程
            LoginResult ret;
            send(_cSOCK, (char *)&ret, sizeof(LoginResult), 0); //再发消息体

        }
        case CMD_Logout:
        {
            Logout* logout = (Logout*)header;
            printf("收到命令:CMD_Logout,数据长度:%d
UserName:%s
", logout->dataLength, logout->username);

            //忽略判断用户密码是否正确的过程
            LogoutResult let;
            send(_cSOCK, (char *)&let, sizeof(let), 0); //再发消息体
        }
            break;
        case CMD_New_User_Join:
        {
            NewUserJoin* UserJoin = (NewUserJoin*)header;
            printf("收到命令:CMD_Logout,数据长度:%d
UserName:%s
", UserJoin->dataLength);

            //忽略判断用户密码是否正确的过程
            NewUserJoin let;
            send(_cSOCK, (char *)&let, sizeof(let), 0); //再发消息体
        }
            break;
        default:
        {
            DataHeader header = { 0 };
            send(_cSOCK, (char *)&header.cmd, sizeof(header), 0);
        }

            break;
        }

    }
    //单次发送指定socket数据
    int SendData(SOCKET _cSOCK, DataHeader* header)
    {
        if (isRun() && header)
        {
            return send(_cSOCK, (const char*)header, header->dataLength, 0);
        }
        return SOCKET_ERROR;
    }
    //群发指定socket数据
    void SendDataToAll(DataHeader* header)
    {
        if (isRun() && header)
        {
            for (int n = (int)g_client.size() - 1; n >= 0; n--)
            {
                SendData(g_client[n], header);
            }
        }
    }
};


#endif

MessageHeader.hpp

enum CMD { CMD_Login, CMD_Login_Result, CMD_Logout, CMD_Logout_Result, CMD_New_User_Join, CMD_ERROR };

//包头
struct DataHeader
{
    short dataLength;
    short cmd;
};
//包体
struct Login :public DataHeader
{
    Login()
    {
        dataLength = sizeof(Login);
        cmd = CMD_Login;
    }
    char username[32];
    char password[32];
};

struct LoginResult :public DataHeader
{
    LoginResult()
    {
        dataLength = sizeof(LoginResult);
        cmd = CMD_Login_Result;
        result = 0;
    }
    int result;
};

struct Logout :public DataHeader
{
    Logout()
    {
        dataLength = sizeof(Logout);
        cmd = CMD_Logout;
    }
    char username[32];
};

struct LogoutResult :public DataHeader
{
    LogoutResult()
    {
        dataLength = sizeof(LogoutResult);
        cmd = CMD_Logout_Result;
        result = 0;
    }
    int result;
};

struct NewUserJoin :public DataHeader
{
    NewUserJoin()
    {
        dataLength = sizeof(LogoutResult);
        cmd = CMD_New_User_Join;
        sock = 0;
    }
    int sock;
};

Server.cpp

#include "EasyTcpServer.hpp"

int main()
{
    EasyTcpServer server;
    server.InitSock();
    server.Bind(nullptr,4567);
    server.Listen(5);


    while (server.isRun())
    {
        server.onRun();
        //printf("空闲时间处理其他业务.......
");
    }
    server.Close();
    system("pause");
    return 0;
}

客户端:

EasyTcpClient.hpp

#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_
#ifdef _WIN32

#define WIN32_LEAN_AND_MEAN
#include<WinSock2.h>
#include<Windows.h>
#pragma comment(lib,"ws2_32.lib")

#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET (SOCKET)(-0)
#define SOCKET_ERROR           (-1)
#endif
#include<stdio.h>
#include "MessageHeader.hpp"

class EasyTcpClient
{
    SOCKET _sock;
public:
    EasyTcpClient()
    {
        _sock = INVALID_SOCKET;//将Socket设置为一个无效的套接字
    }

    //虚构函数
    virtual ~EasyTcpClient()
    {
        Close();
    }

    //初始化socket
    void initSocket()
    {
        //启动Win Socket2.x环境
    #ifdef _WIN32
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
    #endif
        //1.建立一个socket
        if (INVALID_SOCKET != _sock)  // 如果当前的socket并不是无效的,则将其关闭
        {
            printf("<socket=%d>关闭旧连接......
",_sock);
            Close();
        }
        _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // 新建一个socket
        if (INVALID_SOCKET == _sock)
        {
            printf("ERROR:建立失败!
");
        }
        else{
            printf("客户端绑定成功......
");
        }
    }

    //连接服务器
    int Connet(char* ip, unsigned short port)
    {
        if (INVALID_SOCKET == _sock)
        {
            initSocket();
        }
        sockaddr_in _sin = {};     //创建网络地址
        _sin.sin_family = AF_INET;
        _sin.sin_port = htons(port); //Host to Network Short
    #ifdef _WIN32
        _sin.sin_addr.S_un.S_addr = inet_addr(ip);//inet_addr("127.0.0.1"); // IP地址 
    #else
        _sin.sin_addr.s_addr = inet_addr(ip);
    #endif
        int ret = connect(_sock, (sockaddr *)&_sin, sizeof(sockaddr_in));
        if (SOCKET_ERROR == ret)
        {
            printf("ERROR:连接失败!
");
        }
        else
        {
            printf("客户端连接成功......
");
        }
        return ret;
    }

    //关闭服务器
    void Close()
    {
        if (_sock != INVALID_SOCKET)
        {
            //关闭Win Socket2.x环境
    #ifdef _WIN32
            closesocket(_sock);
            //WinSocket关闭
            WSACleanup();
    #else
            close(_sock);
    #endif
            _sock = INVALID_SOCKET;
        }

    }

    //查询网络消息
    bool onRun()
    {
        if (isRun())
        {
            //伯克利 socket
            fd_set fd_Read;
            FD_ZERO(&fd_Read);//FD_ZERO 清空集合里的数据
            FD_SET(_sock, &fd_Read);//FD_SET 可以进行操作的宏
            timeval t = { 1, 0 };
            int ret = select(_sock, &fd_Read, 0, 0, &t);
            if (ret < 0)
            {
                printf("<socket=%d>select任务结束1!", _sock);
                return false;
            }
            if (FD_ISSET(_sock, &fd_Read))
            {
                FD_CLR(_sock, &fd_Read);

                if (RecvData(_sock) == -1)
                {
                    printf("<socket = %d>select任务结束2!", _sock);
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    //是否工作中
    bool isRun()
    {
        return _sock != INVALID_SOCKET;
    }

    //接收数据 处理粘包和拆包
    int RecvData(SOCKET _cSOCK)
    {
        //增加一个缓冲区
        char szRecv[1024] = {};
        //5.接收客户端新数据
        int nLen = recv(_cSOCK, szRecv, sizeof(DataHeader), 0);
        DataHeader *header = (DataHeader*)szRecv;

        if (nLen <= 0)
        {
            printf("与服务器断开连接!任务结束!");
            return -1;
        }
        recv(_cSOCK, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
        OnNetMsg(header);
        return 0;
    }

    //响应网络消息
    void OnNetMsg(DataHeader* header)
    {
        switch (header->cmd){
        case CMD_Login_Result:
        {
            
            LoginResult *loginresult = (LoginResult*)header;
            printf("收到服务端消息请求:CMD_Login_Result,数据长度:%d
", loginresult->dataLength);
        }
            break;
        case CMD_Logout_Result:
        {
            LogoutResult* logoutresult = (LogoutResult*)header;
            printf("收到服务端消息请求:CMD_Logout_Result,数据长度:%d
UserName:%s
", logoutresult->dataLength);
        }
            break;
        case CMD_New_User_Join:
        {
            NewUserJoin* newuserjoin = (NewUserJoin*)header;
            printf("收到服务端消息请求:CMD_New_User_Join,数据长度:%d
UserName:%s
", newuserjoin->dataLength);
        }
            break;
        }
    }

    //发送数据
    int SendData(DataHeader* header)
    {
        if (isRun() && header)
        {
            return send(_sock, (const char*)header, header->dataLength, 0);
        }
        return SOCKET_ERROR;
    }

private:

};


#endif

MessageHeader.hpp

enum CMD { CMD_Login, CMD_Login_Result, CMD_Logout, CMD_Logout_Result, CMD_New_User_Join, CMD_ERROR };

//包头
struct DataHeader
{
    short dataLength;
    short cmd;
};
//包体
struct Login :public DataHeader
{
    Login()
    {
        dataLength = sizeof(Login);
        cmd = CMD_Login;
    }
    char username[32];
    char password[32];
};

struct LoginResult :public DataHeader
{
    LoginResult()
    {
        dataLength = sizeof(LoginResult);
        cmd = CMD_Login_Result;
        result = 0;
    }
    int result;
};

struct Logout :public DataHeader
{
    Logout()
    {
        dataLength = sizeof(Logout);
        cmd = CMD_Logout;
    }
    char username[32];
};

struct LogoutResult :public DataHeader
{
    LogoutResult()
    {
        dataLength = sizeof(LogoutResult);
        cmd = CMD_Logout_Result;
        result = 0;
    }
    int result;
};

struct NewUserJoin :public DataHeader
{
    NewUserJoin()
    {
        dataLength = sizeof(LogoutResult);
        cmd = CMD_New_User_Join;
        sock = 0;
    }
    int sock;
};

client.cpp

#include "EasyTcpClient.hpp"
//线程库头文件
#include<thread>

void cmdThread(EasyTcpClient* client)
{
    while (true)
    {
        char cmdBuf[256] = {};
        scanf("%s", cmdBuf);
        if (0 == strcmp(cmdBuf, "exit"))
        {
            client->Close();
            printf("退出!
");
            break;
        }
        else if (0 == strcmp(cmdBuf, "login"))
        {
            Login login;
            strcpy(login.username, "sutaoyu");
            strcpy(login.password, "sutaoyu01");
            client->SendData(&login);
        }
        else if (0 == strcmp(cmdBuf, "logout"))
        {
            Logout logout;
            strcpy(logout.username, "sutaoyu");
            client->SendData(&logout);
        }
        else{
            printf("不支持的命令!");
        }
    }
    
}

int main()
{
    EasyTcpClient client;
    client.initSocket();
    client.Connet("127.0.0.1", 4567);

    //启动线程
    std::thread t1(cmdThread, &client);
    t1.detach();

    while (client.isRun())
    {
        client.onRun();

    }
    client.Close();

    printf("已退出!
");
    getchar();
    return 0;
}
原文地址:https://www.cnblogs.com/zhuifeng-mayi/p/11007761.html