7_select的运用

服务器端使用select函数

#define WIN32_LEAN_AND_MEAN   //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS

#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<vector>
#pragma comment(lib, "ws2_32.lib")  //method_1

enum CMD
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_NEW_USER_JOIN,
    CMD_EXIT
};
struct DataHeader
{
    short data_length;
    short cmd;
};
struct Login : public DataHeader
{
    Login()
    {
        data_length = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char username[32];
    char password[32];
};
struct LoginResult : public DataHeader
{
    LoginResult()
    {
        data_length = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
    }
    int result;
};
struct Logout : public DataHeader
{
    Logout()
    {
        data_length = sizeof(Logout);
        cmd = CMD_LOGOUT;
    }
    char username[32];
};
struct LogoutResult : public DataHeader
{
    LogoutResult()
    {
        data_length = sizeof(LogoutResult);
        cmd = CMD_LOGOUT_RESULT;
    }
    int result;
};
struct NewUserJOIN : public DataHeader
{
    NewUserJOIN()
    {
        data_length = sizeof(NewUserJOIN);
        cmd = CMD_NEW_USER_JOIN;
    }
    int sock;
};

std::vector<SOCKET> client_socks;

bool return_value_error(char* function_name, int ret)
{
    if (SOCKET_ERROR == ret)
    {
        std::cout << function_name <<  "错误" << std::endl;
        return TRUE;
    }
    return FALSE;
}

int Communicate(SOCKET client_sock)
{
    //6.接收数据
    DataHeader header = {};
    int buf_len = recv(client_sock, (char*)&header, sizeof(DataHeader), 0);
    //7.处理数据
    if (return_value_error("recv", buf_len))
    {
        return -1;
    }
    else
    {
        if (header.cmd == CMD_EXIT)
        {
            std::cout << "客户端连接退出" << std::endl;
            return -1;
        }
        else if (header.cmd == CMD_LOGIN)
        {
            Login data = {};
            buf_len = recv(client_sock, (char *)&data + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0);  //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
            return_value_error("recv", buf_len);
            std::cout << "接收请求:LOGIN" << ",数据长度:" << data.data_length << ",用户名:" << data.username << std::endl;
            LoginResult res = {};
            if (!strncmp(data.username, "aaron", sizeof("aaron") && !strncmp(data.password, "123456", sizeof("123456"))))
            {
                res.result = 0;      //网络通信一般返回0为正常
            }
            else
            {
                res.result = 1;
            }
            buf_len = send(client_sock, (char *)&res, sizeof(LoginResult), 0);
            return_value_error("send", buf_len);
        }
        else if (header.cmd == CMD_LOGOUT)
        {
            Logout data = {};
            buf_len = recv(client_sock, (char *)&data + sizeof(DataHeader), sizeof(Logout) - sizeof(DataHeader), 0);
            return_value_error("recv", buf_len);
            std::cout << "接收请求:LOGOUT" << ",数据长度:" << data.data_length << ",用户名:" << data.username << std::endl;
            LogoutResult res = {};
            res.result = 0;
            buf_len = send(client_sock, (char *)&res, sizeof(LogoutResult), 0);
            return_value_error("send", buf_len);
        }
    }
    return 0;
}
int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);     //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
                               //1.创建socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    //地址系列规范,套接字规范,使用的协议
                                                                //2.绑定地址
    sockaddr_in server_addr = {};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    server_addr.sin_addr.S_un.S_addr = INADDR_ANY;          //代表本机所有地址
    int ret = bind(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in));  //若成功返回0
    return_value_error("bind", ret);
    //3.监听端口
    ret = listen(sock, 5);    //第二个参数为挂起的连接队列的最大长度,若成功返回0
    return_value_error("listen", ret);
    while (true)
    {
        fd_set readfds;
        fd_set writefds;
        fd_set exceptfds;

        FD_ZERO(&readfds);
        FD_ZERO(&writefds);
        FD_ZERO(&exceptfds);

        FD_SET(sock, &readfds);
        FD_SET(sock, &writefds);
        FD_SET(sock, &exceptfds);

        for (size_t i = 0; i < client_socks.size(); ++i)
        {
            FD_SET(client_socks[i], &readfds);
        }
        //select 函数确定一个或多个套接字的状态,并在必要时等待执行同步 I/O
        //nfds是指fd_set中所有描述符的范围,而不是数量
        //即是所有文件描述符最大值加1,windows中这个参数不重要
        timeval time = { 0, 0 };   //设置等待时间为0,即非阻塞模式
        int ret = select(sock + 1, &readfds, &writefds, &exceptfds, &time);  //readfds:一个可选指针,指向要检查的一组套接字的可读性。

        if (return_value_error("select", ret))
        {
            break;
        }
        if(FD_ISSET(sock, &readfds))
        {
            FD_CLR(sock, &readfds);
            //4.接收客户端连接,
            sockaddr_in client_addr = {};
            int len = sizeof(sockaddr_in);
            //std::cout << typeid(recv_buf).name() << std::endl;
            SOCKET client_sock = accept(sock, (sockaddr*)&client_addr, &len);    //accept函数返回一个新的套接字来和客户端通信,sockaddr和sockaddr_in所代表的意义一样,字节大小也一样,只不过sockaddr_in是后面创建的,之前的接口没有更改
            if (INVALID_SOCKET == client_sock)
            {
                std::cout << "连接客户端错误" << std::endl;
                break;
            }
            else
            {
                std::cout << "客户端:" << inet_ntoa(client_addr.sin_addr) << "连接成功" << std::endl;
                client_socks.push_back(client_sock);
                char buf[] = "hello, i'm server";
                //5.发送数据
                int buf_len = send(client_sock, buf, sizeof(buf), 0);  //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
                return_value_error("send", buf_len);
            }
        }

        //std::cout << "空闲时间处理其他事务" << std::endl;
        for (u_int i = 0; i < readfds.fd_count; ++i)
        {
            if (-1 == Communicate(readfds.fd_array[i]))
            {
                std::vector<SOCKET>::iterator iter = std::find(client_socks.begin(), client_socks.end(), readfds.fd_array[i]);
                if (iter != client_socks.end())
                {
                    client_socks.erase(iter);
                }
            }
        }


    }
    //
    for (size_t i = 0; i < client_socks.size(); ++i)
    {
        closesocket(client_socks[i]);
    }
    std::cout << "服务器退出" << std::endl;
    getchar();
    //8.关闭套接字
    closesocket(sock);
    WSACleanup();
    return 0;
}

客户端

#define WIN32_LEAN_AND_MEAN   //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS

#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#pragma comment(lib, "ws2_32.lib")  //method_1

bool judge_return_value(char* function_name, int ret)
{
    if (SOCKET_ERROR == ret)
    {
        std::cout << function_name << "错误" << std::endl;
        return FALSE;
    }
    return TRUE;
}

enum CMD
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_EXIT
};
struct DataHeader
{
    short data_length;
    short cmd;
};
struct Login : public DataHeader
{
    Login()
    {
        data_length = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char username[32];
    char password[32];
};
struct LoginResult : public DataHeader
{
    LoginResult()
    {
        data_length = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
    }
    int result;
};
struct Logout : public DataHeader
{
    Logout()
    {
        data_length = sizeof(Logout);
        cmd = CMD_LOGOUT;
    }
    char username[32];
};
struct LogoutResult : public DataHeader
{
    LogoutResult()
    {
        data_length = sizeof(LogoutResult);
        cmd = CMD_LOGOUT_RESULT;
    }
    int result;
};


int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);     //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
    //创建socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    //客户端的地址由系统自动分配,不需要绑定
    //连接服务器
    sockaddr_in server_addr = {};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    int ret = connect(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in));       //如果未发生错误,则返回零
    judge_return_value("connect", ret);
    //接受消息
    char buf[128] = {};
    int buf_len = recv(sock, buf, 128, 0);
    judge_return_value("recv", buf_len);
    std::cout << buf << std::endl;
    char recv_buf[128] = {};
    char send_buf[128] = {};
    while (true)
    {
        //输入数据
        std::cin >> send_buf;
        if (strncmp(send_buf, "exit", sizeof("exit")) == 0)
        {
            DataHeader header = { 0, CMD_EXIT };
            buf_len = send(sock, (char*)&header, sizeof(DataHeader), 0);
            judge_return_value("send", buf_len);
            break;
        }
        else if (strncmp(send_buf, "login", sizeof("login")) == 0)
        {
            Login info = {};
            strcpy(info.username, "aaron");
            strcpy(info.password, "123456");
            buf_len = send(sock, (char*)&info, sizeof(Login), 0);
            judge_return_value("send", buf_len);
            LoginResult res = {};
            buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
            judge_return_value("recv", buf_len);
            std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:"<<res.result << std::endl;
        }
        else if (strncmp(send_buf, "logout", sizeof("logout")) == 0)
        {
            Logout info = { };
            strcpy(info.username, "aaron");
            buf_len = send(sock, (char*)&info, sizeof(Logout), 0);
            judge_return_value("send", buf_len);
            LogoutResult res = {};
            buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
            judge_return_value("recv", buf_len);
            std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
        }
    }
    std::cout << "连接退出" << std::endl;
    getchar();
    //关闭套接字
    closesocket(sock);
    WSACleanup();
    return 0;
}

客户端加入线程

#define WIN32_LEAN_AND_MEAN   //windows.h和WinSock2.h有宏定义冲突,导致WinSock2.h必须要在windows.h前引用,定义该宏后可不用强制位置
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS

#include<windows.h>
#include<WinSock2.h>
#include<iostream>
#include<thread>
#pragma comment(lib, "ws2_32.lib")  //method_1

enum CMD
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_NEW_USER_JOIN,
    CMD_EXIT
};
struct DataHeader
{
    short data_length;
    short cmd;
};
struct Login : public DataHeader
{
    Login()
    {
        data_length = sizeof(Login);
        cmd = CMD_LOGIN;
    }
    char username[32];
    char password[32];
};
struct LoginResult : public DataHeader
{
    LoginResult()
    {
        data_length = sizeof(LoginResult);
        cmd = CMD_LOGIN_RESULT;
    }
    int result;
};
struct Logout : public DataHeader
{
    Logout()
    {
        data_length = sizeof(Logout);
        cmd = CMD_LOGOUT;
    }
    char username[32];
};
struct LogoutResult : public DataHeader
{
    LogoutResult()
    {
        data_length = sizeof(LogoutResult);
        cmd = CMD_LOGOUT_RESULT;
    }
    int result;
};
struct NewUserJOIN : public DataHeader
{
    NewUserJOIN()
    {
        data_length = sizeof(NewUserJOIN);
        cmd = CMD_NEW_USER_JOIN;
    }
    int sock;
};

bool judge_return_value(char* function_name, int ret)
{
    if (SOCKET_ERROR == ret)
    {
        std::cout << function_name << "错误" << std::endl;
        return FALSE;
    }
    return TRUE;
}


bool return_value_error(char* function_name, int ret)
{
    if (SOCKET_ERROR == ret)
    {
        std::cout << function_name << "错误" << std::endl;
        return TRUE;
    }
    return FALSE;
}

bool flag = true;
void cmdThread(SOCKET sock)
{
    char send_buf[128] = {};
    while (true)
    {
        //输入数据
        std::cin >> send_buf;
        if (strncmp(send_buf, "exit", sizeof("exit")) == 0)
        {
            DataHeader header = { 0, CMD_EXIT };
            int buf_len = send(sock, (char*)&header, sizeof(DataHeader), 0);
            flag = false;
            judge_return_value("send", buf_len);
            break;
        }
        else if (strncmp(send_buf, "login", sizeof("login")) == 0)
        {
            Login info = {};
            strcpy(info.username, "aaron");
            strcpy(info.password, "123456");
            int buf_len = send(sock, (char*)&info, sizeof(Login), 0);
            judge_return_value("send", buf_len);
            LoginResult res = {};
            buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
            judge_return_value("recv", buf_len);
            std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
        }
        else if (strncmp(send_buf, "logout", sizeof("logout")) == 0)
        {
            Logout info = {};
            strcpy(info.username, "aaron");
            int buf_len = send(sock, (char*)&info, sizeof(Logout), 0);
            judge_return_value("send", buf_len);
            LogoutResult res = {};
            buf_len = recv(sock, (char*)&res, sizeof(LoginResult), 0);
            judge_return_value("recv", buf_len);
            std::cout << "数据指令:" << res.cmd << ",数据长度:" << res.data_length << ",返回:" << res.result << std::endl;
        }
    }
}

int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);     //Windows socket网络环境的启动函数,需要引入ws2_32库文件,可以使用method_1,也可以在属性界面链接器的输入选项添加依赖项
    //创建socket
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);    //客户端的地址由系统自动分配,不需要绑定
    //连接服务器
    sockaddr_in server_addr = {};
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(6666);
    server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    int ret = connect(sock, (sockaddr*)&server_addr, sizeof(sockaddr_in));       //如果未发生错误,则返回零
    judge_return_value("connect", ret);
    //接受消息
    char buf[128] = {};
    int buf_len = recv(sock, buf, 128, 0);
    judge_return_value("recv", buf_len);
    std::cout << buf << std::endl;
    char recv_buf[128] = {};
    char send_buf[128] = {};
    std::thread t(cmdThread, sock);   //创建线程输入cmd
    t.detach();

    while (flag)
    {
        fd_set readfds;
        FD_ZERO(&readfds);
        FD_SET(sock, &readfds);

        timeval time = { 0, 0 };   //设置等待时间为0,即非阻塞模式
        int ret = select(sock + 1, &readfds, 0, 0, &time);
        if (FD_ISSET(sock, &readfds))
        {
            DataHeader header = {};
            int buf_len = recv(sock, (char*)&header, sizeof(DataHeader), 0);
            NewUserJOIN new_user = {};
            buf_len = recv(sock, (char *)&new_user + sizeof(DataHeader), sizeof(NewUserJOIN) - sizeof(DataHeader), 0);  //如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 len 参数中请求发送的数量
            return_value_error("recv", buf_len);
            std::cout << "新客户:" << new_user.sock << "加入" << std::endl;
        }

    }
    std::cout << "连接退出" << std::endl;
    getchar();
    //关闭套接字
    closesocket(sock);
    WSACleanup();
    return 0;
}

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top