I/O复用(一)-select (待完善)

本文详细介绍了select系统调用的工作原理,包括其参数含义、fd_set结构体、FD_SETSIZE限制以及在服务器开发中的基本使用流程。同时,文章指出在使用select时需要注意的两个关键点:readfds等集合的更新以及超时时间timeout的处理。
摘要由CSDN通过智能技术生成

代码和知识有引用自 高性能服务器开发

select系统调用:在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写、异常事件

select 函数API如下

int select(int nfds, fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数含义:
nfds 被监听的文件描述符总数,通常被设置为select监听的所有文件描述符中的最大值加1(因为文件描述符是从0开始的)
readfds、writefds、exceptfds 对应需要监听的可读、可写、异常事件的fd集合
timeout select函数超时时间,不设置的话select函数会一直阻塞等待事件发生

其中readfds、writefds、exceptfds都为同个结构体fd_set,定义在/usr/include/sys/select.h中

/usr/include/bits/typesizes.h
#define __FD_SETSIZE            1024

/* The fd_set member is required to be an array of longs.  */
typedef long int __fd_mask;

/* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
#define __NFDBITS       (8 * (int) sizeof (__fd_mask))
#define __FD_ELT(d)     ((d) / __NFDBITS)
#define __FD_MASK(d)    ((__fd_mask) 1 << ((d) % __NFDBITS))

/* fd_set for select and pselect.  */
typedef struct
  {
    /* XPG4.2 requires this member name.  Otherwise avoid the name
       from the global namespace.  */
#ifdef __USE_XOPEN
    __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else
    __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif
  } fd_set;

/* Maximum number of file descriptors in `fd_set'.  */
#define FD_SETSIZE              __FD_SETSIZE

由此可见,fd_set就只有一个整型数组,该数组的每一位(bit)标记一个文件描述符。fd_set可以容纳的文件描述符数量由FD_SETSIZE决定。因此select在同时处理文件描述符的总量是受到限制的,在/usr/include/sys/select.h中还定义了一系列的宏来处理fd_set结构体的位:

#include <sys/select.h>
FD_ZERO(fd_set * fdset); 			//清除fdset的所有位
FD_SET(int fd,fd_set *fdset);		//设置fdset的位fd
FD_CLR(int fd,fd_set *fdset);    	//清除fdset的位fd
int FD_ISSET(int fd,fd_set * fdset) //验证fdset的位fd是否被设置
    

/* We don't use `memset' because this would require a prototype and
   the array isn't too big.  */
# define __FD_ZERO(set)  \
  do {                                                                        \
    unsigned int __i;                                                         \
    fd_set *__arr = (set);                                                    \
    for (__i = 0; __i < sizeof (fd_set) / sizeof (__fd_mask); ++__i)          \
      __FDS_BITS (__arr)[__i] = 0;                                            \
  } while (0)

#endif  /* GNU CC */

#define __FD_SET(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] |= __FD_MASK (d)))
#define __FD_CLR(d, set) \
  ((void) (__FDS_BITS (set)[__FD_ELT (d)] &= ~__FD_MASK (d)))
#define __FD_ISSET(d, set) \
  ((__FDS_BITS (set)[__FD_ELT (d)] & __FD_MASK (d)) != 0)

接下来是参数timeout,结构体形式为:

struct timeval 
{
	long    tv_sec;         /* seconds */
	long    tv_usec;        /* microseconds */
};

一般服务端实现的基础流程如下图

对应代码如下 

#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <iostream>
#include <string.h>
#include <vector>

#define INVALID_FD -1

int main(int argc,char * argv[])
{
    int listenfd = socket(AF_INET,SOCK_STREAM,0);
    if(listenfd == -1)
    {
        std::cout << "create socket err" << std::endl;
        return -1;
    }
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(3000);
    if(bind(listenfd,(struct sockaddr *)&bindaddr,sizeof(bindaddr)) == -1)
    {
        std::cout << "bind err" << std::endl;
    }

    if(listen(listenfd,SOMAXCONN) == -1)
    {
        std::cout << "listen err" << std::endl;
    }

    // 存储客户端socket的数组
    std::vector<int> clientfds;
    int maxfd = listenfd;

    while(true)
    {
        fd_set readset;
        FD_ZERO(&readset);  //清除readset的所有位

        FD_SET(listenfd,&readset);  //设置readset的位fd

        int clientfdslength = clientfds.size();
        for(int i=0; i<clientfdslength; ++i)
        {
            if(clientfds[i] != INVALID_FD)
            {
                FD_SET(clientfds[i],&readset);
            }
        }

        timeval tm;
        tm.tv_sec = 1;
        tm.tv_usec = 0;

        // 只检测可读事件
        int ret = select(maxfd+1,&readset,NULL,NULL,&tm);
        if(ret == -1)
        {
            if(errno != EINTR)  //出错退出
                break;
        }
        else if (ret == 0)
        {
            continue; //超时重来
        }
        else
        {
            if(FD_ISSET(listenfd,&readset)) //检测到有事件
            {
                // 侦听sockct的可读事件,表明新的连接来到
                struct sockaddr_in clientaddr;
                socklen_t clientaddrlen = sizeof(clientaddr);
                // 接收客户端连接,生成新的对应客户端连接socket
                int clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientaddrlen);
                if(clientfd == -1)
                {
                    break;
                }
                // 只接收连接不调用recv收取数据
                std::cout << "accept a client connection, fd:" << clientfd << std::endl;
                clientfds.push_back(clientfd);
                // 记录最新的最大fd值作为下一轮循环中select的第一个参数
                if (clientfd > maxfd)
                {
                    maxfd = clientfd;
                }
            }
            else
            {
                // 做客户端的时候保证传送数据不超过63个字符
                char recvbuf[64];
                int clientfdslength = clientfds.size();
                for(int i=0; i<clientfdslength; ++i)
                {
                    if(clientfds[i] != INVALID_FD && FD_ISSET(clientfds[i],&readset))
                    {
                        memset(recvbuf,0,sizeof(recvbuf));
                        // 非侦听socket,则接收socket
                        int length = recv(clientfds[i],recvbuf,64,0);
                        if( length <= 0 && errno != EINTR)
                        {
                            std::cout << "recv data err,clientfd: " << clientfds[i] << std::endl;

                            close(clientfds[i]);
                            clientfds[i] = INVALID_FD;
                            continue;
                        }
                        std::cout << "clientfd:" << clientfds[i] << ", recv data:" << recvbuf << std::endl; 
                    }
                }
            }
            
        }

    }

    int clientlength = clientfds.size();
    for (int i=0; i< clientlength; ++i)
    {
        if(clientfds[i] != INVALID_FD)
        {
            close(clientfds[i]);
        }
    }
    
    close(listenfd);
    return 0;
}

但是select也有几个点需要注意:

1. 看代码在循环里面,会先进行清除readset的所有位以及设置readset的位fd这两步操作,为何不在循环外面做?这是因为select 函数调用前后会修改 readfds、writefds 和 exceptfds 这三个集合中的内容。所以清零再重新设置,就可以复用该位。

2. 在循环中也可以看出需要修改函数超时时间timeout的结构,这也是因为在select函数调用后也会将timeout结构体的内容重置。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值