简述:

多路I/O复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

这个机制能够通过select/poll/epoll等来使用。这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的 I/O 操作都能在一个线程内并发交替地顺序完成。

不是不阻塞,是集中阻塞;

Select

select仅仅知道了有I/O事件发生了,却并不知道是哪那几个文件描述符(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据

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

形参:

nfds:轮询的最大文件描述符+1

readfds:监控集合中变化,例如监控服务器的套接字放到读集合中

writefds:监控集合中描述符的写变化  NULL

exceptfds:监控集合中描述符的异常变化  NULL

timeout: 具体填写一下结构体中的成员,表明在指定时间内监控变化,超时直接返回;

     赋值0;表示非阻塞(函数运行时,有描述符变化,得到对应信息,如果没有描述符改变,函数直接返回)

     赋值NULL阻塞,select函数运行开始,等待直到描述符发生改变

struct timeval {

    long    tv_sec;     /* seconds */

    long    tv_usec;    /* microseconds */

};

返回值:

成功返回发生变化的描述符的个数;超时返回0;失败返回-1

  注意:调用一次select 只能监控一次描述符的变化,集合中的描述符发生改变后,会从集合中自动消除;

2)操作集合相关函数:

void FD_CLR(int fd, fd_set *set); //将fd从集合中去除

int  FD_ISSET(int fd, fd_set *set);//判断集合中fd是否发生变化,是返回真

void FD_SET(int fd, fd_set *set);//将fd添加到集合中

void FD_ZERO(fd_set *set);//清空集合

例题:利用多路io实现多人聊天

//----------------ser
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define PORT 12234
#define IP "127.0.0.1"
int max=0;
int cfd[20]={0};
int count=0;
int main()
{
    //1.创建套接字
    int sfd=socket(AF_INET,SOCK_STREAM,0);
    if(sfd==-1)
    {
        perror("socket");
        return -1;
    }
    //2.绑定
    struct sockaddr_in s_addr;
    s_addr.sin_family=AF_INET;
    s_addr.sin_port=htons(PORT);
    s_addr.sin_addr.s_addr=inet_addr(IP);

    int res=bind(sfd,(struct sockaddr *)&s_addr,sizeof(s_addr));
    if(res==-1)
    {
        perror("bind");
        return -1;
    }
    //3.监听
    res=listen(sfd,20);
    if(res==-1)
    {
        perror("listen");
        return -1;
    }
    struct sockaddr_in c_addr;//用于连接时所需绑定的端口和ip
    socklen_t len=sizeof(c_addr);
    max=sfd;
    fd_set table;//创建操作表
    fd_set bf_table;//备份
    FD_ZERO(&bf_table);//清空操作表
    FD_SET(sfd,&bf_table);//将sfd添加到操作表中
    char r_buf[128]={0};
    char w_buf[128]={0};
    while(1)
    {   
        table=bf_table;//将备份表里的内容给操作表
        res = select(max+1,&table,NULL,NULL,NULL);
        if(FD_ISSET(sfd,&table))//如果判断为真成立说明要连接
        {
            cfd[count]=accept(sfd,(struct sockaddr *)&c_addr,&len);
            max=(max>cfd[count])?max:cfd[count];
            //时刻保存select第一个参数是套接字中最大的
            FD_SET(cfd[count],&bf_table);
            count++;
            //把新连接的套接字放入备份表中
            if(res==1)//说明操作表内只有一个套接字 sfd
            {
                continue;
            }
        }
        //到此处说明操作表内不只一个套接字
        //有人要发消息情况
        int i=0;
        for(i=0;i<count;i++)//判断是谁发消息
        {
            if(FD_ISSET(cfd[i],&table))
            {
                memset(r_buf,0,sizeof(r_buf));
                int a=read(cfd[i],r_buf,sizeof(r_buf));
                if(a>0)
                {
                    strcpy(w_buf,r_buf);
                    //把读到的信息放入到写端里
                    for(int k=0;k<count;k++)
                    {
                        if(k!=i)//除发的本人外都发送
                        {
                            write(cfd[k],w_buf,sizeof(w_buf));
                        }
                    }
                }
                else if(a==0)
                {
                    FD_CLR(cfd[i],&bf_table);
                    sprintf(w_buf,"%d下线",cfd[i]);
                    //通知别人自己已下线
                    for(int k=0;k<count;k++)//除发的本人外都发送
                    {
                        if(k!=i)
                        {
                            write(cfd[k],w_buf,sizeof(w_buf));
                        }
                    }
                    //去除已下线的
                    for(int j=i;j<count;j++)
                    {
                        cfd[j]=cfd[j+1];
                    }
                    count--;
                }
            }
        }

    }
   
    return 0;
}
//-----------------cli.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#define IP "127.0.0.1"
#define PORT 12234
int fd=0;
void *W_FUN(void *p);
void *R_FUN(void *p);
int main()
{
    //1.创建套接字
    fd=socket(AF_INET,SOCK_STREAM,0);
    printf("fd = %d\n",fd);
    if(fd==-1)
    {
        perror("socket");
        return -1;
    }
    //2.连接
    struct sockaddr_in ser_addr;
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=inet_addr(IP);
    ser_addr.sin_port=htons(PORT);
    int res=connect(fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
    if(res==-1)
    {
        perror("connect");
        return -1;
    }
    printf("连接成功\n");
    //创建读写进程
    pthread_t r_id,w_id;
    pthread_create(&r_id,NULL,R_FUN,NULL);
    pthread_create(&w_id,NULL,W_FUN,NULL);

    pthread_join(r_id,NULL);
    pthread_join(w_id,NULL);
    return 0;
}
void *R_FUN(void *p)
{
    char buf[128]={0};
    int a=0;
    while(1)
    {
        memset(buf,0,sizeof(buf));
        a=read(fd,buf,sizeof(buf));
        printf("%s\n",buf);
        if(a==0)
        {
            close(fd);
            exit(0);
        }
    }
}

void *W_FUN(void *p)
{
    char buf[128]={0};
    while(1)
    {
        memset(buf,0,sizeof(buf));
        scanf("%s",buf);
        getchar();
        write(fd,buf,sizeof(buf));
        if(strcmp(buf,"bye")==0)
        {
            close(fd);
            exit(0);
        }
    }
}