单线程select模型
阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。
函数只有在得到结果之后才会返回。
非阻塞
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
/**
参 数 :int nfds -- maxfd是要监视的最大的文件描述符值+1;
参 数 : fd_set *readfds -- 读文件描述符的集合
参 数 : fd_set *writefds -- 可写文件描述符集合
参 数 : fd_set *exceptfds -- 异常文件描述符集合
参 数 : struct timeval *timeout --超时描述,在一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
返回值 :
说 明 :select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型。
*/
int select(_In_ int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval * timeout);
FD_ZERO(fd_set *fdset); --将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。 FD_SET(fd_set *fdset); --用于在文件描述符集合中增加一个新的文件描述符。 FD_CLR(fd_set *fdset); --用于在文件描述符集合中删除一个文件描述符。 FD_ISSET(int fd,fd_set *fdset); --用于测试指定的文件描述符是否在该集合中。
服务端
#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #include<windows.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") #else #include<unistd.h> //uni std #include<arpa/inet.h> #include<string.h> #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include<stdio.h> #include<thread> #include<vector>
std::vector<SOCKET> g_clients; int processor(SOCKET _cSock) {
// 处理业务 return 0; } int main() { #ifdef _WIN32 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); //------------ #endif //-- 用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 short #ifdef _WIN32 _sin.sin_addr.S_un.S_addr = INADDR_ANY;//inet_addr("127.0.0.1"); #else _sin.sin_addr.s_addr = INADDR_ANY; #endif if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin))) { printf("错误,绑定网络端口失败...\n"); } else { printf("绑定网络端口成功...\n"); } // 3 listen 监听网络端口 if (SOCKET_ERROR == listen(_sock, 5)) { printf("错误,监听网络端口失败...\n"); } else { printf("监听网络端口成功...\n"); } while (true) { //伯克利套接字 BSD socket //描述符(socket) 集合
//fd_set 最大值为默认64, 所以一次最大能处理64个链接 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); SOCKET maxSock = _sock; for (int n = (int)g_clients.size() - 1; n >= 0; n--) { FD_SET(g_clients[n], &fdRead); if (maxSock < g_clients[n]) { maxSock = g_clients[n]; } } ///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量 ///既是所有文件描述符最大值+1 在Windows中这个参数可以写0 //select 函数不设置 timeval 时,select是阻塞的,直到有数据。 //select 函数设置 timeval 时,超时即返回、select是非阻塞的。 timeval t = { 1,0 }; int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t); if (ret < 0) { printf("select任务结束。\n"); break; } //判断描述符(socket)是否在集合中 if (FD_ISSET(_sock, &fdRead)) { FD_CLR(_sock, &fdRead); // 4 accept 等待接受客户端连接 sockaddr_in clientAddr = {}; int nAddrLen = sizeof(sockaddr_in); SOCKET _cSock = INVALID_SOCKET; #ifdef _WIN32 _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); #else _cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen); #endif if (INVALID_SOCKET == _cSock) { printf("错误,接受到无效客户端SOCKET...\n"); } else { g_clients.push_back(_cSock); printf("新客户端加入:socket = %d,IP = %s \n", (int)_cSock, inet_ntoa(clientAddr.sin_addr)); } } for (int n = (int)g_clients.size() - 1; n >= 0; n--) { if (FD_ISSET(g_clients[n], &fdRead)) { if (-1 == processor(g_clients[n])) { auto iter = g_clients.begin() + n; if (iter != g_clients.end()) { g_clients.erase(iter); } } } } //printf("空闲时间处理其它业务..\n"); } #ifdef _WIN32 for (int n = (int)g_clients.size() - 1; n >= 0; n--) { closesocket(g_clients[n]); } // 8 关闭套节字closesocket closesocket(_sock); //------------ //清除Windows socket环境 WSACleanup(); #else for (int n = (int)g_clients.size() - 1; n >= 0; n--) { close(g_clients[n]); } // 8 关闭套节字closesocket close(_sock); #endif printf("已退出。\n"); getchar(); return 0; }
#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define _WINSOCK_DEPRECATED_NO_WARNINGS #include<windows.h> #include<WinSock2.h> #pragma comment(lib,"ws2_32.lib") #else #include<unistd.h> //uni std #include<arpa/inet.h> #include<string.h> #define SOCKET int #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) #endif #include<stdio.h> #include<thread> bool g_bRun = true; // 主线程中处理数据 int processor(SOCKET _cSock) { //缓冲区 char szRecv[4096] = {}; // 接收数据 // recv to do return 0; } //线程中发送数据 void sendThread(SOCKET sock) { while (true) { char cmdBuf[256] = {}; // 发送数据 // send to do } } int main() { #ifdef _WIN32 //启动Windows socket 2.x环境 WORD ver = MAKEWORD(2, 2); WSADATA dat; WSAStartup(ver, &dat); #endif //------------ //-- 用Socket API建立简易TCP客户端 // 1 建立一个socket 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); #ifdef _WIN32 _sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); #else _sin.sin_addr.s_addr = inet_addr("192.168.74.1"); #endif int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in)); if (SOCKET_ERROR == ret) { printf("错误,连接服务器失败...\n"); } else { printf("连接服务器成功...\n"); } //启动线程处理发送业务 std::thread t1(sendThread, _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) { printf("select任务结束1\n"); break; } if (FD_ISSET(_sock, &fdReads)) { FD_CLR(_sock, &fdReads); if (-1 == processor(_sock)) { printf("select任务结束2\n"); break; } } //printf("空闲时间处理其它业务..\n"); } // 关闭套节字closesocket #ifdef _WIN32 closesocket(_sock); //清除Windows socket环境 WSACleanup(); #else close(_sock); #endif printf("已退出。\n"); getchar(); return 0; }