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

来源:互联网 发布:java编译器中文安卓版 编辑:程序博客网 时间:2024/05/17 01:13

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,        //监控的文件描述符集里最大文件描述符加1fd_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是否置1void FD_SET(int fd, fd_set *set); 把文件描述符集合里fd位置1void 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 1024int 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
原创粉丝点击