select模式是在Windows下创建服务端一种常见的模型,因为Windows并没有提供像Linux中epoll类似的机制,select模式就是初学者最常用的模式之一。
select模式的核心就是select函数,函数定义为:
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
);
#endif /* INCL_WINSOCK_API_PROTOTYPES */
我们可以看出select函数的返回值为int类型,具体来说一般有-1,0,大于0三种,
- 如果返回值大于 0,表示有文件描述符就绪,返回值是就绪文件描述符的数量。
- 如果返回值等于 0,表示超时,没有文件描述符就绪。
- 如果返回值等于 -1,表示出错,可以通过 error 来获取具体的错误信息。
那么文件描述符的数量具体指的是什么呢?我们接着往下看,第一个参数nfds指的是fd_set集合中所有描述符(SOCKET)的范围,在Windows操作系统中我们一般直接填sock+1(其实在Windows中并不重要,Windows会自动适配范围),第二三四个参数都是fd_set类型的,分别是是否有可读可写或者其他需要处理的事件发生,就拿第二个是否可读来说明一下,毕竟第三个和第四个都一个样。
在讲这个之前,我们先来明确一下fd_set这个结构体
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
有两个参数,一个表示set的大小,另一个表示一个socket数组,具体表示的就是一个通配符合集。
已经理解了fd_set,理解readfds就不难了,说直白点就是表示可读通配符的合集,在下文中我们会提到,就是用来判断从目前连接的客户端所接受的套接字是否有可读内容。
前四个参数已经解决,第五个参数就比较简单了,我们先来看一下第五个参数的定义
_In_opt_ const struct timeval FAR * timeout
是timeval类型,用来设置处理客户端请求的时间间隔的,话句话说,就是设置服务端接收客户端请求时是否阻塞,如果是非阻塞时间限制为多少,我们再来看一下timeval的定义
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
很显然有两个参数一个是秒,一个是毫秒,一般情况下第二个参数设置为0,因为Windows自带的计时器不支持毫秒计时。
既然已经理解完select函数,我们直接上代码
//伯克利 套接字
fd_set fdread;
fd_set fdwrite;
fd_set fdecp;
FD_ZERO(&fdread);//相关函数,将fd_set.count设置为0
FD_ZERO(&fdwrite);
FD_ZERO(&fdecp);
FD_SET(_sock, &fdread);//将套接字_sock传给fd_set
FD_SET(_sock, &fdwrite);
FD_SET(_sock ,&fdecp);
for (int i = (int)g_client.size()-1; i >=0; i--) {
FD_SET(g_client[i], &fdread);//将客户端请求依次放入可读事件集合
}
//nfds指的是fd_set集合中所有描述符(SOCKET)的范围
timeval t = { 0,0 };
//第五个参数 非阻塞 为NULL则为阻塞
int ret=select(_sock + 1, &fdread, &fdwrite, &fdecp, &t);
if (ret < 0) {
printf("出现错误,没有进行任何操作\n");
break;
}
if (FD_ISSET(_sock, &fdread)) {
FD_CLR(_sock,&fdread);
sockaddr_in clients = { };
int clilength = sizeof(sockaddr_in);
SOCKET csock = INVALID_SOCKET;
csock = accept(_sock, (sockaddr*)&clients, &clilength);
if (INVALID_SOCKET == csock) {
printf("error\n");
}
g_client.push_back(csock);
printf("新客户端加入:socket=%d, IP= %s \n", (int)csock, inet_ntoa(clients.sin_addr));
}
for (size_t i = 0; i < fdread.fd_count; i++) {
if (-1 == proc(fdread.fd_array[i])) {
auto iter = find(g_client.begin(), g_client.end(), fdread.fd_array[i]);
if (iter != g_client.end()) {
g_client.erase(iter);
}
}
}//确保在客户端退出后,及时清理相关资源,避免出现资源泄漏的情况
代码中各个需要注意的点已经注释明确,我们就不再多做赘述。
觉得有用的xdm可以点点关注,今后c++高性能服务器开发,unity,算法,计算机网络的相关内容我都会发,各位大佬请批评指正,谢谢观看,有不懂的地方或者错误的地方评论区或者私信交流,谢谢。