请先参看网络编程一(服务器、客户端)
https://www.cnblogs.com/dreammmz/p/13500249.html
网络编程一无法实现多个客户端同时连接访问,对其进行修改。
加入select()函数,该函数用于监视文件描述符的变化情况——读写或是异常。
int select (int maxfd + 1, fd_set* readset, fd_set* writeset,
fd_set* exceptset, const struct timeval* timeout);
返回值:>0:就绪描述字的正数目;-1:出错;0:超时。
fd_set fdRead; fd_set fdWrite; fd_set fdExp; //初始化个数为0; FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_ZERO(&fdExp); FD_SET(_sock, &fdRead); FD_SET(_sock, &fdWrite); FD_SET(_sock, &fdExp); timeval t = {1,0}; int ret=select(_sock +1, &fdRead,&fdWrite,&fdExp, &t);
maxfd + 1:最大的描述符(套接字)加1。
readset:用于检查可读性。
writeset:用于检查可写性。
exceptset:用于检查带外数据。
上面三个类型都为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;
timeout:一个timeval类型的对象,用来决定select等待I/O的最长时间。如果为NULL则一直等待,此时处于阻塞状态。
struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ };
完成代码:
服务器:
#define WIN32_LEAN_AND_MEAN #include<iostream> #include<vector> //注意将WinSock2放在之前 否则会报错 //或者使用宏定义 #include<WinSock2.h> #include<Windows.h> //明确指定需要一个动态库 //在工程中连接器中加入lib动态库 #pragma comment(lib,"ws2_32.lib") enum CMD { CMD_LOGIN, CMD_LOGIN_RESULT, CMD_LOGOUT, CMD_LOGOUT_RESULT, CMD_NEW_USER_JOIN, CMD_ERROR }; struct DataHeader { short dataLength; short cmd; }; 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; result = 0; } int result; }; 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; result = 0; } int result; }; struct NewUserJoin :public DataHeader { NewUserJoin() { dataLength = sizeof(NewUserJoin); cmd = CMD_NEW_USER_JOIN; scok = 0; } int scok; }; //创建全局的vector用来存储加入的客户端 std::vector<SOCKET> g_clients; //用来处理客户端请求 int processor(SOCKET _cSock) { char szRecv[4096] = {}; //5、接受客户端的请求数据 int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0); DataHeader* header = (DataHeader*)szRecv; if (nLen <= 0) { std::cout << "客户端已退出,任务结束。socket="<< _cSock <<std::endl; return -1; } switch (header->cmd) { case CMD_LOGIN: { Login login = {}; recv(_cSock, (char*)&login + sizeof(DataHeader), sizeof(Login) - sizeof(DataHeader), 0); std::cout << "收到客户端:" << _cSock << ";" << std::endl << "数据长度:" << login.dataLength << ";" << std::endl << "userName:" << login.userName << ";" << std::endl << "userPass:" << login.PassWord << std::endl; LoginResult ret; send(_cSock, (char*)&ret, sizeof(LoginResult), 0); } break; case CMD_LOGOUT: { Logout logout = {}; recv(_cSock, (char*)&logout + sizeof(DataHeader), sizeof(logout) - sizeof(DataHeader), 0); std::cout << "收到客户端:" << _cSock << ";" << std::endl << "数据长度:" << logout.dataLength << ";" << std::endl << "userName:" << logout.userName << std::endl; //忽略判断用户名密码 LogoutResult ret; send(_cSock, (char*)&ret, sizeof(ret), 0); } break; default: header->cmd = CMD_ERROR; header->dataLength = 0; send(_cSock, (char*)&header, sizeof(header), 0); break; } return 0; } int main() { //调用windows库文件 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); //1、建立一个socket 套接字 SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //将套接字和IP、端口绑定 //2、bind 绑定用于接受客户端连接的 网路端口 sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(4567);//绑定一个端口号 _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //绑定到那个ip地址 if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin))) { std::cout << "绑定IP、端口失败~" << std::endl; } else { std::cout << "绑定IP、端口成功~" << std::endl; } //3、listen 监听网络接口 //#define INVALID_SOCKET (SOCKET)(~0) //#define SOCKET_ERROR (-1) if (SOCKET_ERROR == listen(_sock, 5)) { std::cout << "监听接口开启失败~" << std::endl; } else { std::cout << "监听接口开启成功~" << std::endl; } while (true) { //描述符(socket)集合 fd_set fdRead; fd_set fdWrite; fd_set fdExp; //初始化个数为0; FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_ZERO(&fdExp); FD_SET(_sock, &fdRead); FD_SET(_sock, &fdWrite); FD_SET(_sock, &fdExp); for (int n = (int)g_clients.size()-1; n>=0 ; --n) { FD_SET(g_clients[n], &fdRead); } timeval t = {1,0}; int ret=select(_sock +1, &fdRead,&fdWrite,&fdExp, &t); if (ret < 0) { std::cout << "select任务结束。" << std::endl; break; } if (FD_ISSET(_sock, &fdRead)) { FD_CLR(_sock, &fdRead); //4、accept 等待接受客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(sockaddr_in); SOCKET _cSock = INVALID_SOCKET; _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); if (INVALID_SOCKET == _cSock) { std::cout << "客户端连接失败~" << std::endl; } else { //循环发送给每个客户端有新用户加入 for (int n = (int)g_clients.size() - 1; n >= 0; --n) { NewUserJoin userJoin; send(g_clients[n], (const char*)&userJoin, sizeof(NewUserJoin), 0); } //将新用户加入到vector中 g_clients.push_back(_cSock); std::cout << "新加入客户端IP:" << inet_ntoa(clientAddr.sin_addr) << std::endl; } } //处理fdRead中的内容 for (unsigned int n = 0; n < fdRead.fd_count; ++n) { if (-1 == processor(fdRead.fd_array[n])) { //判断客户端是否退出 //如果退出需要从vector中将其去除 auto iter = find(g_clients.begin(),g_clients.end(), fdRead.fd_array[n]); if (iter != g_clients.end()) { g_clients.erase(iter); } } } } //出现异常逐个关闭套接字 for (int n = g_clients.size(); n >= 0; --n) { closesocket(g_clients[n]); } //closesocket关闭套接字 closesocket(_sock); WSACleanup(); std::cout << "已退出,任务结束。" << std::endl; system("pause"); return 0; }
客户端:
#define _CRT_SECURE_NO_WARNINGS #define WIN32_LEAN_AND_MEAN #include<iostream> #include<thread> //注意将WinSock2放在之前 否则会报错 //或者使用宏定义 #include<WinSock2.h> #include<Windows.h> //明确指定需要一个动态库 //在工程中连接器中加入lib动态库 #pragma comment(lib,"ws2_32.lib") enum CMD { CMD_LOGIN, CMD_LOGIN_RESULT, CMD_LOGOUT, CMD_LOGOUT_RESULT, CMD_NEW_USER_JOIN, CMD_ERROR }; struct DataHeader { short dataLength; short cmd; }; //登录 //包体继承包头信息 //生成一个完整的包 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; result = 0; } int result; }; //退出 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; result = 0; } int result; }; //网络数据报文的格式定义 //报文有两个部分 包头和包体 //包头 描述本次消息包的大小 描述数据的作用 //包体 传输数据 struct NewUserJoin :public DataHeader { NewUserJoin() { dataLength = sizeof(NewUserJoin); cmd = CMD_NEW_USER_JOIN; scok = 0; } int scok; }; int processor(SOCKET _cSock) { char szRecv[4096] = {}; //5、接受客户端的请求数据 int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0); DataHeader* header = (DataHeader*)szRecv; if (nLen <= 0) { std::cout << "与服务器断开连接,任务结束。" << std::endl; return -1; } //通过接收到的header来判断 //客户端发送的命令是什么 //同时给客户端反馈 switch (header->cmd) { case CMD_LOGIN_RESULT: { recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength- sizeof(DataHeader), 0); LoginResult* login = (LoginResult*)szRecv; std::cout << "收到服务器CMD_LOGIN_RESULT;" << "数据长度:" << login->dataLength << ";" << std::endl; } break; case CMD_LOGOUT_RESULT: { recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0); LogoutResult* logout = (LogoutResult*)szRecv; std::cout << "收到服务器CMD_LOGOUT_RESULT;" << "数据长度:" << logout->dataLength << ";" << std::endl; } break; case CMD_NEW_USER_JOIN: { recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0); NewUserJoin* userJoin = (NewUserJoin*)szRecv; std::cout << "收到服务器消息 CMD_NEW_USER_JOIN; " << "数据长度:" << userJoin->dataLength << ";" << std::endl; } break; default: break; } return 0; } bool g_bRun = true; void cmdThread(SOCKET _sock) { while (true) { char cmdBuf[256] = {}; std::cin >> cmdBuf; if (0 == strcmp(cmdBuf, "exit")) { g_bRun = false; std::cout << "收到exit命令,任务结束。" << std::endl; break; } else if (0 == strcmp(cmdBuf, "login")) { //5、向服务器发送命令 Login login; strcpy(login.userName, "xmq"); strcpy(login.PassWord, "mima"); send(_sock, (const char*)&login, sizeof(login), 0); } else if (0 == strcmp(cmdBuf, "logout")) { //5、向服务器发送命令 Logout logout; strcpy(logout.userName, "xmq"); send(_sock, (const char*)&logout, sizeof(logout), 0); } else { std::cout << "不支持命令,请重新输入。" << std::endl; } } } int main() { //调用windows库文件 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); //1、建立一个socket SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == _sock) { std::cout << "建立socket失败~" << std::endl; } else { std::cout << "建立socket成功~" << std::endl; } //2、连接服务器 sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(4567); _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)); if (SOCKET_ERROR == ret) { std::cout << "连接服务器失败" << std::endl; } else { std::cout << "连接服务器成功" << std::endl; } //启动线程 std::thread t1(cmdThread,_sock); t1.detach(); while (g_bRun) { fd_set fdReads; FD_ZERO(&fdReads); FD_SET(_sock, &fdReads); timeval t = {1,0}; int ret = select(_sock + 1, &fdReads, 0, 0, &t); if (ret < 0) { std::cout << "select任务结束1。" << std::endl; break; } if (FD_ISSET(_sock, &fdReads)) { FD_CLR(_sock, &fdReads); if (-1 == processor(_sock)) { std::cout << "select任务结束2。" << std::endl; break; } } //std::cout << "空闲时间处理其他任务。" << std::endl; } //7、closesocket 关闭套接字 closesocket(_sock); WSACleanup(); std::cout << "已退出,任务结束。" << std::endl; system("pause"); return 0; }