epoll与fork
? ? ? ? ?使用epoll時,如果在調(diào)用epoll_create之后,調(diào)用了fork創(chuàng)建子進(jìn)程,那么父子進(jìn)程雖然有各自epoll實(shí)例的副本,但是在內(nèi)核中,它們引用的是同一個實(shí)例。子進(jìn)程向自己的epoll實(shí)例添加、修改和刪除文件描述符時,是可以影響到父進(jìn)程的epoll_wait的。所以會發(fā)生意想不到的問題,分情況看一下:
?
? ? ? ? ?1:向子進(jìn)程中的epoll實(shí)例添加描述符,描述符事件觸發(fā)后,也會影響到父進(jìn)程的epoll實(shí)例,代碼如下:
#define MAXEVENTS 20int listenfd; struct epoll_event events[MAXEVENTS];int epfd = epoll_create(MAXEVENTS);if((pid = fork()) < 0) return;if(pid == 0) {listenfd = socketfd();struct epoll_event lisevent;lisevent.events = EPOLLIN;lisevent.data.fd = listenfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &lisevent); }while(1) {res = epoll_wait(epfd, events, MAXEVENTS, -1);for(i = 0; i < res; i++){connectfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, (socklen_t *)&addrlen);if(connectfd < 0){perror("accept error");continue;}printf("connect from %s\n", inet_ntop(AF_INET, &(clientaddr.sin_addr), addrbuf, 20));close(connectfd);} }
???????? 上述代碼中,在fork之前創(chuàng)建epoll實(shí)例,然后在子進(jìn)程中,創(chuàng)建監(jiān)聽socket,并且加入到epoll實(shí)例中。父子進(jìn)程同時在epoll實(shí)例上調(diào)用epoll_wait等待連接的到來。如果此時客戶端建鏈,則打印如下:
accept error: Bad file descriptor accept error: Bad file descriptor …… accept error: Bad file descriptor connect from 127.0.0.1? ? ? ? ?也就是說,連接到來時,盡管是在子進(jìn)程中創(chuàng)建的監(jiān)聽套接字,加入到子進(jìn)程中的epoll實(shí)例中。但是父子進(jìn)程中epoll實(shí)例都會收到觸發(fā)的事件,二者的epoll_wait都會停止阻塞,開始調(diào)用accept。
???????? 父進(jìn)程調(diào)用accept失敗,打印出Bad file descriptor錯誤,是因?yàn)樵诟高M(jìn)程中,根本沒有監(jiān)聽套接字。所以,只要子進(jìn)程沒有調(diào)用accept成功,則該連接事件就會一直觸發(fā),從而父進(jìn)程一直打印accept錯誤信息,直到子進(jìn)程調(diào)用accept成功,打印出connect from 127.0.0.1。
?
???????? 2:在fork之前,創(chuàng)建epoll實(shí)例、監(jiān)聽套接字listenfd,并將listenfd加入到epoll實(shí)例中。然后父子進(jìn)程一起等待事件的觸發(fā),代碼如下:
#define MAXEVENTS 20int listenfd; struct epoll_event events[MAXEVENTS];int epfd = epoll_create(MAXEVENTS);listenfd = socketfd(); struct epoll_event lisevent; lisevent.events = EPOLLIN; lisevent.data.fd = listenfd;res = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &lisevent);if((pid = fork()) < 0) return;while(1) {res = epoll_wait(epfd, events, MAXEVENTS, -1);for(i = 0; i < res; i++){printf("[%s]before accept\n", pid?"father":"child");connectfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, (socklen_t *)&addrlen);printf("[%s]after accept\n", pid?"father":"child");if(connectfd < 0){perror("accept error");continue;}printf("connect from %s\n", inet_ntop(AF_INET, &(clientaddr.sin_addr), addrbuf, 20));close(connectfd);} }
???????? 上述代碼在fork之前創(chuàng)建好epoll實(shí)例和監(jiān)聽套接字,然后調(diào)用fork,父子進(jìn)程在各自的epoll實(shí)例上等待事件的發(fā)生,如果此時到來了一個客戶端連接,則打印如下:
[father]before accept [father]after accept connect from 127.0.0.1 [child]before accept???????? 可見,到來的連接觸發(fā)的事件,會同時通告給被父子進(jìn)程的epoll實(shí)例。父進(jìn)程調(diào)用accept得到該連接,而子進(jìn)程調(diào)用accept時,連接已經(jīng)被取走了,所以子進(jìn)程中的accept阻塞。
?
???????? 總結(jié):在fork之前創(chuàng)建的epoll實(shí)例,盡管分別處于父子進(jìn)程各自的空間中,但是它們在底層引用的同一個內(nèi)核結(jié)構(gòu)。所以,當(dāng)事件發(fā)生時,會同時通告給父子進(jìn)程中的epoll實(shí)例。這其實(shí)算是epoll設(shè)計上的一個缺陷,應(yīng)該避免在fork之前創(chuàng)建epoll實(shí)例,或者在fork之后,關(guān)閉原epoll實(shí)例,重新創(chuàng)建本進(jìn)程的epoll實(shí)例。這一點(diǎn)在libev的文檔中有所提及:
? ? ? ? The biggest issue is fork races, however - if a program forks then?both parent and child process have to recreate the epoll set, which can take considerable time (one syscall per file descriptor) and is of course hard to detect.
?
?
參考:http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
轉(zhuǎn)載于:https://www.cnblogs.com/gqtcgq/p/7247110.html
總結(jié)
以上是生活随笔為你收集整理的epoll与fork的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 对某人失望心寒的说说 无法诉说的委屈的句
- 下一篇: 图形学 网址暂存