【IO复用】select(处理读/写/异常事件)

IO复用可以使程序同时监听多个文件描述符
客户端和服务器:(如果一个客户端在给服务器发数据,那么就会在recv阻塞住,就多个客户端就不能并发运行,就需要使用多线程,来接收不同的c,但是多线程是有限的)
通过检测那个文件描述中有数据,就去接收
一个select最多处理1024个描述符 只要使用的就置为1
按位与,来判断是否有数据。

  • select
    select 成功时返回就绪(可读、可写和异常)文件描述符的总数。如果在超时时间内
    没有任何文件描述符就绪,select 将返回 0。select 失败是返回-1.如果在 select 等待
    期间,程序接收到信号,则 select 立即返回-1,并设置 errno 为 EINTR。
    请添加图片描述
    请添加图片描述
int select(int maxfd, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
//maxfd 参数指定的被监听的文件描述符的总数。它通常被设置为 select 监听的所
//readfds、writefds 和 exceptfds 参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用 select 函数时,通过这 3 个参数传入自己感兴趣的文件
//描述符。select 返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪
  • fd_set 结构如下:
#define __FD_SETSIZE 1024
 typedef long int __fd_mask;
#define __NFDBITS (8 * (int) sizeof (__fd_mask))
 typedef struct
 {
 #ifdef __USE_XOPEN
 __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->fds_bits)
 #else
 __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
 # define __FDS_BITS(set) ((set)->__fds_bits)
 #endif
 } fd_set;
  • 通过下列宏可以访问 fd_set 结构中的位:
 通过下列宏可以访问 fd_set 结构中的位:
 FD_ZERO(fd_set *fdset); // 清除 fdset 的所有位
 FD_SET(int fd, fd_set *fdset); // 设置 fdset 的位 fd

 FD_CLR(int fd, fd_set *fdset); // 清除 fdset 的位 fd
 int FD_ISSET(int fd, fd_set *fdset);// 测试 fdset 的位 fd 是否被设置
 timeout 参数用来设置 select 函数的超时时间。它是一个 timeval 结构类型的指
针,采用指针参数是因为内核将修改它以告诉应用程序 select 等待了多久。
  • timeval
    结构的定义如下:
 struct timeval
 {
 long tv_sec; //秒数
 long tv_usec; // 微秒数
 };
 如果给 timeout 的两个成员都是 0,则 select 将立即返回。如果 timeout 传递NULL,则 select 将一直阻塞,直到某个文件描述符就绪
有文件描述符中的最大值+1
  • select.c
#include<unistd.h>
#include<sys/select.h>
#include<sys/time.h>

#define STDIN 0
int main()
{
        int fd = STDIN;//键盘(标准输入)只有一个文件描述符
        fd_set fdset;
        while(1)
        {
                FD_ZERO(&fdset); //将所有的描述符的集合清0
                FD_SET(fd,&fdset); //给所有的位赋值
                struct timeval tv = {5,0};//select遍历完以后,阻塞,等待5秒钟,等待数据就绪,如过再5秒内有数据就绪,会立即读出,超过5秒,则n = 0,就绪的描述符为0,打印timeout,再重新执行select、。
                int n = select(fd+1,&fdset,NULL,NULL,&tv);
                                    
                //不需要把所有的位都遍历一边,只需要遍历到最后一个设置的位即可。
                //描述符的集合
               //select 成功时返回就绪(可读、可写和异常)文件描述符的总数。
            if( n == -1)
            {
                     printf("select err\n");
                     continue;
             }
            else if(n == 0)
            {
                    printf("time out\n");
            }
            else
            {
                    if(FD_ISSET(fd,&fdset)) //判断文件描述符是否已经就绪
                    {
                       char buff[128] = {0};
                       read(fd,buff,127);
                       printf("read:%s\n",buff);
                    }
            }
        }
}
  • ser.c
#include<stdlib.h>
#include<string.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/time.h>
#include<assert.h>
#define FD_MAX 10
int socket_init();
void fds_init(int fds[])
{
        for(int i = 0 ;i< FD_MAX;i++)
        {
                 fds[i] = -1;
         }
}
void fds_add(int fds[],int fd)
{
        assert(fd != -1); 
        for(int i = 0;i<FD_MAX;i++)
        {
                if(fds[i] == -1)
                {
                fds[i] = fd;  //每次添加一个描述符,fd是一个整型值,1,2,3....
                break;
                }
        }
}
void fds_del(int fds[],int fd)
{
         for(int i = 0 ;i< FD_MAX;i++)
         {
                if(fds[i] == fd)
                {
                        fds[i] = -1;
                        break;
                }
         }
}
int main()
{
     int sockfd = socket_init(); //返回的是监听套接子
     if(sockfd == -1)
     {
             printf("socket init failed\n");
             exit(1);
     }
     int fds[FD_MAX];
     fds_init(fds);
     fds_add(fds,sockfd);
     fd_set fdset;
     while(1)
     {
             FD_ZERO(&fdset);
             int maxfd = -1;  //找到最大的值
             for(int i = 0;i<FD_MAX;i++)
             {
                     if(fds[i] == -1)   
                     {
                           continue;
                     }
                     //将数组中所有不等于-1的描述符添加到集合中。
                      
                     FD_SET(fds[i],&fdset);
                     if(maxfd <fds[i])
                     {
                             maxfd = fds[i];
                     }
             }
             struct timeval tv = {5,0};
             int n = select(maxfd+1,&fdset,NULL,NULL,&tv);  //找到集合中就绪的描述符  遍历的是集合!!!!!!!不是数组!!!!,所以是fd的值!!!! 因此是maxfd+1
             if(n == -1)
             {
                      continue;
             }
             else if(n == 0)
             {
                    printf(" timeout\n");
                     continue;

             }
             else
             {
                     for(int i = 0;i<FD_MAX;i++)
                     {
                             if(fds[i] == -1)
                             {
                                     continue;
                             }
                             if(FD_ISSET(fds[i],&fdset))
                             {
                                  //描述符上是否有事件产生,connect 的时候以及close的时候,
                                  //不同的描述符需要写不同if,else,比如管道描述符和套接子描述符就是不同的描述符。处理方式就不同
                                     if(fds[i] == sockfd)
                                     {           
                                     struct sockaddr_in caddr;
                                     int len = sizeof(caddr);
                                     int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
                                      if(c < 0)
                                      {
                                          continue;
                                      }
                                      printf("accept c = %d\n",c);
                                      fds_add(fds,c);
                                      }
                             else
                             {
                                     char buff[128] = {0};
                                     int num =  recv(fds[i],buff,127,0);
                                     if(num <  0)
                                     {
                                            close(fds[i]);
                                            fds_del(fds,fds[i]);
                                            printf("client close\n");
                                      }
                                     else
                                     {
                                           printf("recv(%d) = %s\n",fds[i],buff);
                                           send(fds[i],"ok",2,0);
                                     }
                                     }
                             }
                     }
             }

             
     }
}
int socket_init()
{
        int sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(sockfd == -1)
        {
                return -1;
        }
        struct sockaddr_in saddr;
        memset(&saddr,0,sizeof(saddr));
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(6000);
        saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

        int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
        if(res == -1)
        {
                return -1;
        }
        res = listen(sockfd,5);
        if(res == -1)
        {
                 return -1;
        }                       //不通过accept来处理完成连接的监听套接子,而是由select来处理。
        return sockfd;

}
  • cli.c
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
int main() 
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1 )
    {
           exit(1);
     }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res == -1)
    {
            printf("connect falied\n");
            close(sockfd);
           exit(1);
     }
    while(1)
    {
    char buff[128] = {0};
    printf("input\n");
    fgets(buff,128,stdin);
    if(strncmp(buff,"end",3)== 0)
    {
            break;
    }
    send(sockfd,buff,strlen(buff)-1,0);
    memset(buff,0,128);
    recv(sockfd,buff,127,0);
    printf("recv = %s\n",buff);
    }
    close(sockfd);
    exit(0);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多路 I/O 复用是一种在系统编程中常用的技术,它允许一个进程同时监视多个 I/O 事件,以提高程序的效率和响应能力。在 C 语言中,一个常用的多路 I/O 复用函数是 `select`。 `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`:超时时间,如果设置为 NULL,则阻塞直到有事件发生;如果设置为零,立即返回;如果设置为一个指定时间,超过该时间还没有事件发生,则返回。 `select` 函数的工作原理是将进程阻塞,直到监视的文件描述符集合中的任意一个文件描述符就绪(可读可写或出现异常),或者超过指定的超时时间。 使用 `select` 函数进行多路 I/O 复用的一般步骤如下: 1. 创建并初始化文件描述符集合。 2. 将需要监视的文件描述符添加到相应的集合中。 3. 调用 `select` 函数进行阻塞等待。 4. 检查哪些文件描述符已经就绪。 5. 处理就绪的文件描述符。 需要注意的是,`select` 函数在每次调用时都会修改传入的文件描述符集合,因此在每次调用前需要重新初始化。 除了 `select`,还有其他的多路 I/O 复用函数,如 `poll` 和 `epoll`,它们在不同的操作系统中有不同的实现方式和特性,可以根据具体需求选择合适的函数。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值