(C ++サーバー研究ノート):ウィンドウの下で選択モデルを使用する

目次

サーバーが選択されたモデルにアップグレードされます

ソケットの選択モデル

モデルの選択へのクライアントの切り替え

サーバーが選択されたモデルにアップグレードされます

ソケットの選択モデル

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)

【コアコード】

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);
        }
    }
} 

[完全なコード]

#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;
 }

モデルの選択へのクライアントの切り替え

【メインコード】

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;
	}
}

【クライアントソースコード】

#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;
}

 

おすすめ

転載: blog.csdn.net/baidu_41388533/article/details/112389983