浅谈使用select函数实现IO多路复用

        一个服务器在处理多个客户端连接的情况时,如果为每个客户端创建一个线程来进行连接,显然是不太合适的。比较合适的方法就是使用IO多路复用,本文主要介绍使用select函数方式实现的IO多路复用,poll,epoll等方式后续文章介绍。所谓的IO多路复用模型即为:一个线程,通过记录I/O流的状态来同时管理多个I/O。

       select()函数可以监视多个文件描述符的状态,当所监视的文件描述由阻塞变为非阻塞状态时给出相应的返回值,接下来可以进行读/写/异常操作。

        使用select()函数对文件描述符进行监视时,需要用到FD系列函数:

void FD_CLR(int fd, fd_set *set);   // 清除某一个被监视的文件描述符。
int  FD_ISSET(int fd, fd_set *set); // 测试一个文件描述符是否是集合中的一员
void FD_SET(int fd, fd_set *set);   // 添加一个文件描述符,将set中的某一位设置成1;
void FD_ZERO(fd_set *set);          // 清空集合中的文件描述符,将每一位都设置为0;

        假设此时有两个客户端socket1,socket2同时连接服务器,select()函数使用方法如下:

        1、调用FD_ZERO清空集合fReadds中的文件描述符;

        2、调用DF_SET将文件描述符socket1和socket2添加到集合fReadds中;

        3、调用select()函数,第一个参数设置为socket1和socket2的最大值加1;

        4、如果select()函数返回值大于0,调用FD_ISST判断所监视的文件描述符socket1和socket2是否有数据可读。

        原理说明:

        1、假设取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
​         2、执行FD_ZERO(&set);则set用位表示是0000,0000。
​         3、若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
​         4、若再加入fd=2,fd=1,则set变为0001,0011
​         5、执行select(6,&set,0,0,0),最后一个参数为0,所以阻塞等待
​         6、若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。

        根据原理说明可以看出,每次FD_SET和FD_ISSET都是需要根据socket数量来进行循环执行的。也就是需要注意以下几点:

        1、需要通过循环把所有的socket放入集合fReadds;

        2、select之后,再通过循环对所有的socket进行判断是否有数据传输;

        3、所有的socket判断完毕,数据处理完成之后。需要重新从FD_ZERO开始再次执行。

        示例代码:

list<int> listSocket;

fd_set fReadds;
struct timeval timeout;

timeout.tv_sec = 0;
timeout.tv_usec = 200;

listSocket.push_back(socket1);
listSocket.push_back(socket2);

while (1)
{
    // 清空集合
    FD_ZERO(&fReadds);
	int nMaxSocked = 0;
    for (list<int>::iterator itr = listSocket.begin(); itr != listSocket.end(); itr++)
	{
        // 将文件描述符添加到集合
        FD_SET(*itr, &fReadds);
	    if (nMaxSocked < itr->nSocket)
		{
			nMaxSocked = itr->nSocket;
		}
    }

    // 第一个参数为socket的最大值加1,这样才能监视所有的socket
    // 最后一个参数不能为0,否则阻塞模式会导致后续连接不上
    int ret = select(nMaxSocked+1, &fReadds, NULL, NULL, &timeout);
	if (ret == SOCKET_ERROR)
    {
        printf("select error\n");
        continue;
    }
    else if (ret == 0)
    {
        continue;
    }
    else
    {
        for (list<int>::iterator itr = listSocket.begin(); itr != listSocket.end(); itr++)
	    {
            // 判断所有监视的socket是否有数据传输
            if (FD_ISSET(*itr, &fReadds))
            {
                // 接收数据,进行处理
            }
        }
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
`select` 函数是一种 I/O 多路复用的机制,用于同时监听多个文件描述符的状态变化。它可以使用单个系统调用同时监视多个文件描述符,并在有一个或多个文件描述符就绪时通知应用程序。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:待监视的最大文件描述符加 1。 - `readfds`:可读文件描述符集合。 - `writefds`:可写文件描述符集合。 - `exceptfds`:异常条件文件描述符集合。 - `timeout`:超时时间,如果为 `NULL` 则为阻塞模式,即一直等待直到有文件描述符就绪;如果为零时间(`tv_sec` 和 `tv_usec` 均为 0),则为非阻塞模式,即立即返回;否则为指定超时时间。 `select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则表示超时;如果返回值为 -1,则表示出错。 使用 `select` 函数的一般流程如下: 1. 初始化需要监视的文件描述符集合。 2. 调用 `select` 函数等待文件描述符就绪。 3. 检查返回值确定哪些文件描述符已经就绪。 4. 处理就绪的文件描述符。 5. 重复上述步骤。 需要注意的是,`select` 函数有一些限制,比如只能监视的文件描述符数量有限,一般为 1024 或更小。此外,在某些平台上,使用 `select` 函数可能会有性能上的限制,可以考虑使用更高效的机制,如 `poll` 或 `epoll`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

古道青阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值