关于select , pselect , poll

来源:互联网 发布:淘宝上门取件在哪里 编辑:程序博客网 时间:2024/05/22 09:47
    处理多个输入的时候,需要使得内核一旦发现进程指定的一个或多个I/O条件就绪(即输入准备好被读取,或描述符已能承接更多输出),它就通知进程
        此即为 I/O 复用


    应用场合:
        1、客户处理多个描述符时(交互式输入+网络套接字)
        2、客户同时处理多个套接字
        3、一个TCP服务器既要处理监听套接字,又要处理已连接套接字
        4、服务器既要处理TCP,又要UDP
        5、服务器处理多个服务或多个协议


int select(int maxfdpl, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);    (一)        struct timeval        {            long tv_sec ;    //sec            long tv_usec;    //micro-sec        };        1、为空: 永远等下去        2、等待一段固定时间        3、根本不等待 定时器值必须为 0    (二)        支持的异常条件:            1、某个套接字的带外数据到达            2、某个已置为分组模式的伪终端存在可从其主端读取的控制状态信息    (三)        要用到的宏函数:            1、void FD_ZERO(fd_set *fdset)            //clear all            2、void FD_SET(int fd,fd_set *fdset)    //turn on            3、void FD_CLR(int fd,fd_set *fdset)    //turn off            4、int  FD_ISSET(int fd,fd_set *fdset)    //test    (四)        该函数返回后,用FD_ISSET来测试描述符,描述符集内任何与未就绪描述符对应的位返回时均清成0.        为此,每次重新调用select函数,都得【再次】把所有所关心的描述符位置一    (五)        返回已就绪的总位数





因此我们将客户端读取函数改成这样:
void str_cli(FILE *fp, int sockfd){    int maxfdpl;    fd_set reset;    char sendling[MAX],receive[MAX];    FD_ZERO(reset);    for(;;)    {        FD_SET(fileno(fp),&reset);        FD_SET(sockfd, &reset);        maxfdlp = max(fileno(fp),sockfd)+1;        Select(maxfdlp, &reset,NULL,NULL,NULL);            if(FD_ISSET(sockfd,&rerset))        {            if(Readline(sockfd,receive,MAX) == 0)                err_quit();            Fputs(buf,stdout);        }        if(FD_ISSET(fileno(fp),&reset))        {            if(Fgets() == NULL)                        return;            Writen(sockfd,sending,strlen(sending));        }    }}




批量输入:
    采用一问一答的方式虽然很符合回射客户端/服务器的要求,但是既然套接字是全双工通信,我们可以将标准输入和标准输出替换为文件,充分利用。
        但是,最后发现输出文件总是小于输入文件(对于回射服务器,他们理应相等)
        问题在于我们对标准输入中的EOF的处理
            str_cli函数就此返回到main函数,而main函数随后终止。然而在这种批量方式下,标准输入中的EOF并不意味着我们同时也完成了从套接字的读入:可能仍有请求在去往服务器的路上,或仍有请求在返回客户端的路上
    
    我们需要的是一种关闭TCP连接其中一半的方法。也就是说,我们想给服务器TCP发送一个FIN,告诉服务器我们已经完成了发送数据,但是仍然保持该套接字描述符打开以便读取



P135 讲述了select 与缓冲区之间的微妙处理关系,但我没怎么看懂


终止网络连接常用close,不过close有两个限制,却可以使用shutdown函数来避免。
    1、close把描述符的引用计数减1,仅在该计数为0时关闭描述符。使用shutdown可以不管引用计数就激发TCP的正常连接终止序列(由FIN开始的4个字节)
    2、close终止读和写两个方向的数据传输。既然TCP是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据要发送给我们。即上面批量输入遇到的问题。
int shutdown(int sockfd, int how)    参数howto:        A、SHUT_RD            关闭连接的读这一半  ----  套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。进程不能再对这样的套接字调用任何读函数,对一个TCP套接字调用该操作后,由该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。。。        B、SHUT_WR            关闭写这一半   -----   对于TCP套接字,这称为半关闭。当前留在套接字发送缓冲区的数据将被发送掉,后跟TCP正常终止序列。不管套接字描述符的引用计数是否为0,写半部分关闭照样执行,不能再对这个套接字调用任何写函数        C、SHUT_RDWR            都关闭



修改后,如下:
void str_cli(FILE *fp, int sockfd){    int maxfdlp, stdineof;    fd_set rset;    int nbyte;    FD_ZERO(&rset);    stdineof = 0;    for(;;)    {        maxfdlp = max(fileno(fp),sockfd)+1;        if(stdinof == 0)            FD_SET(fileno(fp), &rset);        FD_SET(sockfd, &rset);        if(select(maxfdlp,&rset,NULL,NULL,NULL) < 0)        {            perror("select");            exit(1);        }        if(FD_ISSET(sockfd, &rset))        {            if((nbyte=read(sockfd,buf,MAXLEN)) == 0)            {                if(stdineof == 1)                    return;                //normal end                else                {                    //service send FIN to this client unexpectedly                    fprintf(stderr,"Service Terminated Unexpected\n");                    exit(1);                }            }            else if(nbyte < 0)            {                perror("Read Error");                exit(1);            }            if(write(1,buf,nbyte) != nbyte)            {                perror("Write Error");                exit(1);            }        }        if(FD_SET(fileno(fp),&rset))        {            if((nbyte=read(fileno(fp),buf,MAXLEN)) == 0)            {                stdineof = 1;                shutdown(sockfd, SHUT_WR);        //send FIN to service to close half-TCP about write                                    //which means we would not send data anymore.                FD_CLR(fileno(fp),&rset);                continue;            }            else if(nbyte < 0)            {                perror("Read Error");                exit(1);            }            if(written(sockfd,buf,nbyte) != nbyte)            {                perror("Write Error");                exit(1);            }        }    }}




既然客户端用select函数进行了更改,服务器自然也可以:
#include "unp.c"int main(int ac, char *av[]){    struct sockaddr_in serv_addr,clin_addr;    int serv_fd, clin_fd;    fd_set rset, allset;    char table[FD_SETSIZE];                            //用一个队列将所有客户的套接字存储下来    int maxi = -1;                                //对应于拥有最大描述符的数组的索引    int maxfd = -1;                                //最大描述符(select第一个参数要用)    int nready;                                    //select将返回的准备好的描述符个数    int i_loop;    socklen_t clin_len;    char comm_buf[MAXLEN];    int nread, nwrite;    if((serv_fd=socket(AF_INET,SOCK_STREAM,0)) < 0)        oops("socket error");        bzero(&serv_addr, sizeof(serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_port = htons(13000);    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    if(bind(serv_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)        oops("bind error");        if(listen(serv_fd, 10) < 0)        oops("listen error");        maxfd = serv_fd;                                    //当前最大描述符当然是用于监听的套接字    for(i_loop=0; i_loop<FD_SETSIZE; i_loop++)        table[i_loop] = -1;    FD_ZERO(&allset);                FD_SET(serv_fd,&allset);                                //设置一个allset,且在循环中令一个变量等于它,就不需要每次都重置了    for(;;)    {        rset = allset;        nready = select(maxfd+1, &rset, NULL, NULL, NULL);                if(nready == -1)            oops("select error");                if(FD_ISSET(serv_fd,&rset))        {            clin_len = sizeof(clin_addr);            if((clin_fd=accept(serv_fd, (struct sockaddr *)&clin_addr, &clin_len)) < 0)                oops("accept error");            for(i_loop=0; i_loop<FD_SETSIZE; i_loop++)            //挑一个空位给这个新套接字                if(table[i_loop] < 0)                {                    table[i_loop] = clin_fd;                    break;                }            FD_SET(clin_fd,&allset);                        //并将这个新套接字设置进这个组中            if(maxfd < clin_fd)                maxfd = clin_fd;            if(maxi < i_loop)                            //取到最大的下标,用于接下来的循环                maxi = i_loop;            if(--nready == 0)                            //若等于0了,就无需再经历下面的循环                continue;        }        for(i_loop=0; i_loop<=maxi; i_loop++)        {            if(table[i_loop] < 0)                continue;            if(FD_ISSET(table[i_loop], &rset))            {                if((nread=read(table[i_loop], comm_buf, MAXLEN)) > 0)                    if(written(table[i_loop], comm_buf, nread) != nread)                        oops("write error");                if(nread < 0)                    oops("read error")                else if(nread == 0)                //connection closed by client                {                    close(table[i_loop]);                    FD_CLR(table[i_loop],&allset);                    table[i_loop] = -1;                }                                if(maxi == i_loop)                {                    maxfd = -1;                    for(i_loop=0; i_loop<=maxi; i_loop++)                        if(maxfd < table[i_loop])                            maxfd = table[i_loop];                }                if(--nready == 0)            //a better solution                    break;            }        }    }}




//    here I try pselect instead of select//    Given that one global variation "intr_flag" will be changed if signal "SIGINT" occures//    and the global variation will be used/*    sigset_t set,nset,oset;    sigemptyset(&nset);    sigemptyset(&set);    sigaddset(&nset,SIGINT);    sigprocmask(SIG_BLOCK,&nset,&oset);    if(int_flag)        handle_intr();                //handle the signal    if((nready = pslect(.....,&set)) == 0)    {        if(errno == EINTR)            if(intr_flag)                handle_intr();    }*//*关于信号处理的部分,之前在APUE中已经有了一定的概念。在测试intr_flag变量之前,我们阻塞SIGINT,当pselect被调用时,它先以空集代替进程的信号掩码,再检查描述符,并可能进入睡眠;当pselect返回时,进程的信号掩码又被重置为调用pselect之前的值*/







关于 POLL
int poll(struct pollfd *fdarray,long nfds,int timeout);struct pollfd{    int fd;    short events;    short revents;};就TCP和UDP套接字而言,以下条件引起poll返回特定的revent:    1、所有正规TCP数据和所有UDP数据都被认为是普通数据    2、TCP带外数据被认为是优先级数据    3、当TCP连接的读半部关闭时,也被认为是普通数据,随后读操作返回0    4、TCP连接存在错误既可认为是普通数据,也可以是错误。无论如何,读操作返回-1,并设置errno。可用于处理RST或超时等条件    5、在监听套接字上有新的连接可用既可以是普通数据(大多数),也可以是优先数据    6、非阻塞式connect的完成被认为是使相应套接字可写timeout:    1、INFTIM    永远等待    2、       0    立即返回    3、      >0    等待指定数目的毫秒



使用poll改写服务器:
#include "unp.h"#define OPEN_MAX 100#define INFTIM -1int main(int ac, char *av[]){    struct sockaddr_in serv_addr,clin_addr;    struct pollfd client[OPEN_MAX];                    //poll要使用的参数    int serv_fd, clin_fd;    int nready;                                    //select将返回的准备好的描述符个数    int maxi;    int i_loop;    socklen_t clin_len;    char comm_buf[MAXLEN];    int nread, nwrite;    if((serv_fd=socket(AF_INET,SOCK_STREAM,0)) < 0)        oops("socket error");        bzero(&serv_addr, sizeof(serv_addr));    serv_addr.sin_family = AF_INET;    serv_addr.sin_port = htons(13000);    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    if(bind(serv_fd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)        oops("bind error");        if(listen(serv_fd, 10) < 0)        oops("listen error");        client[0].fd = serv_fd;    client[0].events = POLLRDNORM;    for(i_loop=0; i_loop<OPEN_MAX; i_loop++)        client[i_loop].fd = -1;                            //poll会自动忽略fd为-1的参数    maxi = 0;    for(;;)    {        if((nready=poll(client, maxi+1, INFTIM)) == -1)            oops("poll error");        if(client[0].revents & POLLRDNORM)                    //检测是否有普通数据        {            clin_len = sizeof(clin_addr);            if((clin_fd=accept(serv_fd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)                oops("accept error");            for(i_loop=0;i_loop<OPEN_MAX;i_loop)                if(client[i_loop].fd < 0)                {                    client[i_loop].fd = clin_fd;                    client[i_loop].events = POLLRDNORM;                }            if(i_loop == OPEN_MAX)            {                fprintf(stderr,"too many descriptors\n");                exit(1);            }            if(i_loop > maxi)                    maxi = i_loop;            if(--nready <= 0)                continue;        }        for(i_loop=1; i_loop<=maxi; i_loop++)        //0固定为监听用的,之前select从0开始是因为数组元素全是存客户套接字的        {            if(client[i_loop].fd < 0)                continue;            if(client[i_loop].revents & (POLLRDNORM | POLLERR) )            {                if((nread=read(client[i_loop].fd, comm_buf, MAXLEN)) > 0)                    if(written(client[i_loop].fd, comm_buf, nread) != nread)                        oops("write error");                if(nread < 0)                {                    if(errno == ECONNRESET)                    {                        close(client[i_loop].fd);                        client[i_loop].fd = -1;                    }                    else                    {                        perror("read error");                        exit(1);                    }                }                else if(nread == 0)                //connection closed by client                {                    close(client[i_loop].fd);                    client[i_loop].fd = -1;                }            }            if(--nready <= 0)                break;        }    }    return 0;}//这样看下来,其实差别不大,但之前select使用的数组不再需要了,简化了些


0 0