网络编程——select模型(总结)
原创
©著作权归作者所有:来自51CTO博客作者寰宇CC的原创作品,请联系作者获取转载授权,否则将追究法律责任
为什么要使用select模型?
答:解决基本C/S模型中,accept()、recv()、send()阻塞的问题
select模型与C/S模型的不同点
- C/S模型中accept()会阻塞一直傻等socket来链接
- select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题
其实select模型解决了
实现多个客户端链接,与多个客户端分别通信
两个模型都存在recv(),send()执行阻塞问题
- 由于服务器端,客户端不需要(客户端只有一个socket,可以通过加线程解决同时recv和send)
select模型逻辑
- 将所有的socket(服务器端+客户端)装进一个数组中
- 通过select()遍历socket数组
- 取出有相应的socket放进另一个数组(都是有响应的socket)
- 对装有响应的socket数组集中处理
- 服务器socket响应:客户端链接,调用accept()
- 客户端socket响应:客户端通信,调用send()或recv()
select()
fd_set
作用:定义一个用来装socket的结构体
#ifndef FD_SETSIZE
#define FD_SETSIZE 64 /*默认64个*/
#endif /* FD_SETSIZE */
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
默认装socket大小为64,可以通过在winsock2.h头文件前声明宏,给一个更大的值
#define FD_SETSIZE 128
#include <WinSock2.h>
因为原理就是不停遍历检测,越多效率越低,延迟越大,所以合适大小最好。
select模型应用,就是小用户量访问。
- 四个操作fd_set的操作宏
select()函数
作用:监视socket集合,如果某个socket发生响应(链接或者收发数据),通过返回值以及参数告诉我们哪个socket有响应
int WSAAPI select(
int nfds, /*填0*/
fd_set *readfds, /*检查是否有可读的socket*/
fd_set *writefds, /*检查是否有可写的socket*/
fd_set *exceptfds, /*检查socket上的异常错误*/
const timeval *timeout
);
select模型代码
fd_set allsockets;
//清零
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;
//select
int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st);
if (0 == nRes) //没有响应的socket
{
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++)
{
//printf("服务器%d,%d:可写\n", socketServer, writeSockets.fd_array[i]);
if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 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 (INVALID_SOCKET == socketClient)
{
//链接出错
continue;
}
FD_SET(socketClient, &allSockets);
//send
}
else
{
char strBuf[1500] = { 0 };
//客户端吧
int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0);
//send
if (0 == nRecv)
{
//客户端下线了
//从集合中拿掉
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp);
}
else if (0 < nRecv)
{
//接收到了消息
printf(strBuf);
}
else //SOCK_ERROR
{
//强制下线也叫出错 10054
int a = WSAGetLastError();
switch (a)
{
case 10054:
{
SOCKET socketTemp = readSockets.fd_array[i];
FD_CLR(readSockets.fd_array[i], &allSockets);
//释放
closesocket(socketTemp);
}
}
}
}
}
}
总结
select模型
将一组socket数组投递给系统,然后在系统里去查询socket是否有信号,过程都是在select函数里面去进行的,再到返回有操作的socket集合
select()函数本质
select()函数执行遍历和返回有响应的socket,整个过程中也是阻塞的。
等待时间
| 阻塞
|
不等待
| 执行阻塞
|
半等待
| 执行阻塞+软阻塞
|
全等待
| 执行阻塞+硬阻塞
|
与CS模型对比
使用CS模型时,当链接了一个客户端,执行完了recv,while循环又回到了accept(),傻等着客户端来链接,无法多客户端链接通信。
使用select模型时,是select在遍历着socket数组,有响应的socket再取出来,没有就一直遍历,虽然select()函数的执行也是阻塞的。可以理解为,每次都是在处理只有响应的socket,所以可以进行多客户端链接通信。
当第一个客户端socket来链接时,select()函数将服务端socket从allsocket取出来,将新建的含有客户端socket添加到allsockets数组中,接着又在遍历allsocket,查看着时候有响应,所以不会像CS模型那种,在accept()函数阻塞着,傻等着。
select()函数主要解决的是accept()函数阻塞问题,而没有解决recv()和send()函数阻塞问题