linux平台IO多路复用 select接口使用例子

这几天在学习net-snmp源码,里面封装了很多select函数调用,这里记录一下linux上select的用法以及相关接口。

先看接口:

//头文件
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

/*
 *  参数nfds表示监听的描述符个数,通常等于最大的描述符加一,select最多同时监听描述符
 *  数量有个上限,FD_SETSIZE(1024),不同平台这个值可能不同,所以如果程序中监听数量特别
 *  多的话,建议使用epoll。
 *
 *  参数 readfds, writefds, exceptfds表示描述符集,可以把我们关心的描述符放到对应的
 *  描述符数组里面,这三个分别对应着可读、可写和异常事件。可以都设置为NULL,这时候select
 *  调用就相当于一个更精确的sleep。 
 *
 *  参数 timeout表示select超时时间,如果为NULL的话,表示永久阻塞,除非监听的描述符集上
 *  有事件发生或者收到信号,为0的话,表示立即返回,其它的值则表示相应的等待时间。
 *
 *  成功返回准备好读写的文件描述符数量,
 *  返回0表示超时,返回-1表示出错。
 */
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
           
/* 从fdset中清空该文件描述符标志位 */           
void FD_CLR(int fd, fd_set *set); 

/* 判断该文件描述符上是否有事件发生 */
int  FD_ISSET(int fd, fd_set *set);

/* 将该文件描述符添加到fd_set数组中 */
void FD_SET(int fd, fd_set *set);

/* 初始化fdset */  
void FD_ZERO(fd_set *set);

每次调用select后,都需要重新清空描述符集并重新添加感兴趣的文件描述符。另外,select返回时会将
剩余时间填充到timeout参数中,因此重新调用select的时候也要重新初始化该时间参数。

示例,创建两个udp套接字,使用select循环监听可读事件,注意收到事件处理完成后需要重新对fd_set描述符集进行初始化,

这一点不如epoll使用方便。

/**
 *  Description : linux 环境 select接口使用示例
 *      创建两个udp套接字,然后使用select监听套接字上读事件。        
 *  Date        : 20181001
 *  Author      : mason
 */

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>

#define BUFFER_SIZE 512
#define log(fmt, arg...) printf("[udptest] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)

void main()
{
    int sock, sock2;
    int addr_len, recv_len;
    char buffer[BUFFER_SIZE] = {0};
    struct sockaddr_in addr, addr2;

    fd_set rfds;
    struct timeval tv;
    int retval, maxfdp1 = 0;
    
    /* 创建UDP套接字 */
    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock == -1) {
        log("create socket fail \r\n");
        return ;
    }    
    
    sock2 = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock2 == -1) {
        log("create socket2 fail \r\n");
        close(sock);
        return ;
    }  

    /* 设置监听地址 */
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY; 
    addr.sin_port = htons(40000);
        
    addr2.sin_family = AF_INET;
    addr2.sin_addr.s_addr = INADDR_ANY; 
    addr2.sin_port = htons(30000);
    addr_len = sizeof(struct sockaddr_in);

    /* 绑定本地监听地址 */
    if (0 != bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)))
    {
        log("bind local listening addr fail,errno : %d \r\n", errno);
        goto end;
    }  
    
    if (0 != bind(sock2, (struct sockaddr *)&addr2, sizeof(struct sockaddr_in)))
    {
        log("bind local listening addr fail,errno : %d \r\n", errno);
        goto end;
    } 

    /* 初始化描述符集 */
    FD_ZERO(&rfds);

    /* 添加到描述符集里面 */
    FD_SET(sock, &rfds);
    maxfdp1 = maxfdp1 > sock ? (maxfdp1 + 1) : (sock + 1);

    /* 添加到描述符集里面 */
    FD_SET(sock2, &rfds);
    maxfdp1 = maxfdp1 > sock ? (maxfdp1 + 1) : (sock + 1);

    /* select超时10s */
    tv.tv_sec = 10;
    tv.tv_usec = 0;

    /* 循环监听 */
    for (;;)
    {
        /* 只监听读事件 */
        retval = select(maxfdp1, &rfds, NULL, NULL, &tv);
        if (retval > 0)
        {
            /* 判断是否可读 */
            if (FD_ISSET(sock, &rfds))
            {
                recv_len = read(sock, buffer, sizeof(buffer));
                if (recv_len != -1)
                {
                    log("revc from sock : %s\r\n", buffer);
                    memset(buffer, 0, sizeof(buffer));
                }
            }
            if (FD_ISSET(sock2, &rfds))
            {
                recv_len = read(sock2, buffer, sizeof(buffer));
                if (recv_len != -1)
                {
                    log("revc from sock2 : %s\r\n", buffer);
                    memset(buffer, 0, sizeof(buffer));
                }
            }
        }
        else if (retval == 0)
        {
            /* select 超时 */
            log("select timeout \r\n");
        }
        else
        {
            log("select error \r\n");
        }

        /* 清空标志位 */
        FD_ZERO(&rfds);

        /* 重新设置超时 */
        tv.tv_sec = 5;
        
        /* 重新添加到select监听数组中 */
        FD_SET(sock, &rfds);
        FD_SET(sock2, &rfds);
    }
   
end:
   close(sock);
   close(sock2);

   return;
}

Makefile:

#
# Linux 同步IO复用 select接口例子
#

app:
	gcc -o select_demo select_demo.c

clean:
	rm -rf *.o select_demo

运行截图:

参考资料:

1. man select http://www.man7.org/linux/man-pages/man2/select.2.html

2. 《UNIX网络编程卷一 套接字API》第6章 IO多路复用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值