服务器开发基础之select模型

服务器开发基础—select模型

特点

  • 解决基本C/S模型中,accept和recv等持续等待的问题,不解决函数本身执行带来的阻塞
  • 实现多个客户端链接,与多个客户端分别通信
  • 用于服务器,客户端就不用这个了,因为只存在一个socket

代码逻辑

  • 打开网络库
  • 校验版本
  • 创建socket
  • 绑定地址与端口
  • 开始监听
  • select

select的逻辑

  • 每个客户端都有socket,服务器也有自己的socket,将所有的socket装进一个数据结构里,即数组
  • 通过select函数,遍历socket数组,当某个socket没有响应,select就会通过其参数/返回值反馈出来
  • 程序员可以进行相应处理,如果检测的是服务器socket就调用accept,如果检测的是客户端socket就调用send或者recv

fd_set结构的意义和使用

1
2
3
4
5
#define FD_SETSIZE      64
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;

select模型默认处理64个socket,一个服务端,六十三个客户端

select模型应用,就是小用户量访问量,几十几百,简单方便

四个操作fd_set的参数宏

  • FD_ZERO:将集合清0
1
#define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
  • FD_SET:向集合中添加一个socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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)
  • FD_CLR:集合中删除指定socket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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)
  • FD_ISSET:判断一个socket是否在集合中
1
#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))

select函数

1
2
3
4
5
6
7
8
9
int
WSAAPI
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
);

作用

监视socket集合,如果某个socket发生事件(链接或者收发数据),通过返回值以及参数告诉我们

参数

  • 参数一:忽略,填0,这个参数仅仅是为了兼容Berkeley sockets
  • 参数二:检查是否有刻度的socket,即客户端发来消息了,该socket就会被设置
  • 参数三:检查是否有可写的socket,就是可以给哪些客户端套接字发消息,即send,只要链接成功建立起来了,那该客户端套接字就是可写的
  • 参数四:检查套接字上的一场错误,将有异常的错误的套接字重新装进来,反馈给程序员
  • 参数五:最大等待时间,比如当客户端没有请求时,那么select函数可以等一会,一段时间后,就继续执行select函数下面的语句

返回值

  • 0:客户端在等待时间内没有反应,直接continue就行
  • 大于0:有客户端请求交流
  • SOCKET_ERROR:发生了错误

参数二代码实例

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
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 == INVALID_SOCKET) {
//链接出错
continue;
}
FD_SET(socketClient, &allSockets);
}
else{
//客户端
char strBuf[1500] = { 0 };
int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
{
if (nRecv == 0) {
//客户端下线
//从集合中拿掉
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp);
}
else if (nRecv > 0) {
//接收到了消息
printf(strBuf);
}
else
{
int a = WSAGetLastError();

switch (a)
{
case 10054: {
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp); }
}
}
}
}
}

参数三代码实例

1
2
3
4
5
6
for (u_int i = 0; i < writeSockets.fd_count; i++)
{
if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", strlen("ok"), 0)) {
int a = WSAGetLastError();
}
}

参数四代码实例

1
2
3
4
5
6
7
8
9
10
//处理错误
for (u_int i = 0; i < errorSockets.fd_count; i++)
{
char str[100] = { 0 };
int len = 99;
if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, len)) {
printf("无法得到错误信息\\n");
}
printf("%s\\n", str);
}

select模型总结

代码结构

select模型的代码,也会有细节优化,结构稍有变化,但是原理都一样

结构核心

  • 参数2:处理accept与recv傻等的情况,也是select结构对比基本模型最大的作用
  • 参数3:send随时都能发,并不一定由参数3决定send

流程总结

socket集合,select判断集合有没有响应

  • 返回0,没有响应继续循环
  • 返回>0,由响应,分为可读,可写,异常
  • SOCKET_ERROR

select是阻塞的

  • 不等待,执行阻塞
  • 半等待,执行阻塞+软阻塞
  • 全等待:执行阻塞+硬阻塞

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
#define FD_SETSIZE 128
#include <WinSock2.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")
#define _WINSOCK_DEPRECATED_NO_WARNINGS

fd_set allSockets;

BOOL WINAPI fun(DWORD dwCtrType) {
switch(dwCtrType){
case CTRL_CLOSE_EVENT:
//释放所有socket
for (u_int i = 0; i < allSockets.fd_count; i++) {
closesocket(allSockets.fd_array[i]);
}
//清理网络库
WSACleanup();
}

return TRUE;
}

int main(void) {

SetConsoleCtrlHandler(fun, TRUE);

WORD wdVersion = MAKEWORD(2, 2);
WSADATA wdSockMsg;
//LPWSADATA lpw = malloc(sizeof(WSADATA));//WSADATA*
int nRes = WSAStartup(wdVersion, &wdSockMsg);
if (nRes != 0) {
switch (nRes) {
case WSASYSNOTREADY:
printf("系统配置问题,重启电脑,检查ws2_32库是否存在,或者是否在环境配置目录下");
break;
case WSAVERNOTSUPPORTED:
printf("要使用的版本不支持,请更新网络库");
break;
case WSAEINPROGRESS:
printf("Windows Sockets实现可能限制同时使用它的应用程序的数量");
break;
case WSAEPROCLIM:
printf("当前函数运行期间,由于某些原因造成阻塞,会返回这个操作码,其他操作均禁止");
break;
case WSAEFAULT:
printf("参数写错了");
break;
}
return 0;
}
if (HIBYTE(wdSockMsg.wVersion) != 2 || LOBYTE(wdSockMsg.wVersion) != 2) {
//说明版本不对
//清理网络库
WSACleanup();
return 0;
}

SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == socketServer)
{
//清理网络库
WSACleanup();
return 0;
}
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);
si.sin_addr.s_addr = htonl(2130706433);
int bres = bind(socketServer, (const struct sockaddr*)&si, sizeof(si));
if (SOCKET_ERROR == bres) {
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;

}
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) {
int a = WSAGetLastError();
//释放
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}

//清零
FD_ZERO(&allSockets);
//添加一个元素
FD_SET(socketServer, &allSockets);
while (1) {
fd_set readSockets = allSockets;
fd_set writeSockets = allSockets;
fd_set errorSockets = allSockets;
//间隔时间
struct timeval st;
st.tv_sec = 3;
st.tv_usec = 0;

int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st);

if (nRes == 0) {
//没有响应
continue;
}
else if (nRes > 0) {
//处理错误
for (u_int i = 0; i < errorSockets.fd_count; i++)
{
char str[100] = { 0 };
int len = 99;
if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len)) {
printf("无法得到错误信息\n");
}
printf("%s\n", str);
}


for (u_int i = 0; i < writeSockets.fd_count; i++)
{
if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", strlen("ok"), 0)) {
int a = WSAGetLastError();
}
}
//有响应
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 == INVALID_SOCKET) {
//链接出错
continue;
}
FD_SET(socketClient, &allSockets);
}
else{
//客户端
char strBuf[1500] = { 0 };
int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
{
if (nRecv == 0) {
//客户端下线
//从集合中拿掉
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp);
}
else if (nRecv > 0) {
//接收到了消息
printf(strBuf);
}
else
{
int a = WSAGetLastError();

switch (a)
{
case 10054: {
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp); }
}
}
}
}
}
}
else {
//发生错误
break;
}
}

//释放所有socket
for (u_int i = 0; i < allSockets.fd_count; i++) {
closesocket(allSockets.fd_array[i]);
}
//清理网络库
WSACleanup();
system("pause");
return 0;

}