Windows网络编程之select模型(二)
一、select模型的特点
select
函数通常用于多路复用(multiplexing)操作,允许你同时监视多个套接字(sockets)的状态,并在其中任何一个套接字准备好进行 I/O 操作时进行响应。
以下是 select
模型的特点和作用:
-
并发处理多个套接字:
select
允许你同时监视多个套接字的状态,例如可读、可写、出错等,而不需要为每个套接字创建一个独立的线程。这使得在一个单独的线程中能够有效地管理多个连接。 -
事件驱动:
select
是事件驱动的,它会阻塞等待套接字上的事件发生,例如数据可读或可写。一旦有事件发生,它会通知你,你可以采取适当的操作响应这些事件。 -
跨平台: 尽管
select
最初是在 Unix 系统中引入的,但它也在 Windows 中提供支持。
实现原理:
- 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数组结构中,即数组,通过select函数遍历数组中的socket数组,当某个socket有响应时,select就会通过其参数/返回值反馈出来
- 如果检测到的是服务器socket,那么就是有客户端链接。
- 如果检测到的是客户端socket,那么就是客户端请求通信。
二、select函数
函数原型:
1 2 3 4 5 6 7 | int select( int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timeval* timeout ); |
参数介绍:
-
nfds
:表示最大套接字描述符(socket descriptor)加1的值。在 Windows 中,这个参数通常设置为要监视的最大套接字描述符的值加1。 -
readfds
:一个指向fd_set
结构体的指针,用于指定要监视可读事件的套接字集合。你可以使用FD_ZERO
和FD_SET
宏来清除和设置套接字集合中的套接字。初始化为所有的socket,通过select投放给系统,系统将有事件发生的socket再赋值回来,调用后,这个参数就只剩下有请求的socket。 -
writefds
:一个指向fd_set
结构体的指针,用于指定要监视可写事件的套接字集合。检查是否有可写的socket,就是可以给哪些客户端套接字发消息,只要链接成功建立了,那么该客户端套接字就是可写的 -
exceptfds
:一个指向fd_set
结构体的指针,用于指定要监视异常事件的套接字集合。 -
timeout
:一个指向struct timeval
结构体的指针,用于设置超时时间,以毫秒为单位。如果设置为NULL
,select
将会一直阻塞,直到有套接字就绪或出现错误。如果设置为0
,select
将立即返回,用于轮询套接字状态。
返回值:
- 如果
select
函数成功,它将返回可读、可写或异常事件的套接字数量,即就绪的套接字数量。 - 如果发生错误,
select
返回SOCKET_ERROR
,你可以使用WSAGetLastError
函数获取详细的错误码。
三、Server模型的源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | #include <WinSock2.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") int main() { WSADATA wsaData; // 创建一个 WSADATA 结构 // 初始化 Winsock 库,指定要使用的版本 int ret = WSAStartup(MAKEWORD(2, 2), &wsaData); if (ret != 0) { printf ( "WSAStartup 失败,错误码: %d\n" , ret); return 0; } //校验版本 if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) { printf ( "版本不符合" ); WSACleanup(); return 0; } // 在这里进行网络编程操作 SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socketServer == INVALID_SOCKET) { int errorCode = WSAGetLastError(); printf ( "socket创建失败,错误码:%u\n" , errorCode); closesocket(socketServer); WSACleanup(); } sockaddr_in si; si.sin_family = AF_INET; si.sin_addr.s_addr = inet_addr( "127.0.0.1" ); //si.sin_addr.s_addr = INADDR_ANY; // 使用0.0.0.0监听所有可用端口 si.sin_port = htons(1234); ret = bind(socketServer, (SOCKADDR*)&si, sizeof (si)); if (ret == SOCKET_ERROR) { printf ( "bind绑定失败,错误码:%u\n" , WSAGetLastError()); closesocket(socketServer); WSACleanup(); return 0; } ret = listen(socketServer, SOMAXCONN); if (ret == SOCKET_ERROR) { printf ( "listen监听失败,错误码:%u\n" , WSAGetLastError()); closesocket(socketServer); WSACleanup(); return 0; } fd_set allSockets; //清零 FD_ZERO(&allSockets); FD_SET(socketServer, &allSockets); //添加一个元素 //FD_SET(socketServer, &allSockets); //删除一个元素 //FD_CLR(socketServer, &allSockets); //判断socket是否在集合中,不在返回0,在则返回非0 //FD_ISSET(socketServer, &allSockets); timeval st; st.tv_sec = 3; //3秒 st.tv_usec = 0; while ( true ) { fd_set readSockets = allSockets; fd_set writeSockets = allSockets; FD_CLR(socketServer, &writeSockets); fd_set errorSocket = allSockets; ret = select(0, &readSockets, &writeSockets, &errorSocket, &st); if (ret == 0) { //没有可链接的socket Sleep(2000); continue ; } else if (ret > 0) { //处理错误 for (u_int i = 0; i < errorSocket.fd_count; i++) { char error[100] = { 0 }; int len = sizeof (error); ret = getsockopt(errorSocket.fd_array[i], SOL_SOCKET, SO_ERROR, error, &len); if (ret == SOCKET_ERROR) { printf ( "无法得到错误信息\n" ); } else { printf ( "%s\n" , error); } } //只要有客户端链接成功,那么writeSockets集合中就一直会有可用的客户端socket //writeSockets集合中的客户端socket和readSockets集合中的客户端socket是等价的,都可以用来向客户端发送数据 for (u_int i = 0; i < writeSockets.fd_count; i++) { Sleep(5000); printf ( "服务器%d,%d:可写\n" , socketServer, writeSockets.fd_array[i]); //向客户端发送消息 const char send_buff[] = "hello, I'm is writeSockets" ; ret = send(writeSockets.fd_array[i], send_buff, strlen (send_buff), 0); if (ret == SOCKET_ERROR) { printf ( "send失败,错误码:%u\n" , WSAGetLastError()); } } //有socket for (u_int i = 0; i < readSockets.fd_count; i++) { if (readSockets.fd_array[i] == socketServer) { //accept SOCKET socketClient = accept(socketServer, NULL, NULL); if (socketClient == SOCKET_ERROR) { continue ; } printf ( "客户端上线\n" ); FD_SET(socketClient, &allSockets); } else { //客户端 char buffer[1240] = { 0 }; int iResult = recv(readSockets.fd_array[i], buffer, sizeof (buffer), 0); if (iResult == 0) { printf ( "客户端下线\n" ); //从集合中删除 SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //释放 closesocket(socketTemp); } else if (iResult > 0) { //接收到了客户端信息 printf ( "%s\n" , buffer); //向客户端发送消息 const char send_buff[] = "hello, I'm is server" ; ret = send(readSockets.fd_array[i], send_buff, strlen (send_buff), 0); if (ret == SOCKET_ERROR) { printf ( "send失败,错误码:%u\n" , WSAGetLastError()); } } else { //SOCK_ERROR //强制下线也叫出错:10054 printf ( "erroCode:%d" , WSAGetLastError()); if (10054 == WSAGetLastError()) { //从集合中删除 SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //释放 closesocket(socketTemp); } } } } } else { //SOCK_ERROR printf ( "select失败,错误码:%u\n" , WSAGetLastError()); } } //释放所有socket for (u_int i = 0; i < allSockets.fd_count; i++) { closesocket(allSockets.fd_array[i]); } closesocket(socketServer); WSACleanup(); return 1; } |
四、Client模型源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | #include <WinSock2.h> #include <stdio.h> #pragma comment(lib, "ws2_32.lib") int main() { WSADATA wsaData; // 创建一个 WSADATA 结构 // 初始化 Winsock 库,指定要使用的版本 int ret = WSAStartup(MAKEWORD(2, 2), &wsaData); if (ret != 0) { printf ( "WSAStartup 失败,错误码: %d\n" , ret); return 0; } //校验版本 if (HIBYTE(wsaData.wVersion) != 2 || LOBYTE(wsaData.wVersion) != 2) { printf ( "版本不符合" ); WSACleanup(); return 0; } // 在这里进行网络编程操作 SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socketServer == INVALID_SOCKET) { int errorCode = WSAGetLastError(); printf ( "socket创建失败,错误码:%u\n" , errorCode); closesocket(socketServer); WSACleanup(); } //链接服务器 sockaddr_in si; si.sin_family = AF_INET; si.sin_addr.s_addr = inet_addr( "127.0.0.1" ); si.sin_port = htons(1234); ret = connect(socketServer, (SOCKADDR*)&si, sizeof (si)); if (ret == SOCKET_ERROR) { printf ( "connect失败,错误码:%u\n" , WSAGetLastError()); closesocket(socketServer); WSACleanup(); return 0; } int nCount = 0; while ( true ) { if (nCount >= 5) { break ; } const char send_buff[] = "hello, I'm is client" ; ret = send(socketServer, send_buff, sizeof (send_buff), 0); if (ret == SOCKET_ERROR) { printf ( "send失败,错误码:%u\n" , WSAGetLastError()); } Sleep(1000); char buffer[1024] = { 0 }; ret = recv(socketServer, buffer, sizeof (buffer), 0); if (ret == 0) { printf ( "客户端连接中断\n" ); } else if (ret == SOCKET_ERROR) { printf ( "recv失败,错误码:%u\n" , WSAGetLastError()); } else { printf ( "recv_len:%d,recv_data:%s\n" , ret, buffer); } nCount++; } closesocket(socketServer); WSACleanup(); return 1; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?