【Linux网络编程】select函数总结

1. 为什么使用IO多路复用

在应用程序中同时处理多路输入输出流时
1.若采用阻塞模式,将得不到预期的目的;
2.若采用非阻塞模式,对多个输入进行轮询,但又太浪费CPU时间;
3.若设置多个进程,又会产生资源的问题;
4.如果使用多线程,又涉及到临界资源访问的问题;

所以比较好的方法是使用I/O多路复用。

2. IO多路复用基本思想

先构造一张有关描述符的表,然后调用select函数。
当这些文件描述符中的一个或多个已准备好进行I/O时函数才返回。
函数返回时告诉进程哪个描述符已就绪,可以进行I/O操作。
io多路复用基本图解

select函数本身是阻塞的
只有当一个或者多个文件描述符准备就绪的时候,函数立即返回。
函数返回的的时候告诉我们文件描述符哪个已经就绪了。

3. select函数

功能:
	实现IO多路复用
头文件:
	 #include <sys/select.h>
函数原型:
	int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout);
参数
	@nfds:监视的最大文件描述符+1
	@readfds:要监视的读文件描述符集合,如果不关心,可以传NULL
	@writefds:要监视的写文件描述符集合,如果不关心,可以传NULL
	@exceptfds:要监视的异常的文件描述符集合,如果不关心,可以传NULL
	(一般我们只关心readfds)
	@timeout:超时时间
		为0时非阻塞
		为NULL时阻塞
		为结构体时阻塞一定时间
返回值:
	成功  返回就绪文件描述符的个数
	失败  返回-1,置位错误码
	超时  返回0
	
	void FD_CLR(int fd, fd_set *set);
	功能:
		删除集合中的文件描述符
	参数:
		@fd:文件描述符
		@set:构建要监视的文件描述符集合
	int  FD_ISSET(int fd, fd_set *set);
	功能:
		判断文件描述符是否在集合中
	参数:
		@fd:文件描述符
		@set:构建要监视的文件描述符集合
	返回值:
		为0时不在里面
		非0时在里面
	void FD_SET(int fd, fd_set *set);
	功能:
		将文件描述符添加到集合中
	参数:
		@fd:文件描述符
		@set:构建要监视的文件描述符集合
	void FD_ZERO(fd_set *set);
	功能:
		清空集合
	参数:
		@set:构建要监视的文件描述符集合

注意:

1.select只能监视小于 FD_SETSIZE(1024) 的文件描述符。
2.select函数在返回时会将没有就绪的文件描述符在表中擦除,
  所以,在循环中调用select时,每次需要重新填充集合。

4. 实例代码

三个写端(除了打开的管道文件不同基本一致)
其中一个代码为:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    int fd1;
    char buf[128] = {0};
    //只读方式打开文件
    fd1 = open("fifo1", O_WRONLY);
    while (1)
    {
        //每次获取数据前清空一下buf
        memset(buf,0,sizeof(buf));
        //从终端获取数据
        fgets(buf, sizeof(buf), stdin);
        //将获取数据的\n改为\0
        buf[strlen(buf) - 1] = '\0';
        //将数据写到管道文件中
        write(fd1, buf, strlen(buf));
    }
    //关闭文件
    close(fd1);
    return 0;
}

读端代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>

int main(int argc,const char * argv[])
{
    //以只读的方式打开管道文件
    int fd1=open("fifo1",O_RDONLY);
    int fd2=open("fifo2",O_RDONLY);
    int fd3=open("fifo3",O_RDONLY);
    char buff[128]={0};
    //定义文件描述符表的结构体变量(母体)
    fd_set readfds;
    //定义文件描述符表的结构体变量(子体)
    fd_set readfds_msg;
    //清空变量
    FD_ZERO(&readfds);
    FD_ZERO(&readfds_msg);
    
    char buf[128]={0};
    int max_fd=0;
    int ret;
    int i;
    //将文件描述符放入表中
    FD_SET(fd1,&readfds);
    //将最大文件描述符保存在max_fd中
    max_fd=max_fd>fd1?max_fd:fd1;
    FD_SET(fd2,&readfds);
    max_fd=max_fd>fd2?max_fd:fd2;
    FD_SET(fd3,&readfds);
    max_fd=max_fd>fd3?max_fd:fd3;

    //循环读取输入到终端上
    while(1)
    {
        //每次循环前将母本重新赋值给子本
        //因为select每次调用都会擦除没有没有就绪的文件描述符
        readfds_msg=readfds;
        //select返回值为就绪的文件描述符个数
        if((ret=select(max_fd+1,&readfds_msg,NULL,NULL,NULL))==-1)
        {
            perror("select error");
        }
        //将读取的数据写到终端中
        for(i=3;i<max_fd+1&&ret!=0;i++)
        {
            //判断文件是否就绪,就绪将数据写到终端上
            if(FD_ISSET(i,&readfds_msg))
            {
                memset(buf,0,sizeof(buf));
                read(i,buf,sizeof(buf));
                printf("[%d]号写端发来[%s]\n",i,buf);
                ret--;
            }
        }
    }
    close(fd1);
    close(fd2);
    close(fd3);

    return 0;
}

5. 结果展示

代码结果为:
结果
在图中可以发现实现了三个写端对一个读端的并发执行。

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

夜猫徐

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

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

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

打赏作者

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

抵扣说明:

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

余额充值