UNIX提供了五种IO模型,分别是阻塞式IO、非阻塞式IO、IO复用、信号驱动式IO、异步IO五种,以UDP的recvfrom为例,五种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;
}