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;
}
运行截图:实现功能 建议聊天室 通知房间内有新用户加入的功能。(我是狼人杀玩家,似乎这是一个狼人杀的逻辑业务)