• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            那誰的技術博客

            感興趣領域:高性能服務器編程,存儲,算法,Linux內核
            隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
            數據加載中……

            多進程服務器中,epoll的創建應該在創建子進程之后

            看我的測試代碼,似乎應該是在創建子進程之后創建epoll的fd,否則程序將會有問題,試將代碼中兩個CreateWorker函數的調用位置分別調用,一個在創建epoll fd之前,一個在之后,在調用在創建之前的代碼會出問題,在我的機器上(linux內核2.6.26)表現的癥狀就是所有進程的epoll_wait函數返回0, 而客戶端似乎被阻塞了:

            服務器端:
            #include <iostream>
            #include 
            <sys/socket.h>
            #include 
            <sys/epoll.h>
            #include 
            <netinet/in.h>
            #include 
            <arpa/inet.h>
            #include 
            <fcntl.h>
            #include 
            <unistd.h>
            #include 
            <stdio.h>
            #include 
            <errno.h>
            #include 
            <sys/types.h>
            #include 
            <sys/wait.h>

            using namespace std;

            #define MAXLINE 5
            #define OPEN_MAX 100
            #define LISTENQ 20
            #define SERV_PORT 5000
            #define INFTIM 1000

            typedef 
            struct task_t
            {
                
            int fd;
                
            char buffer[100];
                
            int n;
            }task_t;

            int CreateWorker(int nWorker)
            {
                
            if (0 < nWorker)
                {
                    
            bool bIsChild;
                    pid_t nPid;

                    
            while (!bIsChild)
                    {
                        
            if (0 < nWorker)
                        {
                            nPid 
            = ::fork();
                            
            if (nPid > 0)
                            {
                                bIsChild 
            = false;
                                
            --nWorker;
                            }
                            
            else if (0 == nPid)
                            {
                                bIsChild 
            = true;
                                printf(
            "create worker %d success!\n", ::getpid());
                            }
                            
            else
                            {
                                printf(
            "fork error: %s\n", ::strerror(errno));
                                
            return -1;
                            }
                        }
                        
            else 
                        {
                            
            int nStatus;
                            
            if (-1 == ::wait(&nStatus))
                            {
                                
            ++nWorker;
                            }
                        }
                    }
                }

                
            return 0;
            }

            void setnonblocking(int sock)
            {
                
            int opts;
                opts
            =fcntl(sock,F_GETFL);
                
            if(opts<0)
                {
                    perror(
            "fcntl(sock,GETFL)");
                    exit(
            1);
                }
                opts 
            = opts|O_NONBLOCK;
                
            if(fcntl(sock,F_SETFL,opts)<0)
                {
                    perror(
            "fcntl(sock,SETFL,opts)");
                    exit(
            1);
                }   
            }

            int main()
            {
                
            int i, maxi, listenfd, connfd, sockfd,epfd,nfds;
                ssize_t n;
                
            char line[MAXLINE];
                socklen_t clilen;
                
            struct epoll_event ev,events[20];

                
            struct sockaddr_in clientaddr;
                
            struct sockaddr_in serveraddr;
                listenfd 
            = socket(AF_INET, SOCK_STREAM, 0);
                   bzero(
            &serveraddr, sizeof(serveraddr));
                serveraddr.sin_family 
            = AF_INET;
                
            char *local_addr="127.0.0.1";
                inet_aton(local_addr,
            &(serveraddr.sin_addr));//htons(SERV_PORT);
                serveraddr.sin_port=htons(SERV_PORT);
                  
            // 地址重用
                int nOptVal = 1;
                socklen_t nOptLen 
            = sizeof(int);
                
            if (-1 == ::setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &nOptVal, nOptLen))
                {
                    
            return -1;
                }    
                setnonblocking(listenfd);
                bind(listenfd,(sockaddr 
            *)&serveraddr, sizeof(serveraddr));
                listen(listenfd, LISTENQ);    
                
                CreateWorker(
            5);
                
                
            //把socket設置為非阻塞方式
                
                
            //生成用于處理accept的epoll專用的文件描述符
                epfd=epoll_create(256);    
                
            //設置與要處理的事件相關的文件描述符
                ev.data.fd=listenfd;
                
            //設置要處理的事件類型
                ev.events=EPOLLIN|EPOLLET;
                
            //ev.events=EPOLLIN;
                
            //注冊epoll事件
                epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
             
                 
            //CreateWorker(5);
                 
                maxi 
            = 0;
                
                task_t task; 
                task_t 
            *ptask;
                
            while(true
                {
                    
            //等待epoll事件的發生
                    nfds=epoll_wait(epfd,events,20,500);
                    
            //處理所發生的所有事件     
                    for(i=0;i<nfds;++i)
                    {
                        
            if(events[i].data.fd==listenfd)
                        {                
                            connfd 
            = accept(listenfd,NULL, NULL);
                            
            if(connfd<0){                    
                                printf(
            "connfd<0, listenfd = %d\n", listenfd);
                                printf(
            "error = %s\n", strerror(errno));
                                exit(
            1);
                            }
                            setnonblocking(connfd);
                           
                            
            //設置用于讀操作的文件描述符
                            memset(&task, 0sizeof(task));
                            task.fd 
            = connfd;
                            ev.data.ptr 
            = &task;
                            
            //設置用于注冊的讀操作事件
                            ev.events=EPOLLIN|EPOLLET;
                            
            //ev.events=EPOLLIN;
                            
            //注冊ev
                            epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
                        }
                        
            else if(events[i].events&EPOLLIN)
                        {
                            cout 
            << "EPOLLIN" << endl;
                            ptask 
            = (task_t*)events[i].data.ptr;
                            sockfd 
            = ptask->fd;
                            
                            
            if ( (ptask->= read(sockfd, ptask->buffer, 100)) < 0) {
                                
            if (errno == ECONNRESET) {
                                    close(sockfd);
                                    events[i].data.ptr 
            = NULL;
                                } 
            else
                                    std::cout
            <<"readline error"<<std::endl;
                            } 
            else if (ptask->== 0) {
                                close(sockfd);
                                events[i].data.ptr 
            = NULL;
                            }
                            ptask
            ->buffer[ptask->n] = '\0';
                            cout 
            << "read " << ptask->buffer << endl;
                            
                            
            //設置用于寫操作的文件描述符                                
                            ev.data.ptr = ptask;
                            
            //設置用于注測的寫操作事件
                            ev.events=EPOLLOUT|EPOLLET;
                                            
                            
            //修改sockfd上要處理的事件為EPOLLOUT
                            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
                        }
                        
            else if(events[i].events&EPOLLOUT)
                        {   
                            cout 
            << "EPOLLOUT" << endl;
                            ptask 
            = (task_t*)events[i].data.ptr;
                            sockfd 
            = ptask->fd;
                            
                            write(sockfd, ptask
            ->buffer, ptask->n);
                            
                            
            //設置用于讀操作的文件描述符              
                            ev.data.ptr = ptask;
                            
                            
            //修改sockfd上要處理的事件為EPOLIN
                            epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd,&ev);
                            cout 
            << "write " << ptask->buffer;
                            memset(ptask, 
            0sizeof(*ptask));
                            close(sockfd);
                        }
                    }
                }
                
            return 0;
            }

            測試客戶端:
            #!/usr/bin/perl

            use strict;
            use Socket;
            use IO::Handle;

            sub echoclient
            {
                my $host 
            = "127.0.0.1";
                my $port 
            = 5000;

                my $protocol 
            = getprotobyname("TCP");
                $host 
            = inet_aton($host);

                socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die 
            "socket() failed: $!";

                my $dest_addr 
            = sockaddr_in($port, $host);
                connect(SOCK, $dest_addr) or die 
            "connect() failed: $!";

                SOCK
            ->autoflush(1);

                my $msg_out 
            = "hello world\n";
                print 
            "out = ", $msg_out;
                print SOCK $msg_out;
                my $msg_in 
            = <SOCK>;
                print 
            "in = ", $msg_in;

                close SOCK;
            }

            #
            &echoclient;
            #exit(
            0);

            for (my $i = 0; $i < 9999; $i++)
            {
                echoclient;
            }


            我查看了lighttpd的實現,也是在創建完子進程之后才創建的epoll的fd.

            請問誰知道哪里有講解這個的文檔?



            這是美麗的分割線:
            -----------------------------------------------------------------------
            感謝luke, 他幫我解釋了這個問題的原因:

            假如fd1是由A進程加入epfd的,而且用的是ET模式,那么加入通知的是進程B,顯然B進程不會對fd1進行處理,所以以后fd1的事件再不會通知,所以 經過幾次循環之后,所有的fd都沒有事件通知了,所以epoll_wait在timeout之后就返回0了。而在客戶端的結果可想而知,只能是被阻塞。

            也就是說, 這是一種發生在epoll fd上面的類似于"驚群"的現象.

            posted on 2008-10-08 22:52 那誰 閱讀(12708) 評論(18)  編輯 收藏 引用 所屬分類: 網絡編程 、服務器設計

            評論

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            想法不錯,把accept前的多進程用于多路復用函數,完美的方案,呵呵。
            現實世界往往都不是那么完美。
            還沒看到過select poll epoll類的應用前面可以加fork做這種領導者模型的。想想也不太現實,即使可以多進程select,那么一個被喚醒后,讀數據前,另一個也一定會被喚醒,這樣還是和單進程一樣。
            2008-10-09 08:51 | cppexplore

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            @cppexplore
            沒看懂...什么叫"把accept前的多進程用于多路復用函數"..
            2008-10-09 17:21 |

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            如果在創建worker之前就創建了epoll fd, 那么這個fd會被5個進程所共享,因為fork會復制所有父進程的內容;如果是之后創建,等于是每個進程都會創建一個自己的epoll fd。那么這兩者之間有什么差別呢?
            2008-10-09 19:35 | luke

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            @創
            listen后 accept前加fork是常見的設計,在不使用多路復用函數的情況下
            @luke
            區別就是fork前生成fd ,會導致多個進程在相同epoll上wait
            2008-10-09 22:28 | cppexplore

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            汗一個,這不是cu的converse嘛。。
            2008-10-09 23:28 | Ask u

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            @Ask u
            是我
            2008-10-10 10:07 |

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            這里有個問題,我想值得思考:
            如果多個進程同時去epoll_wait同一個epfd,那么加入有fd狀態發生了變化,內核怎么知道去通知哪一個進程來處理呢?我想在內核的實現上,不會去把每個監聽的fd和進程進行綁定。所以內核不知道該去通知哪個進程來處理這些狀態發生變化的fd們,所以出現了上述的情況,我沒有讀過epoll_wait的實現代碼,上述判斷只屬于猜測。
            在google上也看到過有人評論,當多個線程監聽同一個epfd的時候,會發現所有線程同時從epoll_wait出來,我想這也是因為epoll_wait內部不知道該通知哪個線程來處理所導致的。
            2008-10-10 11:52 | luke

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            再進一步解釋:
            epoll_wait還是要工作的,即使是在多個進程同時wait在同一個epfd上,那會怎么工作呢,很顯然,按epoll_wait順序來通知進程。
            加入 fd1是由A進程加入epfd的,而且用的是ET模式,那么加入通知的是進程B,顯然B進程不會對fd1進行處理,所以以后fd1的事件再不會通知,所以經過幾次循環之后,所有的fd都沒有事件通知了,所以epoll_wait在timeout之后就返回0了。而在客戶端的結果可想而知,只能是被阻塞。
            2008-10-10 12:31 | luke

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            這里還有一個問題需要討論一下:
            你在加listenfd的時候,用的是ET模式。ET模式的情況是,如果你不一次把緩沖區讀空,以后,epoll就不會再通知你有數據。所以在大量并發連接的情況下,你只accept一次之后,會不會在buf中同時還有另外一個connect。這樣的話,會不會以后就得不到listenfd的事件通知了?只是懷疑,沒有實踐過,見諒。
            2008-10-10 13:01 | luke

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            @luke
            感謝你給我的解答,這個問題我已經弄明白了.

            另外,accept的問題確實存在,同樣感謝你提醒了我.
            2008-10-10 13:18 |

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            請教一下。是不是當有連接事件發生時,內核只通知某一個進程中epoll_wait上有事件發生?而這種連接負載均衡是由內核實現的,在我們的程序中不用管?
            2009-04-17 13:20 | firefly

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            @firefly
            是的.

            2009-04-18 10:49 | 那誰

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            撇開你說的這個問題不談,你的程序中對所有的socket的處理,怎么只用到了一個task變量?其他第二個socket可能沖掉第一個socket。
            2009-04-28 15:26 | 游客

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            還有代碼里if (-1 == ::wait(&nStatus))應該是if (-1 != ::wait(&nStatus))吧?
            2009-06-10 15:12 | alexhappy

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            accept后的處理部分有大問題:如果有另外一個客戶再連接上來就會清洗掉原來task中的內部。。無法做到多用戶并發!
            2009-09-03 18:42 | jolonchan

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后[未登錄]  回復  更多評論   

            listenfd被加入到所有的進程的epoll中,當listenfd可讀的時候,根據epoll_wait的驚群現象,是不是所有的子進程都會被喚醒?這樣這種多進程結構會不會有性能問題。
            2009-11-06 11:17 | fisherman

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            我測試后發現不是這樣的。
            加入 fd1是由A進程加入epfd的,而且用的是ET模式,那么加入通知的是進程B,顯然B進程不會對fd1進行處理,所以以后fd1的事件再不會通知,所以經過幾次循環之后,所有的fd都沒有事件通知了,所以epoll_wait在timeout之后就返回0了。而在客戶端的結果可想而知,只能是被阻塞。


            每次都是獲得的fd是4的時候阻塞,即從epoll_wait返回的fd是epfd的時候阻塞了。我不知道原因是什么
            2009-12-30 21:51 | skeeter

            # re: 多進程服務器中,epoll的創建應該在創建子進程之后  回復  更多評論   

            lz沒有close epollfd
            2011-03-29 11:27 | zhanglistar
            中文成人无码精品久久久不卡| 亚洲国产成人久久精品影视| 久久无码中文字幕东京热| 7777久久久国产精品消防器材| 一本色道久久88—综合亚洲精品| 91精品国产9l久久久久| 久久99国产一区二区三区| 人妻少妇久久中文字幕| 久久国产影院| 久久久精品免费国产四虎| 亚洲人成电影网站久久| 国产 亚洲 欧美 另类 久久| 午夜人妻久久久久久久久| 久久久久亚洲精品中文字幕| 成人久久久观看免费毛片| 久久国产免费直播| 久久久久亚洲精品天堂久久久久久| 亚洲精品无码久久千人斩| 久久国产视屏| 国产精品热久久毛片| 97超级碰碰碰久久久久| 色婷婷综合久久久久中文| 中文成人久久久久影院免费观看 | 天天躁日日躁狠狠久久| 国产精品成人99久久久久| 国内精品人妻无码久久久影院 | 国产AⅤ精品一区二区三区久久| 无码AV中文字幕久久专区| 久久久这里有精品| 青青久久精品国产免费看 | 无码国内精品久久综合88| 久久一区二区三区99| 久久久WWW成人免费精品| 久久狠狠一本精品综合网| 九九热久久免费视频| 亚洲国产成人久久综合一 | 久久精品国产亚洲精品| 亚洲综合婷婷久久| 国产精品久久久久影视不卡| 成人免费网站久久久| 久久精品免费一区二区三区|