【linux 多路复用 select】

本文详细解释了select函数的功能、用法以及注意事项,特别是在服务器端监控多个文件描述符的就绪状态,包括超时管理和fd_set数据结构的应用。还提供了示例代码来演示如何在服务器程序中使用select进行IO多路复用。
摘要由CSDN通过智能技术生成

简介

功能:select允许程序监视多个文件描述符,直到一个或多个文件描述符可读或可写则认为文件描述符已准备就绪
特点:支持阻塞模式和非阻塞模式
阻塞模式 直到监听的文件描述符可读或者可写返回
非阻塞模式 描述符就绪,设置的超时时间到,被信号处理程序中断 返回
注意点:1.select在退出时,每个文件描述符集和超时时间都会被修改,以指示哪些文件描述符实际更改了状态,所以循环调用前必须重新初始化,超时时间也必须重新设置!!!
2.在文件描述符集中可以指定的文件描述符范围上。Linux内核没有固定的限制,但是glibc实现使fd设置一个固定大小的类型,其中fd SETSIZE定义为1024,并且fd *()宏根据该限制进行操作。要监视大于1023的文件描述符,请使用poll(2)

FD_ZERO() 重置描述符.
FD_SET() 添加描述符
FD_CLR() 删除描述符
FD_ISSET 文件描述符是否在就绪集合里

结构体

//超时设置
struct timeval {
    long    tv_sec;         /* seconds */long    tv_usec;        /* microseconds */		微秒
};
函 数 名  : select
功能描述  : 
输入参数  :  int nfds 监听最大描述符+1
		   fd_set *readfds  监听读属性
		   fd_set *writefds 监听写属性
		   fd_set *exceptfds 监听异常属性
		   struct timeval *timeout 超时设置
返 回 值  : int -1 失败 并设置错误码 0 超时 >0 就绪文件描述符个数(读 写 异常集合 总数)
int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
*************************************************************/

服务器端代码(仅用于验证)

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>

#define SERVER_PORT       9999
#define SERVER_LISTEN_NUM 10
#define TIMEOUT_SEC       5
#define TIMEOUT_USEC      0
#define SOKCETLIST_MAX    SERVER_LISTEN_NUM+1

/*
struct sockaddr {
    sa_family_t sa_family;
    char        sa_data[14];
}

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

*/

typedef struct
{
    int use;
    int sock;
}SocketList_t;

SocketList_t g_sock_list[SOKCETLIST_MAX];

int socketlist_init(void)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        g_sock_list[i].use  = 0;
        g_sock_list[i].sock = -1;
    }

}

int socketlist_add(int sock)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (g_sock_list[i].use)
        {
            continue;
        }

        g_sock_list[i].use  = 1;
        g_sock_list[i].sock = sock;

        return 0;
    }

    return -1;
}

void socketlist_del(int sock)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (g_sock_list[i].use && g_sock_list[i].sock == sock)
        {
            g_sock_list[i].use  = 0;
            g_sock_list[i].sock = -1;
            return;
        }
    }
}

int socketlist_fdmax_get(void)
{
    int i      = 0;
    int maxfd = -1;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (!g_sock_list[i].use)
        {
            continue;
        }

        maxfd = g_sock_list[i].sock >= maxfd ? g_sock_list[i].sock : maxfd;
    }

    return maxfd;
}

void sock_init_before_select(fd_set *readset, fd_set *writeset, fd_set *errorfds)
{
    int i = 0;

    if (readset)
    {
        FD_ZERO(readset);
    }
    if (writeset)
    {
        FD_ZERO(writeset);
    }
    if (errorfds)
    {
        FD_ZERO(errorfds);
    }

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (!g_sock_list[i].use)
        {
            continue;
        }

        if (readset)
        {
            FD_SET(g_sock_list[i].sock, readset);
        }

        if (writeset)
        {
            FD_SET(g_sock_list[i].sock, writeset);
        }

        if (errorfds)
        {
            FD_SET(g_sock_list[i].sock, errorfds);
        }
    }
}

int main(void *arg, char *argv[])
{
    int i           = 0;
    int sock        = -1;
    int listen_sock = -1;
    int ret         = 0;
    int max_fd      = -1;
    struct sockaddr_in address;
    struct sockaddr_in remote_addr;
    socklen_t remote_addrlen = sizeof(remote_addr);
    fd_set writeset;
    fd_set readset;
    fd_set errorfds;
	unsigned long optval = 1;
    struct timeval timeout = {TIMEOUT_SEC, TIMEOUT_USEC};
    unsigned char recv_buf[256];

    socketlist_init();

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock)
    {
        printf("socket fail\r\n");
        return -1;
    }

    //一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    address.sin_family = AF_INET;
    address.sin_port = htons(SERVER_PORT);
    address.sin_addr.s_addr = INADDR_ANY;
    ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    if (-1 == ret)
    {
        printf("bind fail\r\n");
        return -1;
    }

    ret = listen(sock, SERVER_LISTEN_NUM);
    if (-1 == ret)
    {
        printf("listen fail\r\n");
        return -1;
    }

    socketlist_add(sock);

    while (1)
    {
        max_fd = socketlist_fdmax_get();
        sock_init_before_select(&readset, &writeset, &errorfds);
        printf("time sec:%ld,usec:%ld\r\n", timeout.tv_sec, timeout.tv_usec);
        timeout.tv_sec  = TIMEOUT_SEC;
        timeout.tv_usec = TIMEOUT_USEC;
        ret = select(max_fd+1, &readset, NULL, &errorfds, &timeout);    //连接连上了文件描述符置为可写
        printf("select ret:%d\r\n", ret);
        if (-1 == ret)
        {
            printf("select error:%d,%s\r\n", errno, strerror(errno));
        }
        else if (0 == ret)
        {
            printf("select time out\r\n");
        }
        else
        {
            for (i = 0; i < SOKCETLIST_MAX; i++)
            {
                if (g_sock_list[i].use && FD_ISSET(g_sock_list[i].sock, &readset))
                {
                    if (sock == g_sock_list[i].sock)
                    {
                        listen_sock = accept(sock, (struct sockaddr *)&remote_addr, (socklen_t *)&remote_addrlen);
                        if (-1 == listen_sock)
                        {
                            printf("accept fail err:%s\r\n", strerror(errno));
                            continue;
                        }
                        printf("new client addr\r\n");
                        socketlist_add(listen_sock);
                    }
                    else
                    {
                        //可以创建客户端线程处理,这里只是简单验证
                        ret = recv(g_sock_list[i].sock, recv_buf, sizeof(recv_buf), 0);
                        if (-1 == ret)
                        {
                            continue;
                        }
                        printf("recv success ret:%d\r\n", ret);
                    }
                }
            }
        }
    }

}

返回错误情况

1.EBADF 无效描述符,例如监听的描述符被close了 则返回此错误 文件描述符被close了
2.EINTR(signal 7)Linux平台上执行malloc(),如果没有足够的RAM,Linux不是让malloc()失败返回, 而是向当前进程分发SIGBUS信号
3.EINVAL 无效参数 (1).select 第一个参数 最大描述符+1是负数(2).描述符超过最大值(3).超时参数为负数 参数无效
4.ENOMEM 内存申请失败

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
select函数是Linux C编程中的一种多路复用机制,用于监视并等待多个文件描述符的属性发生变化。它可以同时监视多个文件描述符的可读、可写和异常状态。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参数指定等待时间,当超过指定时间后,select函数会返回。当select函数返回后,可以通过遍历fd_set集合来确定哪些文件描述符已经就绪。 使用select函数可以实现多个文件描述符的并发处理,提高程序的效率和响应速度。 #### 引用[.reference_title] - *1* [IO多路复用之——select详解](https://blog.csdn.net/weixin_60954394/article/details/127062613)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [LinuxC语言多路复用——select函数](https://blog.csdn.net/qq_45097019/article/details/105166595)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值