(C++ server study notes): use the select model under windows

table of Contents

The server is upgraded to the select model

Socket's select model

Client switch to Select model

The server is upgraded to the select model

Socket's select model

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
);
  • 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;
  • FD_ZERO
#define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)

[Core code]

fd_set fdRead;
fd_set fdWrite;
fd_set fdExp;

//清空集合
FD_ZERO(&fdRead);
FD_ZERO(&fdWrite);
FD_ZERO(&fdExp);

//将socket加入集合
FD_SET(sock, &fdRead);
FD_SET(sock, &fdWrite);
FD_SET(sock, &fdExp);

//将存的通信符加入集合
for (size_t i = 0; i < g_clients.size(); i++)
{
    FD_SET(g_clients[i], &fdRead);
}

//将select设置为非阻塞模式
timeval timeout = { 0,0 };

//使用select
int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &timeout);
if (ret < 0)
{
    printf("客户端已退出,任务结束\n");
    break;
}
if (FD_ISSET(sock, &fdRead))
{
    FD_CLR(sock, &fdRead);
    //4.accept 接收客户端连接
    sockaddr_in clientAddr = {};
    int clAddrLen = sizeof(sockaddr_in);

    SOCKET sockAccpt = INVALID_SOCKET;
    sockAccpt = accept(sock, (sockaddr*)&clientAddr, &clAddrLen);

    if (INVALID_SOCKET == sockAccpt)
    {
        printf("Accept Error\n");
    }
    else
    {
        printf("Accept Success\n");
    }
    printf("新客户端加入:Socket = %d,IP = %s \n", (int)sockAccpt, inet_ntoa(clientAddr.sin_addr));

    //将新加入的通信文件描述符加入
    g_clients.push_back(sockAccpt);
}

//处理集合中的文件描述符对应的通信信息
for (size_t i = 0; i <fdRead.fd_count ; i++)
{
    int ret = Processor(fdRead.fd_array[i]);
    if (-1 == ret)
    {
        //出现错误,从动态数组中删除
        auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
        if (iter != g_clients.end())
        {
            g_clients.erase(iter);
        }
    }
} 

[Complete code]

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>
#include <vector>

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

enum CMD             //命令枚举
{
    CMD_LOGIN,
    CMD_LOGIN_RESULT,
    CMD_LOGOUT,
    CMD_LOGOUT_RESULT,
    CMD_NEW_USER_JOIN,
    CMD_ERROR
};

//DataHeader
struct DataHeader      //数据包头
{
    short dataLength;
    short cmd;
};

//DataPackage
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;
        lgResult = 0;
    }
    int lgResult;
};

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;
        lgOutResult = 0;
    }
    int lgOutResult;
};

struct NewUserJoin :public DataHeader  //新加入用户
{
    NewUserJoin()
    {
        dataLength = sizeof(NewUserJoin);
        cmd = CMD_NEW_USER_JOIN;
        sockID = 0;
    }
    int sockID;
};


std::vector<SOCKET> g_clients;

int Processor(SOCKET sockAccpt)
{//缓冲区
    char szRecv[1024] = {};

    //读取包头数据
    int nLen = recv(sockAccpt, (char*)&szRecv, sizeof(DataHeader), 0);
    DataHeader* dbHeader = (DataHeader*)szRecv;
    if (nLen < 0)
    {
        printf("客户端<%d>已退出,任务结束\n", sockAccpt);
        return -1;
    }
    //if(nLen >= sizeof(DataHeader))
    switch (dbHeader->cmd)
    {
    case CMD_LOGIN:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
        Login* login = (Login*)szRecv;
        printf("收到客户端<Socket%d>请求:CMD_LOGIN ,数据长度: %d, UserName = %s, \
                    PassWord = %s \n", sockAccpt, login->dataLength, login->UserName, login->PassWord);
        //忽略对用户密码进行判断
        LoginResult lgRet = {};
        send(sockAccpt, (char*)&lgRet, sizeof(LoginResult), 0);
    }
    break;
    case CMD_LOGOUT:
    {
        recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
        LogOut* logout = (LogOut*)szRecv;
        printf("收到客户端<Socket%d>请求:CMD_LOGOUT ,数据长度: %d, UserName = %s, \
                    \n", sockAccpt, logout->dataLength, logout->UserName);
        //忽略对用户密码进行判断
        LogOutResult lgOutRet = {};
        send(sockAccpt, (char*)&lgOutRet, sizeof(LogOutResult), 0);
    }
    break;
    default:
        DataHeader HeaderError = { 0, CMD_ERROR };
        send(sockAccpt, (char*)&HeaderError, sizeof(HeaderError), 0);
        break;
    }
}

int main()
{
    WORD ver = MAKEWORD(2, 2);
    WSAData dat;
    WSAStartup(ver, &dat);

    //1.创建socket套接字
    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //2,bind 绑定用于接收客户端连接的端口
    sockaddr_in sinAddr = {};
    sinAddr.sin_family = AF_INET;
    sinAddr.sin_port = htons(5678); //host to net unsigned short
    sinAddr.sin_addr.S_un.S_addr = INADDR_ANY;  //inet_addr("127.0.0.1")
    if (SOCKET_ERROR == bind(sock, (sockaddr*)&sinAddr, sizeof(sockaddr_in)))
    {
        printf("Bind Error\n");
    }
    else
    {
        printf("Bind Success\n");
    }

    //3. listen 监听网络端口
    if (SOCKET_ERROR == listen(sock, 5))
    {
        printf("Listen Error\n");
    }
    else
    {
        printf("Listen Success\n");
    }

    while (true)
    {
        //伯克利 socket
        fd_set fdRead;
        fd_set fdWrite;
        fd_set fdExp;

        //清空集合
        FD_ZERO(&fdRead);
        FD_ZERO(&fdWrite);
        FD_ZERO(&fdExp);

        //将socket加入集合
        FD_SET(sock, &fdRead);
        FD_SET(sock, &fdWrite);
        FD_SET(sock, &fdExp);

        //将存的通信符加入集合
        for (size_t i = 0; i < g_clients.size(); i++)
        {
            FD_SET(g_clients[i], &fdRead);
        }

        //将select设置为非阻塞模式
        timeval timeout = { 0,0 };

        //使用select
        int ret = select(sock + 1, &fdRead, &fdWrite, &fdExp, &timeout);
        if (ret < 0)
        {
            printf("select任务结束\n");
            break;
        }
        if (FD_ISSET(sock, &fdRead))
        {
            FD_CLR(sock, &fdRead);
            //4.accept 接收客户端连接
            sockaddr_in clientAddr = {};
            int clAddrLen = sizeof(sockaddr_in);

            SOCKET sockAccpt = INVALID_SOCKET;
            sockAccpt = accept(sock, (sockaddr*)&clientAddr, &clAddrLen);

            if (INVALID_SOCKET == sockAccpt)
            {
                printf("Accept Error\n");
            }
            else
            {
                //群发消息
                for (size_t i = 0; i < g_clients.size(); i++)
                {
                    NewUserJoin userJoin;
                    send(g_clients[i], (const char*)&userJoin, sizeof(NewUserJoin), 0);
                }
                //将新加入的通信文件描述符加入
                g_clients.push_back(sockAccpt);
                printf("新客户端加入:Socket = %d,IP = %s \n", (int)sockAccpt, inet_ntoa(clientAddr.sin_addr));
            }
        }

        //处理集合中的文件描述符对应的通信信息
        for (size_t i = 0; i <fdRead.fd_count ; i++)
        {
            int ret = Processor(fdRead.fd_array[i]);
            if (-1 == ret)
            {
                //出现错误,从动态数组中删除
                auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[i]);
                if (iter != g_clients.end())
                {
                    g_clients.erase(iter);
                }
            }
        } 
    }

    //关闭通信文件描述符
    for (size_t i = 0; i < g_clients.size(); i++)
    {
        closesocket(g_clients[i]);
    }

    //closesocket 关闭套接字
    closesocket(sock);

    WSACleanup();

    printf("结束任务\n");
    getchar();
    return 0;
 }

Client switch to Select model

[Main code]

fd_set fdRead;

//清空集合
FD_ZERO(&fdRead);

//将socket加入集合
FD_SET(sockCli, &fdRead);

//将select设置为非阻塞模式
timeval timeout = { 0,0 };

//使用select
int ret = select(sockCli + 1, &fdRead, NULL, NULL, &timeout);
if (ret < 0)
{
	printf("select任务结束\n");
	break;
}

if (FD_ISSET(sockCli, &fdRead))
{
	FD_CLR(sockCli, &fdRead);
	if (-1 == Processor(sockCli))
	{
		printf("select任务结束2\n");
		break;
	}
}

[Client source code]

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define  _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#include <cstdio>
#pragma comment(lib,"ws2_32.lib")

enum CMD             //命令枚举
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR
};

//DataHeader
struct DataHeader      //数据包头
{
	short dataLength;
	short cmd;
};

//DataPackage
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;
		lgResult = 0;
	}
	int lgResult;
};

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;
		lgOutResult = 0;
	}
	int lgOutResult;
};

struct NewUserJoin :public DataHeader  //新加入用户
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sockID = 0;
	}
	int sockID;
};


int Processor(SOCKET sockAccpt)
{//缓冲区
	char szRecv[1024] = {};

	//读取包头数据
	int nLen = recv(sockAccpt, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader* dbHeader = (DataHeader*)szRecv;
	if (nLen < 0)
	{
		printf("与服务断开,任务结束\n");
		return -1;
	}
	//if(nLen >= sizeof(DataHeader))
	switch (dbHeader->cmd)
	{
		case CMD_LOGIN:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			Login* login = (Login*)szRecv;
			printf("收到服务端返回数据:CMD_LOGIN ,数据长度: %d,\n", dbHeader->dataLength);
		}
		break;
		case CMD_LOGOUT:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			LogOut* logout = (LogOut*)szRecv;
			printf("收到服务端返回数据:CMD_LOGOUT ,数据长度: %d \n", dbHeader->dataLength);
		}
		break; 
		case CMD_NEW_USER_JOIN:
		{
			recv(sockAccpt, szRecv + sizeof(DataHeader), dbHeader->dataLength - sizeof(DataHeader), 0);
			NewUserJoin* newJoin = (NewUserJoin*)szRecv;
			printf("收到服务端返回数据:CMD_NEW_USER_JOIN ,数据长度: %d \n", dbHeader->dataLength);
		}
		break;
		default:
			break;
	}
}

int main()
{
	WORD ver = MAKEWORD(2, 2);
	WSAData dat;
	WSAStartup(ver, &dat);

	//1.建立一个socket
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == sockCli)
	{
		printf("Socket Error\n");
	}
	else
	{
		printf("Socket Success\n");
	}

	//2. connect连接服务器
	sockaddr_in servAddr = {};
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(5678);
	servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

	int ret = connect(sockCli, (sockaddr*)&servAddr, sizeof(sockaddr_in));

	if (SOCKET_ERROR == ret)
	{
		printf("Connect Error\n");
	}
	else
	{
		printf("Connect Success\n");
	}

	while (true)
	{
		fd_set fdRead;
	
		//清空集合
		FD_ZERO(&fdRead);

		//将socket加入集合
		FD_SET(sockCli, &fdRead);

		//将select设置为非阻塞模式
		timeval timeout = { 0,0 };

		//使用select
		int ret = select(sockCli + 1, &fdRead, NULL, NULL, &timeout);
		if (ret < 0)
		{
			printf("select任务结束\n");
			break;
		}

		if (FD_ISSET(sockCli, &fdRead))
		{
			FD_CLR(sockCli, &fdRead);
			if (-1 == Processor(sockCli))
			{
				printf("select任务结束2\n");
				break;
			}
		}

		printf("空闲时间,处理其他业务\n");
		Login lgin{};
		strcpy(lgin.UserName, "喜羊羊");
		strcpy(lgin.PassWord, "123456");
		send(sockCli, (const char*)&lgin, sizeof(Login), 0);
		Sleep(1000);
	}

	//7.关闭套接字 closesocket
	closesocket(sockCli);

	WSACleanup();
	printf("结束任务\n");
	getchar();
	return 0;
}

 

Guess you like

Origin blog.csdn.net/baidu_41388533/article/details/112389983