对fd_set的理解,可以参考下:
https://www.cnblogs.com/wuyepeng/p/9745573.html
int select(int nfds, fd_set* readset, fd_set* writeset, fe_set* exceptset, struct timeval* timeout);
参数:
nfds 需要检查的文件描述字个数(集合中最大描述符+1)
readset 用来检查可读性的一组文件描述字。
writeset 用来检查可写性的一组文件描述字。
exceptset 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout 超时,填NULL为阻塞,填0为非阻塞,其他为一段超时时间
返回值:
返回fd的总数,错误时返回SOCKET_ERROR
select对集合进行筛选,返回时,只保留符合状态的描述符
----------------------------------------------------------------------------------------
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(1, &fdset); //将套接字1加入 fdset集合
FD_SET(2, &fdset); //将套接字2加入 fdset集合
FD_SET(3, &fdset); //将套接字3加入 fdset集合
FD_SET(7, &fdset); //将套接字7加入 fdset集合
FD_CLR(3, &fdset); //将套接字3从 fdset 集合移除,fdset.fdReadd_array[2]就被清除了,
数组后面的数据依次往前提,即7被放到了fd_array[2]
服务端:
//vc的第一套socket,第二套socket,可能存在冲突 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS //也可以放到工程属性,预处理中 #include <windows.h> #include <WinSock2.h> #include <stdio.h> #include <vector> #pragma comment(lib, "ws2_32.lib") enum CMD { CMD_LOGIN, CMD_LOGIN_RET, CMD_LOGOUT, CMD_LOGOUT_RET, CMD_ERROR }; struct DataHeader { int dataLength; CMD 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_RET; ret = 0; } int ret; }; 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_RET; ret = 0; } int ret; }; int processor(SOCKET _cSock) { //5 接收客户端数据 char szRecv[1024] = {}; int nLen = recv(_cSock, szRecv, sizeof(DataHeader), 0); if (nLen <= 0) { printf("客户端%d已退出。\n", _cSock); return -1; } DataHeader* pHeader = (DataHeader*)szRecv; switch (pHeader->cmd) { case CMD_LOGIN: { recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); Login *pLogin = (Login*)szRecv; printf("客户端%d,cmd logint,user:%s,password:%s\n", _cSock, pLogin->userName, pLogin->passWord); //---------------------------- //---------------------------- LoginResult longinRet = {}; send(_cSock, (const char*)&longinRet, sizeof(LoginResult), 0); } break; case CMD_LOGOUT: { recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); LogOut *pLogOut = (LogOut*)szRecv; printf("客户端%d,cmd logout,user:%s\n", _cSock, pLogOut->userName); //---------------------------- LogOutResult logOutRet = {}; send(_cSock, (const char*)&logOutRet, sizeof(LogOutResult), 0); } break; default: { //剩余的垃圾数据是否要读取出来? if (pHeader->dataLength >0) { recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0); } DataHeader header = {}; header.cmd = CMD_ERROR; header.dataLength = 0; send(_cSock, (const char*)&header, sizeof(DataHeader), 0); } break; } return 0; } std::vector<SOCKET> g_clients; int main() { //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); //--用Socket API建立简易TCP服务端 //1.建立一个socket SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); //2 bind绑定用于接受客户端连接的网络端口 sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(4567);//host to net unsigned _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //使用127.0.0.1可以防止外网访问 //启用本机全部的ip地址可以使用,INADDR_ANY if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in))) { printf("错误,绑定网络端口失败...\n"); } else { printf("绑定网络端口成功...\n"); } //3 listen 监听网络端口 if (SOCKET_ERROR == listen(_sock, 5)) { printf("错误,监听网络端口失败...\n"); } else { printf("监听网络端口成功...\n"); } while (true) { fd_set fdRead; fd_set fdWrite; fd_set fdExp; FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_ZERO(&fdExp); FD_SET(_sock, &fdRead); FD_SET(_sock, &fdWrite); FD_SET(_sock, &fdExp); //接入的客户端socket放入到 可读集合中,通过select来筛选出具有可读状态的socket for (size_t n = 0; n<g_clients.size(); n++) { FD_SET(g_clients[n], &fdRead); } timeval t = {0,0}; int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, &t); if (ret < 0) { printf("select任务结束。\n"); break; } if (FD_ISSET(_sock, &fdRead)) { FD_CLR(_sock, &fdRead);//从集合移除了_sock sockaddr_in clientAddr = {}; int nAddrLen = sizeof(clientAddr); SOCKET _cSock = INVALID_SOCKET; _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); if (INVALID_SOCKET == _cSock) { printf("错误,接受到无效客户端socket...\n"); } else { g_clients.push_back(_cSock); printf("新客户端加入:socket=%d,IP=%s\n", _cSock, inet_ntoa(clientAddr.sin_addr)); } } for (size_t n = 0; n<fdRead.fd_count; n++) { if (-1 == processor(fdRead.fd_array[n])) { auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]); if (iter != g_clients.end()) { closesocket(*iter); g_clients.erase(iter); } } } } //6 关闭套接字closesocket for (size_t n = 0; n<g_clients.size(); n++) { closesocket(g_clients[n]); } closesocket(_sock); //清除Windows socket环境 WSACleanup(); printf("已退出。\n"); getchar(); return 0; }
客户端:
#define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS //也可以放到工程属性,预处理中 //#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <WinSock2.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") enum CMD { CMD_LOGIN, CMD_LOGIN_RET, CMD_LOGOUT, CMD_LOGOUT_RET, CMD_ERROR }; struct DataHeader { int dataLength; CMD 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_RET; ret = 1; } int ret; }; 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_RET; ret = 1; } int ret; }; int main() { //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); //--------------------------- //--用Socket API建立简易TCP客户端 //1 建立一个socket SOCKET _sock = INVALID_SOCKET; _sock = socket(AF_INET, SOCK_STREAM, 0); if (INVALID_SOCKET == _sock) { printf("错误,建立Socket失败...\n"); } else { printf("建立Socket成功...\n"); } //2 连接服务器connect sockaddr_in _sin = {}; _sin.sin_family = AF_INET; _sin.sin_port = htons(4567);//host to net unsigned _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) { printf("错误,连接服务端失败...\n"); closesocket(_sock); //清除Windows socket环境 WSACleanup(); printf("已退出。"); getchar(); return 0; } else { printf("连接服务端成功...\n"); } while (true) { //3输入请求命令 char cmdBuf[128] = {}; scanf_s("%s", cmdBuf, 128); getchar(); //4处理请求 if (0 == strcmp(cmdBuf, "exit")) { printf("收到exit命令,任务结束。\n"); break; } else if(0 == strcmp(cmdBuf, "login")) { Login login = {}; strcpy_s(login.userName, "jj"); strcpy_s(login.passWord, "ww"); int nSend = send(_sock, (const char*)&login, sizeof(Login), 0); printf("send length %d\n", nSend); LoginResult cmd_ret = {}; recv(_sock, (char*)&cmd_ret, sizeof(LoginResult), 0); printf("Login result:%d\n", cmd_ret.ret); } else if (0 == strcmp(cmdBuf, "logout")) { LogOut log_out = {}; strcpy_s(log_out.userName, "jj"); send(_sock, (const char*)&log_out, sizeof(LogOut), 0); LogOutResult cmd_ret = {}; recv(_sock, (char*)&cmd_ret, sizeof(LogOutResult), 0); printf("LogOut result:%d\n", cmd_ret.ret); } else { printf("wrong cmd, please input cmd again.\n"); } } //7 关闭套接字closesocket closesocket(_sock); //清除Windows socket环境 WSACleanup(); printf("已退出"); getchar(); return 0; }
//vc的第一套socket,第二套socket,可能存在冲突
#define WIN32_LEAN_AND_MEAN#define _WINSOCK_DEPRECATED_NO_WARNINGS //也可以放到工程属性,预处理中
#include <windows.h>#include <WinSock2.h>#include <stdio.h>#include <vector>
#pragma comment(lib, "ws2_32.lib")
enum CMD{CMD_LOGIN,CMD_LOGIN_RET,CMD_LOGOUT,CMD_LOGOUT_RET,CMD_ERROR};
struct DataHeader{int dataLength;CMD 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_RET;ret = 0;}int ret;};
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_RET;ret = 0;}int ret;};
int processor(SOCKET _cSock){
//5 接收客户端数据char szRecv[1024] = {};
int nLen = recv(_cSock, szRecv, sizeof(DataHeader), 0);if (nLen <= 0){printf("客户端%d已退出。\n", _cSock);return -1;}
DataHeader* pHeader = (DataHeader*)szRecv;
switch (pHeader->cmd){case CMD_LOGIN:{recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0);Login *pLogin = (Login*)szRecv;printf("客户端%d,cmd logint,user:%s,password:%s\n", _cSock, pLogin->userName, pLogin->passWord);//----------------------------//----------------------------LoginResult longinRet = {};send(_cSock, (const char*)&longinRet, sizeof(LoginResult), 0);}break;case CMD_LOGOUT:{
recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0);LogOut *pLogOut = (LogOut*)szRecv;printf("客户端%d,cmd logout,user:%s\n", _cSock, pLogOut->userName);//----------------------------LogOutResult logOutRet = {};send(_cSock, (const char*)&logOutRet, sizeof(LogOutResult), 0);}break;default:{//剩余的垃圾数据是否要读取出来?if (pHeader->dataLength >0){recv(_cSock, szRecv + sizeof(DataHeader), pHeader->dataLength - sizeof(DataHeader), 0);}
DataHeader header = {};header.cmd = CMD_ERROR;header.dataLength = 0;send(_cSock, (const char*)&header, sizeof(DataHeader), 0);}break;}
return 0;}
std::vector<SOCKET> g_clients;
int main(){//启动Windows socket 2.x环境WORD ver = MAKEWORD(2, 2);WSADATA dat;WSAStartup(ver, &dat);
//--用Socket API建立简易TCP服务端//1.建立一个socketSOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//2 bind绑定用于接受客户端连接的网络端口sockaddr_in _sin = {};_sin.sin_family = AF_INET;_sin.sin_port = htons(4567);//host to net unsigned_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//使用127.0.0.1可以防止外网访问//启用本机全部的ip地址可以使用,INADDR_ANYif (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in))){printf("错误,绑定网络端口失败...\n");}else{printf("绑定网络端口成功...\n");}//3 listen 监听网络端口if (SOCKET_ERROR == listen(_sock, 5)){printf("错误,监听网络端口失败...\n");}else{printf("监听网络端口成功...\n");}
while (true){fd_set fdRead;fd_set fdWrite;fd_set fdExp;
FD_ZERO(&fdRead);FD_ZERO(&fdWrite);FD_ZERO(&fdExp);
FD_SET(_sock, &fdRead);FD_SET(_sock, &fdWrite);FD_SET(_sock, &fdExp);//接入的客户端socket放入到 可读集合中,通过select来筛选出具有可读状态的socketfor (size_t n = 0; n<g_clients.size(); n++){FD_SET(g_clients[n], &fdRead);}
timeval t = {0,0};int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, &t);if (ret < 0){printf("select任务结束。\n");break;}
if (FD_ISSET(_sock, &fdRead)){FD_CLR(_sock, &fdRead);//从集合移除了_socksockaddr_in clientAddr = {};int nAddrLen = sizeof(clientAddr);SOCKET _cSock = INVALID_SOCKET;
_cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);if (INVALID_SOCKET == _cSock){printf("错误,接受到无效客户端socket...\n");}else{g_clients.push_back(_cSock);printf("新客户端加入:socket=%d,IP=%s\n", _cSock, inet_ntoa(clientAddr.sin_addr));}
}
for (size_t n = 0; n<fdRead.fd_count; n++){if (-1 == processor(fdRead.fd_array[n])){auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);if (iter != g_clients.end()){closesocket(*iter);g_clients.erase(iter);}}}
}
//6 关闭套接字closesocketfor (size_t n = 0; n<g_clients.size(); n++){closesocket(g_clients[n]);}closesocket(_sock);
//清除Windows socket环境WSACleanup();printf("已退出。\n");getchar();return 0;}