1、poll

函数原型:

#include <poll.h>

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

I/O多路转接之poll,epoll_poll

参数说明:

fds:是一个struct pollfd结构类型的数组,用于存放需要检测其状态的Socket描述符;每当调用这个函数之后,系统不会清空这个数组,操作起来比较方便;特别是对于socket连接比较多的情况下,在一定程度上可以提高处理的效率;这一点与select()函数不同,调用select()函数之后,select()函数会清空它所检测的socket描述符集合,导致每次调用select()之前都必须把socket描述符重新加入到待检测的集合中;因此,select()函数适合于只检测一个socket描述符的情况,而poll()函数适合于大量socket描述符的情况;

nfds:nfds_t类型的参数,用于标记数组fds中的结构体元素的总数量;

timeout:是poll函数调用阻塞的时间,单位:毫秒;

如果timeout==0,那么poll() 函数立即返回而不阻塞,如果timeout==INFTIM(宏 表示为-1),那么poll() 函数会一直阻塞下去,直到所检测的socket描述符上的感兴趣的事件发生是才返回


函数返回值:

    poll函数的返回值与select函数的返回值一样。

    若返回0:表示超时

    若为-1:错误

    若>0:返回就绪事件的个数


使用poll检测输入输出:

#include <stdio.h>
#include <poll.h>

int main()
{
	struct pollfd pfd[1];
	int len = 1;
	pfd[0].fd = 0;
	pfd[0].events = POLLIN;
	pfd[0].revents = 0;
	int done = 0;
	while(!done)
	{
		switch(poll(pfd,1,-1))
		{
			case 0:
				printf("timeout");
				break;
			case -1:
				
				perror("select");
				break;
			default:
				{
				    char buf[1024];
			            if(pfd[0].revents & POLLIN)
				   {
			               ssize_t _s = read(pfd[0].fd,buf,sizeof(buf)-1);
				       if(_s > 0)
				      {
					   buf[_s] = '\0';
					   printf("echo:%s\n",buf);
				       }
				   }
				}
				break;
		}
	}
}

I/O多路转接之poll,epoll_poll_02

poll函数的缺点:

(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

(2)与select一样,poll返回后,需要轮询poolfd来获取就绪的描述符。


poll函数的优点:

(1)poll函数不要求计算最大文件描述符的大小

(2)poll函数在应付大数目的文件描述符的时候速度更快,相比于select

(3)它没有最大连接数的限制,原因是它基于链表来存储的。



2. epoll

   epoll只有epoll_create,epoll_ctl,epoll_wait3个系统调用。

 (1)int epoll_create(int size)

  创建一个epoll的句柄。size参数是可以被忽略的。当创建好epoll句柄后,它就会占用一个fd值。所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

 (2)int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event)

  该函数用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
   epfd:由 epoll_create 生成的epoll专用的文件描述符;
   op:要进行的操作,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修改、   EPOLL_CTL_DEL 删除;
   fd:关联的文件描述符;
   event:告诉内核需要监听的事件;
   如果调用成功则返回0,不成功则返回-1。

 (3)int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout)

   参数:
   epfd:由epoll_create 生成的epoll专用的文件描述符;
   epoll_event:用于回传代处理事件的数组;
   maxevents:每次能处理的事件数;
   timeout:等待I/O事件发生的超时值;


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
void usage(const char* _proc)
{
    printf("Usage:%s [ip] [port]\n",_proc);
}
void set_nonblock(int fd)
{
    int fl = fcntl(fd,F_GETFL); //读取文件状态标志
    fcntl(fd,F_SETFL,fl|O_NONBLOCK); //设置文件状态标志
}
int startup(const char* _ip,int _port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0); //创建套接字
    if(sock < 0)
    {
        perror("socket");
        exit(2);
    }
    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    //设置处于TIME_WAIT时,端口号还可以用
    struct sockaddr_in local; //设置本地local
    local.sin_family = AF_INET;
    local.sin_port = htons(_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,5) < 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],atoi(argv[2]));   //创建监听套接字
    int epfd = epoll_create(256);
      //创建一个epoll的句柄,当创建好句柄后,会占用一个fd值
    if(epfd < 0)
    {
        perror("epoll_create");
        exit(5);
    }
    struct epoll_event _ev;
    _ev.events = EPOLLIN; //设置要处理的事件类型
    _ev.data.fd = listen_sock;  //设置与要处理的事件相关的文件描述符
    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&_ev);
   //注册函数,注册监听事件的类型,
    struct epoll_event revs[64];
    int timeout = -1;  
    int num = 0;
    int done = 0;
    while(!done)
    {
        switch(num = epoll_wait(epfd,revs,64,timeout))
        //revs从内核得到的事件集合,返回需要处理的事件数目
        {
            case 0:
                printf("timeout\n");
                break;
            case -1:
                perror("epoll_wait");
                break;
            default:
            {
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                    int i = 0;
                    for(;i<num;i++)
                    {
                        int rsock = revs[i].data.fd;
                        if(rsock == listen_sock && \
                                revs[i].events & EPOLLIN)
                        {
                            int new_fd = accept(listen_sock,\
                                    (struct sockaddr*)&peer,\
                                    &len);
                            if(new_fd > 0)
                            {
                                printf("get a new client :%s:%d\n",\
                                        inet_ntoa(peer.sin_addr),\
                                        ntohs(peer.sin_port));
                                 set_nonblock(new_fd);
                                _ev.events = EPOLLIN | EPOLLET;
                                _ev.data.fd = new_fd;
                                epoll_ctl(epfd,EPOLL_CTL_ADD,\
                                        new_fd,&_ev);
                            }
                        }
                        else
                        {
                            if(revs[i].events & EPOLLIN)
                            {
                                char buf[1024];
                                ssize_t _s = read(rsock,buf,sizeof(buf)-1);
                                if(_s > 0)
                                {
                                    buf[_s] = '\0';
                                    printf("client:%s\n",buf);
                                    _ev.events = EPOLLOUT | EPOLLET;
                                    _ev.data.fd = rsock;
                                    epoll_ctl(epfd,EPOLL_CTL_MOD,\
                                        rsock,&_ev);
                                }
                                else if(_s == 0)
                                {
                                    printf("client %d close...\n",rsock);
                                    epoll_ctl(epfd,EPOLL_CTL_DEL,\
                                        rsock,NULL);
                                    close(rsock);
                                }
                                else
                                {
                                    perror("read");
                                }
                            }
                            else if(revs[i].events & EPOLLOUT)
                            {
                                const char* msg = "hello\n";
                                //const char* msg = \
    "HTTP/1.0 200 OK\r\n\r\n<html><h1>hello word!+_+||</h1><html>\r\n";
                                write(rsock,msg,strlen(msg));
                                epoll_ctl(epfd,EPOLL_CTL_DEL,\
                                        rsock,NULL);
                                close(rsock);
                            }
                            else
                            {}
                        }
                    }
                }
                break;
        }
    }
    return 0;
}

测试结果:

I/O多路转接之poll,epoll_poll_03

epoll的优点:

(1)支持一个进程打开大数目的socket描述符

(2)IO效率不随socket描述符数目的增加而线性下降