高级IO
APUE第14章讲解了高级IO,主要有:非阻塞IO、IO多路转接技术:(select、poll),下面就简要就上述几个方面讲一讲个人观点。
非阻塞IO
顾名思义,对数据不进行阻塞(你有数据我就处理,没有我就返回,不去花时间等数据到来)。相对于阻塞IO模型,时间效率要好,这对于开发大规模并发服务器无疑是画龙点睛(这成语是不是有点过了^-^,楼主高考语文75分^-^),但是你这个IO又调皮了,你不光能读,还能写。万一数据来了,你让我读,还是让我写?元芳,你猜!于是第一位智者带着它的专利来了:select(applause^-^)
select
select有专门的读事件集合,写事件集合。万一调皮的IO有数据了,我就可以知道对应于哪一个事件,是不是相比傻瓜的”你猜“IO要好很多。select一个不好的地方就是:万一IO亲朋好又多了,祖宗十八代都加入到这个大家庭集合中了,select开销不起啊,我只能容纳maxfd个,只能忍痛割爱,多余的我就不接受了。---------select有最大描述符限制。一般是1024。有一天有3个干儿子发来贺电(IO有可读写数据),亲爹select就在事件集合中找啊找。尼玛,岂不是要线性扫描一遍(好歹将事件集合设计成平衡二叉树之类的查找利器。。),爹找的好辛苦,太累了。--------线性扫描太耗时(究其原因:非活跃IO也在扫描序列中)
poll
这一年,亲爹select出去鬼混了,养了个儿子poll。熟话说,长江后浪推前浪,儿子poll比爹聪明了一点,把两个事件集合打了个包(有了一个pollfd数组)。哎,换汤不换药啊!优缺点与select差不多,不在赘述。
epoll
终于到了改革开放的时代,小伙伴们集齐了七龙珠,召唤出神龙,带来了epoll,该高级IO家族带来了福音。
epoll三大法宝:epoll_create(创建)、epoll_ctl(一般用来注册事件)、epoll_wait(等待事件,而且是活跃的啊)
之前也看过不少关于epoll的博客文章和相关资料,都说epoll的数据结构设计的好,我认为最主要的是它有一个指向事件描述符的指针,指针,就是一指,一针见血。快哉!!!
(以上细节之处未说明!)
下面附一段国外网站关于epoll使用示例的源码以及个人的中文注释,只能说,epoll很好很强大,一直想写一个epoll服务器应用。。欢迎四面八方的建议^-^(当然细节决定成败,细节要设计好)
代码来源:https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <unistd.h> #include <fcntl.h> #include <sys/epoll.h> #include <errno.h>
#define MAXEVENTS 64
static int make_socket_non_blocking (int sfd) { int flags, s;
flags = fcntl (sfd, F_GETFL, 0);//获取描述符标志 if (flags == -1) { perror ("fcntl"); return -1; }
flags |= O_NONBLOCK;//非阻塞 s = fcntl (sfd, F_SETFL, flags); if (s == -1) { perror ("fcntl"); return -1; }
return 0; }
static int create_and_bind (char *port) { struct addrinfo hints; struct addrinfo *result, *rp; int s, sfd;
memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; /* Return IPv4 and IPv6 choices */ hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */ hints.ai_flags = AI_PASSIVE; /* All interfaces */
s = getaddrinfo (NULL, port, &hints, &result);//hints是一个用于地址过滤的模板 if (s != 0) { fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));//此处不能用strerror或perror来生成错误信息,这里使用gai_strerror(int error) return -1; }
for (rp = result; rp != NULL; rp = rp->ai_next) //addrinfo是一个链表 { sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (sfd == -1) continue;
s = bind (sfd, rp->ai_addr, rp->ai_addrlen); if (s == 0) { /* We managed to bind successfully! */ break; }
close (sfd); }
if (rp == NULL) { fprintf (stderr, "Could not bind\n"); return -1; }
freeaddrinfo (result);
return sfd; }
int main (int argc, char *argv[]) { int sfd, s; int efd; struct epoll_event event; struct epoll_event *events;
if (argc != 2) { fprintf (stderr, "Usage: %s [port]\n", argv[0]); exit (EXIT_FAILURE); }
sfd = create_and_bind (argv[1]); if (sfd == -1) abort ();
s = make_socket_non_blocking (sfd); if (s == -1) abort ();
s = listen (sfd, SOMAXCONN); if (s == -1) { perror ("listen"); abort (); }
efd = epoll_create1 (0); if (efd == -1) { perror ("epoll_create"); abort (); }
event.data.fd = sfd; event.events = EPOLLIN | EPOLLET;//读事件与ET模式 s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);//注册事件 if (s == -1) { perror ("epoll_ctl"); abort (); }
/* Buffer where events are returned */ events = calloc (MAXEVENTS, sizeof event);//calloc与malloc的区别在于calloc分配后对内存空间数据做了初始化(0),而malloc只管分配空间,而内存为垃圾数据。
/* The event loop */ while (1) { int n, i;
n = epoll_wait (efd, events, MAXEVENTS, -1); for (i = 0; i < n; i++) { if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) { /* An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */ fprintf (stderr, "epoll error\n"); close (events[i].data.fd); continue; }
else if (sfd == events[i].data.fd)//监听端口有事件^-^ { /* We have a notification on the listening socket, which means one or more incoming connections. */ while (1) { struct sockaddr in_addr; socklen_t in_len; int infd; char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr; infd = accept (sfd, &in_addr, &in_len);//接受请求(来吧,data) if (infd == -1) { if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { /* We have processed all incoming connections. */ break; } else { perror ("accept"); break; } }
s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV); if (s == 0) { printf("Accepted connection on descriptor %d " "(host=%s, port=%s)\n", infd, hbuf, sbuf); } //不断的注册事件
/* Make the incoming socket non-blocking and add it to the list of fds to monitor. */ s = make_socket_non_blocking (infd); if (s == -1) abort ();
event.data.fd = infd; event.events = EPOLLIN | EPOLLET; s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event); if (s == -1) { perror ("epoll_ctl"); abort (); } } continue; } else { /* We have data on the fd waiting to be read. Read and display it. We must read whatever data is available completely, as we are running in edge-triggered mode and won't get a notification again for the same data. */ int done = 0;
while (1) { ssize_t count; char buf[512];
count = read (events[i].data.fd, buf, sizeof buf); if (count == -1) { /* If errno == EAGAIN, that means we have read all data. So go back to the main loop. */ if (errno != EAGAIN) { perror ("read"); done = 1; } break; } else if (count == 0) { /* End of file. The remote has closed the connection. */ done = 1; break; }
/* Write the buffer to standard output */ //将接收到的数据写到标准输出 s = write (1, buf, count); if (s == -1) { perror ("write"); abort (); } }
if (done) { printf ("Closed connection on descriptor %d\n", events[i].data.fd);
/* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */ close (events[i].data.fd); } } } }
free (events);
close (sfd);
return EXIT_SUCCESS;//上面的EXIT_FAILURE 与此处的EXIT_SUCCESS是方便程序移植,我们平常都写return 0;return -1啊 } /*There is still space to explore ^-^,just fight*/