关于select模式的一些理解

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,算法,计算机网络的相关内容我都会发,各位大佬请批评指正,谢谢观看,有不懂的地方或者错误的地方评论区或者私信交流,谢谢。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值