目录
服务端升级为select模型
Socket的select模型
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; }
客户端转为Select模型
【主要代码】
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; }