I/O复用之poll服务器

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  such
as  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 128

void 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%了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值