Unix系统编程(6) - I/O多路复用之select

1. I/O多路复用基本思路

I/O多路复用就是让应用程序可以同时对多个I/O端口进行监控以判断其上的操作是否可以进行,达到时间复用的目的。由于I/O多路复用是在单一进程的上下文中的,因此每个逻辑流程都能访问该进程的全部地址空间,所以开销比多进程低得多。由于I/O多路复用都是在单一进程中进行的,所以不会出现多线程中的线程不安全的问题。

2. select模型

在man page中给出的select函数原型:

       /* According to POSIX.1-2001 */
       #include <sys/select.h>

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

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

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

       #include <sys/select.h>

       int pselect(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, const struct timespec *timeout,const sigset_t *sigmask);

函数准许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。man page中给出了一个较新的函数pselect,这个函数仅仅是多了一个sigmask参数。sigmask参数指定了在执行pselect函数时屏蔽的信号集合。
select()函数参数:

int nfds,        //监控的文件描述符集里最大文件描述符加1
fd_set *readfds, //监控有读数据到达文件描述符集合
fd_set *writefds,//监控有写数据到达文件描述符集合
fd_set *exceptfds,//监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
struct timeval *timeout //定时阻塞监控时间,3种情况
/*
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为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

3. select模型回射服务器

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


#define SERV_PORT 8000
#define MAXLINE 1024

int main()
{
    //maxfd:        打开的最大文件描述符标号
    //listenfd:     监听描述符
    //confd:        链接描述符
    //clientaddrlen 客户端地址长度
    //sockfd:       暂存量
    int maxfd, listenfd, confd, clientaddrlen, sockfd, n, i = 0;
    struct sockaddr_in serveraddr, clientaddr;                      //服务器端地址,客户端地址
    char str[INET_ADDRSTRLEN];                                      //存放IP地址,点分十进制表示
    fd_set rset, allset;                                            //select 使用
    char buf[MAXLINE];                                              //传输数据
    int nReady, client[FD_SETSIZE], maxi;                           //               

    //1. 创建一个socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    //2. 绑定一个端口
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));

    //3. 设置监听
    listen(listenfd, 20);

    maxfd = listenfd;
    maxi = 0;

    for(i=0; i<FD_SETSIZE; ++i);                                    //初始化clinet[]
        client[i] = -1;

    FD_ZERO(&allset);                                               //select监控文件描述符集
    FD_SET(listenfd, &allset);

    for(;;)
    {
        rset = allset;
        nReady = select(maxfd + 1, &rset, NULL, NULL, NULL);

        if(nReady < 0)                                              //select出错
        {
            perror("select err\n");
            break;
        }

        if(FD_ISSET(listenfd, &rset))                               //新链接的客户端
        {
            clientaddrlen = sizeof(clientaddr);
            confd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);

            printf("ip: %s, port: %d \n", 
                  inet_ntop(AF_INET, &clientaddr.sin_addr, str, sizeof(str)),
                  ntohs(clientaddr.sin_port));

            for(i=0; i<FD_SETSIZE; ++i)
            {
                if(client[i] < 0)
                {
                    client[i] = confd;
                    break;
                }
            }

            if(i == FD_SETSIZE)
            {
                fputs("limited\n", stderr);
                exit(1);
            }

            FD_SET(confd, &allset);

            if(confd > maxfd) maxfd = confd;
            if(i > maxi) maxi = i;
            if(--nReady == 0) continue;
        }

        for(i=0; i<=maxi; ++i)                                      //遍历看哪个客户端有数据就绪
        {
            if((sockfd = client[i]) < 0) continue;
            if(FD_ISSET(sockfd, &rset))
            {
                if((n = read(sockfd, buf, MAXLINE)) == 0)
                {
                    close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i]=0;
                }
                else
                    write(sockfd, buf, n);
                if(--nReady == 0)
                    break;
            }
        }
    }
    close(listenfd);
    return 0;
}

测试用客户端代码,可使用上一篇博文中的客户端。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

空空的司马

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

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

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

打赏作者

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

抵扣说明:

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

余额充值