select/poll/epoll区别及实例,IO模型区别

UNIX提供了五种IO模型,分别是阻塞式IO、非阻塞式IO、IO复用、信号驱动式IO、异步IO五种,以UDP的recvfrom为例,五种IO比较如下:
select/poll/epoll区别及实例,IO模型区别
select、poll和epoll是IO复用的三个系统函数,其中select与poll在效率上是等同的,只不过poll理论监测描述符数量大于select,epoll的效率高于poll和select,这里说的效率比较是建立在监听大量描述符的情况下,小规模描述符三者效率比较无意义,如果只有几个感兴趣的描述符用select和poll即可。

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);

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数:
nfds:需要监听的最大描述符数量,通常是感兴趣的描述符最大值+1;
fd_set *readfds, fd_set *writefds, fd_set *exceptfds:分别对应可读事件、可写事件、异常事件,fd_set结构体无需理解,直接声明后配合FD_SET/FD_CLR/FD_ISSET/FD_ZERO使用即可;
struct timeval *timeout:等待时间,如果为NULL表示阻塞,如果为0表示不等待直接返回,其他值表示实际等待时间,struct timeval结构体内容如下:

struct timeval
{
	long tv_sec;//秒
	long tv_usec;//微秒
}

实例如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
//用法1:
int main(void)
{
           fd_set rfds;
           struct timeval tv;
           int retval;
           /* Watch stdin (fd 0) to see when it has input. */
           FD_ZERO(&rfds);
           FD_SET(0, &rfds);
           /* Wait up to five seconds. */
           tv.tv_sec = 5;
           tv.tv_usec = 0;
           retval = select(1, &rfds, NULL, NULL, &tv);
           /* Don't rely on the value of tv now! */

           if (retval == -1)
               perror("select()");
           else if (retval)
               printf("Data is available now.\n");
               /* FD_ISSET(0, &rfds) will be true. */
           else
               printf("No data within five seconds.\n");
           exit(EXIT_SUCCESS);
}
//用法2:
int main(void)
{
	int iRet = -1;
	int fd_max = 100;//自定义最大描述符值
	struct timeval tm = {0};
	tm.tv_sec=1;
	
	fd_set readfds,writefds,exceptfds;
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);
	int i;
	for(i=3;i<(fd_max +1);i++)
	{
		FD_SET(i,&readfds);
		FD_SET(i,&writefds);
		FD_SET(i,&exceptfds);
	}

	iRet = select(fd_max+1,&readfds, &writefds, &exceptfds, &tm);
	if(iRet < 0)
	{
		printf("select ret is %d      errno:%d\n",iRet,errno);
	}
	else if(iRet == 0)
	{
		printf("wait over time\n");
	}
	else
	{
		if(iRet  = FD_ISSET(fd_max,&readfds))//检测关注的描述符
		{
			
		}
		if(FD_ISSET(fd_max,&writefds))//检测关注的描述符
		{
			
		}
		if(FD_ISSET(fd_max,&exceptfds))//检测关注的描述符
		{
			
		}
	}
	return 0;
}

poll:

 	#include <poll.h>
	int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:
fds:关注的句柄事件集合,pollfd 结构体如下:

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

关于events和revents,events是用来设置关注的事件,revents是用来返回触发的事件,一般只会用到三个:POLLIN/POLLRDNORM(可读)、POLLOUT/POLLWRNORM(可写)、POLLERR(错误),定义如下:

			  POLLIN:可读;
              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:可写;
              POLLRDHUP :HUP (since Linux 2.6.17)
                     Stream socket peer closed connection, or shut down  writ‐
                     ing  half  of  connection.   The _GNU_SOURCE feature test
                     macro must be defined (before including any header files)
                     in order to obtain this definition.
              POLLERR:错误;
              POLLHUP:Hang up (output only).
              POLLNVAL:Invalid request: fd not open (output only).
               POLLRDNORM:可读,==POLLIN.
              POLLRDBAND: Priority band data  can  be  read  (generally  unused  on  Linux).
              POLLWRNORM:可写 ==POLLOUT.
              POLLWRBAND: Priority data may be written.
			

nfds用来标识检测的描述符数量;
timeout表示超时时间,单位是毫秒;
实例如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>

int main(void)
{
	//制作一个套接字,省略过程
	int iSockFd;//虚拟定义
	
	int iBufLen = 0;
	char *pMsgBuf = (char *)malloc(1<<20);
    int iRet = -1;
    int iHaveRecv = 0;
    int iTmpErrno;
    struct pollfd stPollfd = {iSockFd, POLLIN, 0};

    iRet = poll(&stPollfd, 1, 1000);
    iTmpErrno = errno;
    if (0 == iRet)
    {
        /* 超时 */
    }
    else if (-1 == iRet)
    {
        /* 出错了 */
        printf("poll error: %s\n", strerror(iTmpErrno) );
		return -1;
    }
	else
	{
		//处理过程
		while (1)
		{
			iHaveRecv = recv(iSockFd, pMsgBuf, iBufLen, 0);
			iTmpErrno = errno;
			if (0 > iHaveRecv)
			{
				if ((EINTR == iTmpErrno) || (EAGAIN == iTmpErrno))/* 被中断或可再次尝试 */
				{
					printf("[EINTR:%d][EAGAIN:%d][errno:%d] -> %s\n", EINTR, EAGAIN, iTmpErrno, strerror(iTmpErrno));
					usleep(50000); /* 延时 50 ms */
					continue;
				}
				else if (ECONNREFUSED == iTmpErrno)
				{
					printf("Connect refuse!!!\n");
					return -1;
				}
				else
				{
					printf("[EINTR:%d][EAGAIN:%d][errno:%d] -> %s\n", EINTR, EAGAIN, iTmpErrno, strerror(iTmpErrno));
					break;
				}
			}
			else if (0 == iHaveRecv)
			{
				/* 对端关闭了Socket */
				printf("Recv error: [%d, %s] [SockFd:%d]\n", iTmpErrno, strerror(iTmpErrno), iSockFd);
				return -1;
			}
			else
			{
				break;
			}
			usleep(10000);
		}
	}

    return 0;
}

epoll:
epoll需要用到三个函数,分别是:

 int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大


 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//epoll的事件注册函数
参数:
epfd:epoll_create()的返回值
op:动作,用三个宏来表示:EPOLL_CTL_ADD:添加新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;
fd:感兴趣的文件描述符;
event:关注的事件,
struct epoll_event结构如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;
struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};
events:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);//等待事件的产生
 参数:
 epfd:epoll_create()的返回值;
 event:关注的事件,结构体内容参上;
 maxevents:最大事件数量;
 timeout:超时时间,单位为毫秒;

实例如下:

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>

#define MAXLINE 10

int main(int argc, char *argv[])
{
    int efd, i;
    int pfd[2];
    pid_t pid;
    char buf[MAXLINE], ch = 'a';
    pipe(pfd);

    pid = fork();
    if (pid == 0) {
        close(pfd[0]);
        while (1) {
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            write(pfd[1], buf, sizeof(buf));
            sleep(5);
        }
        close(pfd[1]);
    }
    else if (pid > 0) {
        struct epoll_event event;
        struct epoll_event resevent[10]; //epoll_wait就绪返回event
        int res, len;
        close(pfd[1]);
        efd = epoll_create(10);
        /* ET 边沿触发 ,默认是水平触发 */
        event.events = EPOLLIN | EPOLLET;
        //event.events = EPOLLIN; 
        event.data.fd = pfd[0];
        epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
        while (1) {
            res = epoll_wait(efd, resevent, 10, -1);
            printf("res %d\n", res);
            if (resevent[0].data.fd == pfd[0]) {
                len = read(pfd[0], buf, MAXLINE/2);
                write(STDOUT_FILENO, buf, len);
            }
        }
        close(pfd[0]);
        close(efd);
    }
    else {
        perror("fork");
        exit(-1);
    }

    return 0;
}
上一篇:网络IO模型_01


下一篇:服务端学习路线(听别人说的,记录以激励自己)