C++ Socket网络编程 1.3版本 将客户端升级为Select模型

本节将要实现的功能是:当有客户端程序连接到服务端时,服务端通知所有客户端,有新的客户端加入。

首先增加一个消息命令和消息结构体:

enum CMD
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGINOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR,
	CMD_NEWUSERJOIN,
};
struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_NEWUSERJOIN;
		result = 0;
	}
	int result;
};

服务端程序中:进行如下修改。 在有新的客户端连接时,对g_clinets 数组中的客户端发送消息,告诉大家有新的客户端接入。

		if (FD_ISSET(_sock, &fdRead))	//判断_sock是否在fdRead中, 如果在 表明有客户端连接请求
		{
			FD_CLR(_sock, &fdRead); 
			//	4. 等待接受客户端连接 accept
			sockaddr_in _clientAddr = {};
			int cliendAddrLen = sizeof(_clientAddr);
			SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端

			_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);//当客户端接入时 会得到连入客户端的socket地址和长度
			if (INVALID_SOCKET == _clientSock) //接受到无效接入
			{
				cout << "ERROR: 接受到无效客户端SOCKET..." << endl;
			}
			else
			{
				cout << "新Client加入:" << "socket = " << _clientSock << " IP = " << inet_ntoa(_clientAddr.sin_addr) << endl;  //inet_ntoa 将ip地址转换成可读的字符串
			}
			for (int n = g_clinets.size() - 1; n >= 0; n--)
			{	
				NewUserJoin userJoin;
				userJoin.cmd = CMD_NEWUSERJOIN;
				userJoin.sockId = _clientSock;
				send(g_clinets[n], (const char*)&userJoin, userJoin.dataLength, 0);
			}
			g_clinets.push_back(_clientSock);
		}

在客户端程序中,加入select网络模型,使其可以不阻塞的处理recv函数,并可以非阻塞的处理其它业务。

在客户端加入的逻辑处理模块,进行改进。
由于标准的 输入流cin是阻塞函数,因此这里将输入命令的相关逻辑删掉了。后面在接入多线程时,继续改进。

	while (true)
	{
		fd_set fdReads;
		FD_ZERO(&fdReads);
		FD_SET(_sock, &fdReads);
		timeval t = {1,0};
		int ret = select(_sock + 1, &fdReads, NULL, NULL, &t);
		if (ret < 0)
		{
			cout << "select任务结束" << endl;
			break;
		}
		if (FD_ISSET(_sock, &fdReads))	//如果_sock在fdRead里面,表明有需求等待处理
		{
			FD_CLR(_sock, &fdReads);
			if (processor(_sock) == -1)
			{
				cout << "Select任务已结束2" << endl;
				break;
			}
		}
		cout<<"客户端非阻塞的执行其它业务"<<endl;
		//Login login;
		//strcpy(login.userName, "Evila");
		//strcpy(login.Password, "Evila_passWord");
		//send(_sock, (const char*)&login, login.dataLength, 0);
		//Sleep(1000);
	}

processor函数的实现: 其实逻辑内容就是接收客户端收到的数据,并对其进行处理

int processor(SOCKET _sock)
{
	char *szRecv = new char[1024];
	//5 首先接收数据包头
	int nlen = recv(_sock, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
	if (nlen <= 0)
	{
		//客户端退出
		cout << "客户端:Socket = " << _sock << " 与服务器断开连接,任务结束" << endl;
		return -1;
	}
	DataHeader* header = (DataHeader*)szRecv;
	switch (header->cmd)
	{
		case CMD_NEWUSERJOIN:
		{
			NewUserJoin _userJoin;
			recv(_sock, (char*)&_userJoin + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
			cout << "收到服务器消息: CMD_NEWUSERJOIN:" << _userJoin.sockId << endl;
		}break;
		case CMD_LOGIN_RESULT:
		{
			LoginResult _lgRes;
			recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
			cout << "收到服务器消息: CMD_LOGIN_RESULT:" << _lgRes.result << endl;
		}break;
		case CMD_LOGOUT_RESULT:
		{
			LogoutResult _lgRes;
			recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
			cout << "收到服务器消息: CMD_LOGIN_RESULT:" << _lgRes.result << endl;
		}break;
		default:
		{
			header->cmd = CMD_ERROR;
			header->dataLength = 0;
			send(_sock, (char*)&header, sizeof(DataHeader), 0);
		}
		break;
	}
	return 0;
}

运行截图:实现功能 建议聊天室 通知房间内有新用户加入的功能。(我是狼人杀玩家,似乎这是一个狼人杀的逻辑业务)
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/La745739773/article/details/89066745