Linux--I/O多路复用(select)

I/O多路复用

I/O多路复用是在多线程或多进程编程中常用技术。主要是通过select/epoll/poll三个函数支持的,就是通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流,I/O 多路复用技术通过把多个 I/O 的阻塞复用到同一个 select 的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求

在之前我们运用多线程的方式也完成了一个可以同时处理客户端的TCP和UDP的简单服务器
线程实现可以同时处理客户端的服务器:https://blog.csdn.net/wfea_lff/article/details/104184974

那我们为什么要用I/O多路复用来完成一个可以同时处理客户端的服务器?? 这两者之间的差距在哪?

多线程缺点:

引入多线程后,系统的执行路径变成了多条,并且这多条执行路径在并发运行。那么首先程序的执行具有一定的不确定性,每次执行结果可能都会不同,因为程序交 替执行的顺序和时机不同了。其次,使得某些资源的访问出现了竞争,问题变得困难,需要资源进行同步。这会使得程序的可靠性和稳定性降低。

I/O多路复用优点:

与传统的多线程/多进程模型相比。I/O 多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源。

应用场景

服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;
服务器需要同时处理多种网络协议的套接字。

一、相关函数

1.select

作用:
select可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。

函数原型

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

所需头文件

#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

返回值:
成功:
(1)当没有满足条件的文件描述符,且设置的timeval 监控时间超时时,select函数会返回一个为0的值。
(2)返回相应的满足条件的文件描述符集中的数量
失败:-1

参数:
nfds:监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds:监控有读数据到达文件描述符集合,传入传出参数
writefds:监控写数据到达文件描述符集合,传入传出参数
exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 结构体,定时阻塞监控时间
3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
结构体结构如下

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

补充:

fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。
系统提供了4个宏对描述符集进行操作

void FD_CLR(int fd, fd_set *set); 	//把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); 	//测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); 	//把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); 			//把文件描述符集合里所有位清0

参数
fd:文件描述符
set:long类型的数组,当调用select()时由内核根据IO状态修改内容,由此来通知执行了select()的进程哪个句柄可读

FD_ISSET的返回值
成功:1
失败:0

select的缺点:

1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2. 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力

二、select参考代码

服务器:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>

#define MAX 10

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
     {
         printf("creat socket fail\n");
         return -1;
     }
     else
     {
         printf("creat socket success , sockfd = %d\n",sockfd);
     }

    struct sockaddr_in seraddr,cliaddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(5000);
    seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    memset(seraddr.sin_zero,0,8);
    
    socklen_t len = sizeof(struct sockaddr);
    int bindret = bind(sockfd,(struct sockaddr *)&seraddr,len);
    if(bindret == -1)
    {
        printf("bind fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("bind success\n");
    }
    
    int listenret = listen(sockfd,MAX);
    if(listenret == -1)
    {
        printf("listen fail\n");
        close(sockfd);
        return -3;
    }
    else
    {
        printf("listen success\n");
    }

    fd_set read,readset;
    int maxfd = sockfd;

    FD_ZERO(&readset);
    FD_SET(sockfd,&readset);

    while(1)
    {
        int confd;
        read = readset;

        int selectret = select(maxfd+1,&read,NULL,NULL,NULL);
        if(selectret == -1)
        {
            printf("select fail\n");
            close(sockfd);
            return -4;
        }
        
        if(FD_ISSET(sockfd,&read))
        {
            confd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
            printf("connect client(%s) success\nconfd = %d\n",inet_ntoa(cliaddr.sin_addr),confd);
            if(maxfd < confd)
            {
                maxfd = confd;
            }
            FD_SET(confd,&readset);
        
            if(selectret == 1)
            {
                continue;
            }
        }
        
        char buf[128];
        int i;
        for(i = sockfd+1;i<=maxfd;i++)
        {
            memset(buf,0,128);
            if(FD_ISSET(i,&read))
            {
                if(recv(i,buf,128,0) == 0)
                {
                    FD_CLR(i,&readset);
                    printf("----------client(%s) quit---------\n",inet_ntoa(cliaddr.sin_addr));
                    continue;
                }
                else
                {
                    printf("client(%s) send message\n%s",inet_ntoa(cliaddr.sin_addr),buf);
                    if(strncmp(buf,"end",3) == 0)
                    {
                        FD_CLR(i,&readset);
                        printf("----------client(%s) quit---------\n",inet_ntoa(cliaddr.sin_addr));
                        continue;
                    }
                }
            }
        }
    }

    return 0;
}

客户端:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>

int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd < 0)
    {
        printf("creat socket fail\n");
        return -1;
    }
    else
    {
        printf("creat socket success , sockfd = %d\n",sockfd);
    }

    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(5000);
    cliaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    memset(cliaddr.sin_zero,0,8);

    int confd = connect(sockfd,(struct sockaddr *)&cliaddr,sizeof(struct sockaddr));
    if(confd < 0)
    {
        printf("connect fail\n");
        close(sockfd);
        return -2;
    }
    else
    {
        printf("----------connect success-----------\nconfd = %d\n",confd);
    }

    char buf[128];
    while(1)
    {
        memset(buf,0,128);
        printf("send message to server\n");
        fgets(buf,128,stdin);
        send(sockfd,buf,strlen(buf),0);
        if(strncmp(buf,"end",3) == 0)
        {
            break;
        }
    }

    close(sockfd);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值