select模型
特点
解决基本c/s模型中,accept,rcev傻等的问题。
- 傻等阻塞
- 执行阻塞 send recv accept 在执行的复制粘贴的过程中都是阻塞的。
(网络模型就是解决阻塞问题的)
实现多个客户端链接,与多个客户端分别通信。
用于服务器,因为客户端就一个socket。
服务器端
1 2 3 4 5 6 7 8
| 网络头文件 网络库 打开网络库 校验版本 创建socket 绑定地址与端口 开始监听 select
|
逻辑
- 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数据结构里,即数组。
- 通过select函数,遍历1中的socket数组,当某个socket有相应,select就会通过其参数/返回值反馈出来。
- 处理。如果见得到的是服务器socket,那就有客户端链接,调用accept。如果检测到客户端socket,那就是客户端请求通信,调用send或者recv。
定义一个装客户端的socket结构体
fd_set
是网络库中定义好的类型。
1 2 3 4 5 6 7 8 9 10 11 12
| typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; } fd_set;
默认FD_SERSIZE 是64,重新宏定义要写在网络库前。 尽量不要太大,大用户量应该用更高级的网络模型。 select模型应用就是小用户量访问,几十几百,简单方便。 fd_set socketClient;
|
四个参数宏
1 2 3 4 5
| FD_ZERO #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0) 将定义好的集合清零 FD_ZERO(&socketClient);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| FD_SET #define FD_SET(fd, set) do { \ u_int __i; \ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count; __i++) { \ if (((fd_set FAR *)(set))->fd_array[__i] == (fd)) { \ break; \ } \ } \ if (__i == ((fd_set FAR *)(set))->fd_count) { \ if (((fd_set FAR *)(set))->fd_count < FD_SETSIZE) { \ ((fd_set FAR *)(set))->fd_array[__i] = (fd); \ ((fd_set FAR *)(set))->fd_count++; \ } \ } \ } while(0, 0) 向集合中添加socket
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| FD_CLR #define FD_CLR(fd, set) do { \ u_int __i; \ for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \ if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \ while (__i < ((fd_set FAR *)(set))->fd_count-1) { \ ((fd_set FAR *)(set))->fd_array[__i] = \ ((fd_set FAR *)(set))->fd_array[__i+1]; \ __i++; \ } \ ((fd_set FAR *)(set))->fd_count--; \ break; \ } \ } \ } while(0, 0) 从集合中删除某个元素,要手动释放,closesocket(socketServer) 同链表删除。
|
1 2 3 4 5
| FD_ISSET #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set)) 判断集合中是否有某个元素 有-返回非0 没有-返回0
|
select
1 2 3 4 5 6
| int WSAAPI select( int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, );
|
作用
监视socket集合,如果某个socket发生事件,(链接或者收发数据),通过返回值以及参数告诉我们。
参数1
Ignored忽略,填0,仅为了兼容(向下兼容性)Berkeley sockets。
参数2
检查是否有可读的scoket。(是否有消息recv/accept/)
即客户端发来消息了,该socket就会被设置。
初始化所有的socket,通过select投放给系统,系统将有事件发生的socket再复制回来,调用后,这个参数就只剩下有请求的socket。
返回有响应的socket。用个中间变量接收。
参数3
检查是否有可写的socket。
从头到尾遍历出来。
即,使可以给哪些客户端套接字发消息,即send,只要链接成功建立起来了,该客户端套接字就是可写的。
初始化所有的socket,通过select投放给系统,系统将可以写的socket在复制回来,调用后,这个参数就是装着可以被send数据的客户端socket。
参数4
检查套接字上的异常错误,用法同参数23。将所有的socket投放进去。
得到异常套接字上的具体错误码。
getsockopt(socket,SOL_SOCKET,SO_ERROR,buf,buflen);
参数5
最大等待时间,比如当客户端没有请求时,那么select函数可以等一会儿,一段时间过后,还没有,就继续执行select下面的语句,如果有了,就立刻执行下面的语句。
1 2 3 4 5 6 7
| TIMEVAL tv_sec 秒 tv_usec 微秒 0 0非阻塞状态,立刻返回 3 4那就再无客户端相应的情况下等待3秒4微秒 NULL select完全阻塞,知道客户端有反应,我才继续
|
返回值
1
| 0 客户端在等待时间内没有反应 处理——continue>0 有客户端请求交流了SOCKET_ERROR 发生了错误 得到错误码WSAGetLaseError()
|
流程总结
1 2 3 4 5 6 7 8 9
| socket集合 socket判断有没有相应的 返回0,没有,继续挑 返回>0,有相应 可读的accept recv 可写的send 异常的getsockopt SOCK_ERROR
|
select是阻塞的。
不等待——执行阻塞
半等待——执行阻塞+软阻塞
全等待——执行阻塞+硬阻塞 死等
完整代码
(仅熟悉流程)
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 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212
| #define _WINSOCK_DEPRECATED_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> #include<string.h> #include<WinSock2.h> #pragma comment(lib,"Ws2_32.lib")
fd_set allSocket;
BOOL WINAPI fun(DWORD dwCtrlType) { switch (dwCtrlType) {
case CTRL_CLOSE_EVENT: for (u_int i = 0; i < allSocket.fd_count; i++) { closesocket(allSocket.fd_array[i]); } WSACleanup(); } return TRUE; }
int main(void) { SetConsoleCtrlHandler(fun, TRUE);
WORD wdVersion = MAKEWORD(2, 2); WSADATA wdSockMsg; int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0) { printf("网络库打开失败"); return 0; }
if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2) { printf("版本不对"); WSACleanup(); return 0; }
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (socketServer == INVALID_SOCKET) { printf("创建服务器socket失败"); int a = WSAGetLastError(); WSACleanup(); return 0; } SOCKADDR_IN si; si.sin_family = AF_INET; si.sin_port = htons(12345); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si))) { printf("绑定错误"); int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); return 0; }
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { int a = WSAGetLastError(); closesocket(socketServer); WSACleanup(); return 0; }
FD_ZERO(&allSocket); FD_SET(socketServer, &allSocket); while (1) { fd_set readSocket = allSocket; fd_set writeSocket = allSocket; FD_CLR(socketServer, &writeSocket); fd_set errorSocket = allSocket;
struct timeval st; st.tv_sec = 3; st.tv_usec = 0; int nRes = select(0, &readSocket, &writeSocket, &errorSocket, &st); if (nRes == 0) { continue; } else if (nRes > 0) {
for (u_int i = 0; i < errorSocket.fd_count; i++) { char str[100] = { 0 }; int len = 99; if (SOCKET_ERROR == getsockopt(errorSocket.fd_array[i], SOL_SOCKET, SO_ERROR,str,&len)) { printf("无法得到错误信息\n"); } printf("%s\n", str); }
for(u_int i = 0;i<writeSocket.fd_count;i++) { if (SOCKET_ERROR == send(writeSocket.fd_array[i], "ok", 2, 0)) { int a = WSAGetLastError(); } }
for (u_int i = 0; i < readSocket.fd_count; i++) { if (readSocket.fd_array[i] == socketServer) { SOCKET socketClient = accept(socketServer, NULL, NULL); if (socketClient == SOCKET_ERROR) { continue; } FD_SET(socketClient, &allSocket); send(readSocket.fd_array[i], "服务器链接成功!", sizeof("服务器链接成功"),0); } else { char strBuf[1500] = { 0 }; int nRecv = recv(readSocket.fd_array[i], strBuf, 1500, 0); if (nRecv == 0) { SOCKET socketTemp = readSocket.fd_array[i]; FD_CLR(readSocket.fd_array[i],&allSocket); closesocket(socketTemp);
} else if(nRecv > 0) { printf(strBuf); } else { int a = WSAGetLastError(); switch (a) { case 10054: { SOCKET socketTemp = readSocket.fd_array[i]; FD_CLR(readSocket.fd_array[i], &allSocket); closesocket(socketTemp); } } printf("%d", a); } } } } else { break; } }
for (u_int i = 0; i < allSocket.fd_count; i++) { closesocket(allSocket.fd_array[i]); } WSACleanup(); system("pause"); return 0; }
|