I/O复用之poll服务器

来源:互联网 发布:网络犯罪调查分集剧情 编辑:程序博客网 时间:2024/06/14 00:47

github代码:

https://github.com/NICK-DUAN/Three-U/tree/master/poll_server

代码编写

  poll服务器的编写上,就不能直接在代码上做文章了,需要先了解一下poll函数中的几个API和参数。
  

  int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  
  先说返回值,返回值以及timeout这个参数和select的几乎一样,timeout使用单位的是毫秒,表示当前服务器对一个请求最长等待多长时间,nfds表示监听事件集合的大小,定义如下:
  

typedef unsigned long int nfds_t

  
  至于struct pollfd *fds,这个才是poll的核心所在,系统的定义为:
  

           struct pollfd {               int   fd;         /* file descriptor */               short events;     /* requested events */               short revents;    /* returned events */           };

  
  fd表示的当然就是每个文件描述符,events和revents是一系列的位表示法,events表示的是,我们需要监听同一结构体的fd的什么事件,revents由内核修改,表示的是当前结构体中的fd上究竟发生了事件。其中,events和revents可以表示的事件如下所示:
 

    POLLIN     There is data to read.    POLLPRI    There is urgent data to read (e.g., out-of-band data on TCP socket; pseudoterminal master in  packet                     mode has seen state change in slave).    POLLOUT    Writing  is  now  possible,  though a write larger that the available space in a socket or pipe will                     still block (unless O_NONBLOCK is set).    POLLRDHUP  tream socket peer closed connection, or shut down writing half of connection.  The _GNU_SOURCE fea‐ture  test macro must be defined            POLLERR    Error condition (only returned in revents; ignored in events).    POLLHUP    Hang up (only returned in revents; ignored in events).  Note that when reading from a  channel  suchas  a pipe or a stream socket, this event merely indicates that the peer closed its end of the chan‐el.  Subsequent reads from the channel will return 0 (end of file) only after all outstanding  data in the channel has been consumed.    POLLNVAL   Invalid request: fd not open (only returned in revents; ignored in events).    POLLRDNORM Equivalent to POLLIN.    POLLRDBAND Priority band data can be read (generally unused on Linux).    POLLWRNORM Equivalent to POLLOUT.    POLLWRBAND Priority data may be written.

对应到中文就是:
这里写图片描述这里写图片描述

  所以我们在编写代码时只需要设置一个struct pollfd的数组即可,这个数组可以告诉系统我们需要监听那个套接字,我们需要监听这个套接字的什么事件,而这个套接字上又具体发生了什么事件,而不用像select那样,每次发生事件之后,设定一个文件描述符到全部数据中,等待下一次的遍历发现并处理,并且在使用完之后重置这个文件描述符,这都是比较耗费时间的。

#include <stdio.h>#include <stdlib.h>#include <sys/poll.h>#include <string.h>#include <sys/socket.h>#include <sys/types.h>#include <arpa/inet.h>#include <netinet/in.h>#define SIZE 128void usage(char* str){    printf("%s [local_ip] [local_port]\n",str);}int startup(const char* ip,const char* port){    int sock=socket(AF_INET,SOCK_STREAM,0);    if(sock<0)    {        perror("sockek");        exit(2);    }    struct sockaddr_in local;    local.sin_family=AF_INET;    local.sin_port=htons(atoi(port));    local.sin_addr.s_addr=inet_addr(ip);    if(bind (sock,(struct sockaddr*)&local,sizeof(local))<0 )    {        perror("bind");        exit(3);    }    if(listen(sock,10)<0)    {        perror("listen");        exit(4);    }    return sock;}int main(int argc,char* argv[]){    if(argc!=3)    {        usage(argv[0]);        exit(1);    }    int listen_sock=startup(argv[1],argv[2]);    int i=0;    int max=0;    int timeout=3000;    struct pollfd readfds[SIZE];    for(i=0;i<SIZE;i++){        readfds[i].fd=-1;        readfds[i].events=0;        readfds[i].revents=0;    }

依旧是初始化,需要将readfds的套接字,监听与发生事件均初始化,否则在之后的代码编程中会有不必要的错误发生。

    readfds[0].fd=listen_sock;    readfds[0].events = POLLIN;

并且将监听套接字放在第一位,保证监听套接字的存在,这样才能在进入后判断是否一个已经到来。

    while(1){        int ret = poll(readfds,SIZE,timeout);        switch(ret){            case -1:                perror("poll");                break;            case 0:                printf("timeout...\n");                break;            default:{                for(i=0;i<SIZE;i++){                    if(i==0 && ( (readfds[i].revents) & POLLIN)){//listen_sock ready

此处使用revents的原因时,在poll函数时使用的时events表示需要监听当前套接字的事件,当poll被成功触发,就将当前套接字上实际发生了什么事件写在了revents上,所以此处的判定使用revents。表示监听套接字上发生可读事件,意味着有连接到来。

                        struct sockaddr_in client;                        socklen_t len=sizeof(client);                        int new_sock=accept(readfds[i].fd,(struct sockaddr*)&client,&len);                        if(new_sock<0){                            perror("accept");                            break;                        }                        printf("get a client:[%s__%d]\n",inet_ntoa(client.sin_addr),htons(client.sin_port));                        int j=1;                        for(;j<SIZE;j++){                            if(readfds[j].fd==-1){                                readfds[j].fd=new_sock;                                readfds[j].events=POLLIN;                                break;                            }                        }if(j==SIZE){                            printf("connect full...\n");                            close(new_sock);                        }

这些代码同select相似,也是在找到一个合适的位置来表示当前的文件描述符。

                    }else if(i!=0 && ( (readfds[i].revents) & POLLIN)){//client ready                        char buff[1024];                        ssize_t s=read(readfds[i].fd,buff,sizeof(buff)-1);                        if(s>0){                            buff[s]=0;                            printf("client say# %s",buff);                            write(readfds[i].fd,buff,sizeof(buff)-1);                        }else if(s==0){                            printf("client quit...\n");                            close(readfds[i].fd);                            readfds[i].fd=-1;                        }                        else{                            perror("read");                        }                    }//end of elif                }//end of for            }//end of default        }//end of switch     }//end of while    return 0;}

  其实,完完全全的写一次select代码,再将poll中的struct pollfd搞明白,以及为什么使用的是revents,那么poll服务器你已经懂了70%了。

原创粉丝点击