zoukankan      html  css  js  c++  java
  • 网络编程二(服务器、客户端)

    请先参看网络编程一(服务器、客户端)

    https://www.cnblogs.com/dreammmz/p/13500249.html

    网络编程一无法实现多个客户端同时连接访问,对其进行修改。

    加入select()函数,该函数用于监视文件描述符的变化情况——读写或是异常。

    int  select  (int  maxfd + 1, fd_set*  readset,  fd_set*  writeset,
                       fd_set*  exceptset,  const struct timeval*  timeout);
    返回值:>0:就绪描述字的正数目;-1:出错;0:超时。
     fd_set fdRead;
     fd_set fdWrite;
     fd_set fdExp;
    
     //初始化个数为0;
     FD_ZERO(&fdRead);
     FD_ZERO(&fdWrite);
     FD_ZERO(&fdExp);
    
     FD_SET(_sock, &fdRead);
     FD_SET(_sock, &fdWrite);
     FD_SET(_sock, &fdExp);
    
     timeval t = {1,0};
     int ret=select(_sock +1, &fdRead,&fdWrite,&fdExp, &t);
     
    maxfd + 1:最大的描述符(套接字)加1。
     
    readset:用于检查可读性。
     
    writeset:用于检查可写性。
     
    exceptset:用于检查带外数据
     
    上面三个类型都为fd_set类型,其实就是一个结构体,如下所示:
    typedef struct fd_set {
            u_int fd_count;               /* how many are SET? */
            SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
    } fd_set;
     
    timeout:一个timeval类型的对象,用来决定select等待I/O的最长时间。如果为NULL则一直等待,此时处于阻塞状态。
    struct timeval {
            long    tv_sec;         /* seconds */
            long    tv_usec;        /* and microseconds */
    };

    完成代码:

    服务器:

    #define WIN32_LEAN_AND_MEAN
    #include<iostream>
    #include<vector>
    //注意将WinSock2放在之前 否则会报错
    //或者使用宏定义
    #include<WinSock2.h>
    #include<Windows.h>
    //明确指定需要一个动态库
    //在工程中连接器中加入lib动态库
    #pragma comment(lib,"ws2_32.lib")
    
    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(NewUserJoin);
            cmd = CMD_NEW_USER_JOIN;
            scok = 0;
        }
        int scok;
    };
    
    //创建全局的vector用来存储加入的客户端
    std::vector<SOCKET> g_clients;
    
    //用来处理客户端请求
    int processor(SOCKET _cSock)
    {
        char szRecv[4096] = {};
        //5、接受客户端的请求数据
        int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
        DataHeader* header = (DataHeader*)szRecv;
        if (nLen <= 0)
        {
            std::cout << "客户端已退出,任务结束。socket="<< _cSock <<std::endl;
            return -1;
        }
    
        switch (header->cmd)
        {
        case CMD_LOGIN:
        {
            Login login = {};
            recv(_cSock, (char*)&login + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0);
            std::cout << "收到客户端:" << _cSock << ";" << std::endl
                << "数据长度:" << login.dataLength << ";" << std::endl
                << "userName:" << login.userName << ";" << std::endl
                << "userPass:" << login.PassWord << std::endl;
            LoginResult ret;
            send(_cSock, (char*)&ret, sizeof(LoginResult), 0);
        }
        break;
        case CMD_LOGOUT:
        {
            Logout logout = {};
            recv(_cSock, (char*)&logout + sizeof(DataHeader), sizeof(logout) - sizeof(DataHeader), 0);
            std::cout << "收到客户端:" << _cSock << ";" << std::endl
                << "数据长度:" << logout.dataLength << ";" << std::endl
                << "userName:" << logout.userName << std::endl;
            //忽略判断用户名密码
            LogoutResult ret;
            send(_cSock, (char*)&ret, sizeof(ret), 0);
        }
        break;
        default:
            header->cmd = CMD_ERROR;
            header->dataLength = 0;
            send(_cSock, (char*)&header, sizeof(header), 0);
            break;
        }
        return 0;
    }
    
    int main()
    {
        //调用windows库文件
        //启动Windows socket 2.x环境
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
        
        //1、建立一个socket 套接字
        SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
        //将套接字和IP、端口绑定
        //2、bind 绑定用于接受客户端连接的 网路端口
        sockaddr_in _sin = {};
        _sin.sin_family = AF_INET;
        _sin.sin_port = htons(4567);//绑定一个端口号
        _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    //绑定到那个ip地址
        if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
        {
            std::cout << "绑定IP、端口失败~" << std::endl;
        }
        else
        {
            std::cout << "绑定IP、端口成功~" << std::endl;
        }
    
        //3、listen 监听网络接口
        //#define INVALID_SOCKET  (SOCKET)(~0)
        //#define SOCKET_ERROR            (-1)
        if (SOCKET_ERROR == listen(_sock, 5))
        {
            std::cout << "监听接口开启失败~" << std::endl;
        }
        else
        {
            std::cout << "监听接口开启成功~" << std::endl;
        }
    
        while (true)
        {
            //描述符(socket)集合
            fd_set fdRead;
            fd_set fdWrite;
            fd_set fdExp;
    
            //初始化个数为0;
            FD_ZERO(&fdRead);
            FD_ZERO(&fdWrite);
            FD_ZERO(&fdExp);
    
            FD_SET(_sock, &fdRead);
            FD_SET(_sock, &fdWrite);
            FD_SET(_sock, &fdExp);
    
            for (int n = (int)g_clients.size()-1; n>=0 ; --n)
            {
                FD_SET(g_clients[n], &fdRead);
            }
    
            timeval t = {1,0};
            int ret=select(_sock +1, &fdRead,&fdWrite,&fdExp, &t);
    
            if (ret < 0)
            {
                std::cout << "select任务结束。" << std::endl;
                break;
            }
    
            if (FD_ISSET(_sock, &fdRead))
            {
                FD_CLR(_sock, &fdRead);    
    
                //4、accept 等待接受客户端连接
                sockaddr_in clientAddr = {};
                int nAddrLen = sizeof(sockaddr_in);
                SOCKET _cSock = INVALID_SOCKET;
                _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
                if (INVALID_SOCKET == _cSock) {
                    std::cout << "客户端连接失败~" << std::endl;
                }
                else 
                {
                    //循环发送给每个客户端有新用户加入
                    for (int n = (int)g_clients.size() - 1; n >= 0; --n)
                    {
                        NewUserJoin userJoin;
                        send(g_clients[n], (const char*)&userJoin, sizeof(NewUserJoin), 0);
                    }
                    //将新用户加入到vector中
                    g_clients.push_back(_cSock);
                    std::cout << "新加入客户端IP:" << inet_ntoa(clientAddr.sin_addr) << std::endl;
                }
            }
    
            //处理fdRead中的内容
            for (unsigned int n = 0; n < fdRead.fd_count; ++n)
            {
                if (-1 == processor(fdRead.fd_array[n]))
                {
                    //判断客户端是否退出
                    //如果退出需要从vector中将其去除
                    auto iter = find(g_clients.begin(),g_clients.end(), fdRead.fd_array[n]);
                    if (iter != g_clients.end())
                    {
                        g_clients.erase(iter);
                    }
                }
            }    
        }
    
        //出现异常逐个关闭套接字
        for (int n = g_clients.size(); n >= 0; --n)
        {
            closesocket(g_clients[n]);
        }
    
        //closesocket关闭套接字
        closesocket(_sock);
    
        WSACleanup();
        std::cout << "已退出,任务结束。" << std::endl;
    
        system("pause");
        return 0;
    }

    客户端:

    #define _CRT_SECURE_NO_WARNINGS
    #define WIN32_LEAN_AND_MEAN
    #include<iostream>
    #include<thread>
    
    //注意将WinSock2放在之前 否则会报错
    //或者使用宏定义
    #include<WinSock2.h>
    #include<Windows.h>
    //明确指定需要一个动态库
    //在工程中连接器中加入lib动态库
    #pragma comment(lib,"ws2_32.lib")
    
    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(NewUserJoin);
            cmd = CMD_NEW_USER_JOIN;
            scok = 0;
        }
        int scok;
    };
    
    int processor(SOCKET _cSock)
    {
        char szRecv[4096] = {};
        //5、接受客户端的请求数据
        int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
        DataHeader* header = (DataHeader*)szRecv;
        if (nLen <= 0)
        {
            std::cout << "与服务器断开连接,任务结束。" << std::endl;
            return -1;
        }
        //通过接收到的header来判断
        //客户端发送的命令是什么
        //同时给客户端反馈
        switch (header->cmd)
        {
        case CMD_LOGIN_RESULT:
        {
            recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength- sizeof(DataHeader), 0);
            LoginResult* login = (LoginResult*)szRecv;
            std::cout << "收到服务器CMD_LOGIN_RESULT;" 
                << "数据长度:" << login->dataLength << ";" << std::endl;
        
        }
        break;
        case CMD_LOGOUT_RESULT:
        {
            recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
            LogoutResult* logout = (LogoutResult*)szRecv;
            std::cout << "收到服务器CMD_LOGOUT_RESULT;"
                << "数据长度:" << logout->dataLength << ";" << std::endl;
        }
        break;
        case CMD_NEW_USER_JOIN:
        {
            recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
            NewUserJoin* userJoin = (NewUserJoin*)szRecv;
            std::cout << "收到服务器消息 CMD_NEW_USER_JOIN; " << "数据长度:" << userJoin->dataLength << ";" << std::endl;
        }
        break;
        default:
            break;
        }
    
        return 0;
    }
    
    bool g_bRun = true;
    void cmdThread(SOCKET _sock)
    {
        while (true)
        {
            char cmdBuf[256] = {};
            std::cin >> cmdBuf;
            if (0 == strcmp(cmdBuf, "exit"))    
            {
                g_bRun = false;
                std::cout << "收到exit命令,任务结束。" << std::endl;
                break;
            }
            else if (0 == strcmp(cmdBuf, "login"))
            {
                //5、向服务器发送命令
                Login login;
                strcpy(login.userName, "xmq");
                strcpy(login.PassWord, "mima");
                send(_sock, (const char*)&login, sizeof(login), 0);
            }
            else if (0 == strcmp(cmdBuf, "logout"))
            {
                //5、向服务器发送命令
                Logout logout;
                strcpy(logout.userName, "xmq");
                send(_sock, (const char*)&logout, sizeof(logout), 0);
            }
            else
            {
                std::cout << "不支持命令,请重新输入。" << std::endl;
            }
        }
        
    }
    
    int main()
    {
        //调用windows库文件
        //启动Windows socket 2.x环境
        WORD ver = MAKEWORD(2, 2);
        WSADATA dat;
        WSAStartup(ver, &dat);
    
        //1、建立一个socket
        SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (INVALID_SOCKET == _sock)
        {
            std::cout << "建立socket失败~" << std::endl;
        }
        else
        {
            std::cout << "建立socket成功~" << std::endl;
        }
        //2、连接服务器
        sockaddr_in _sin = {};
        _sin.sin_family = AF_INET;
        _sin.sin_port = htons(4567);
        _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
        int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
        if (SOCKET_ERROR == ret)
        {
            std::cout << "连接服务器失败" << std::endl;
        }
        else
        {
            std::cout << "连接服务器成功" << std::endl;
        }
        //启动线程
        std::thread t1(cmdThread,_sock);
        t1.detach();
    
        while (g_bRun)
        {
            fd_set fdReads;
            FD_ZERO(&fdReads);
            FD_SET(_sock, &fdReads);
    
            timeval t = {1,0};
            int ret = select(_sock + 1, &fdReads, 0, 0, &t);
            if (ret < 0)
            {
                std::cout << "select任务结束1。" << std::endl;
                break;
            }
            if (FD_ISSET(_sock, &fdReads))
            {
                FD_CLR(_sock, &fdReads);
    
                if (-1 == processor(_sock))
                {
                    std::cout << "select任务结束2。" << std::endl;
                    break;
                }    
            }
    
            //std::cout << "空闲时间处理其他任务。" << std::endl;
            
    
        }
        //7、closesocket 关闭套接字
        closesocket(_sock);
    
        WSACleanup();
    
        std::cout << "已退出,任务结束。" << std::endl;
    
        system("pause");
        return 0;
    }
  • 相关阅读:
    ES6特性
    使用mybatis插件拦截SQL
    前端下载文件的几种方式
    Electron-vue项目使用 Inno Setup 创建安装包
    Windows powershell 常用代码段
    Java8之Predicate, Consumer,Function基础使用
    Java8之Predicate接口使用
    使用Replica Set副本集方式搭建mongodb副本集群
    Typora的一些偏好设置
    使用Typora编写md文档并优雅地上传到博客园
  • 原文地址:https://www.cnblogs.com/dreammmz/p/13524008.html
Copyright © 2011-2022 走看看