• <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>
            posts - 297,  comments - 15,  trackbacks - 0
            Linux下各類TCP網(wǎng)絡(luò)服務(wù)器的實現(xiàn)源代碼

            大家都知道各類網(wǎng)絡(luò)服務(wù)器程序的編寫步驟,并且都知道網(wǎng)絡(luò)服務(wù)器就兩大類:循環(huán)服務(wù)和并發(fā)服務(wù)。這里附上源代碼來個小結(jié)吧。

            首先,循環(huán)網(wǎng)絡(luò)服務(wù)器編程實現(xiàn)的步驟是這樣的:
            [IMG]http://zhoulifa.bokee.com/inc/directsocket.png[/IMG] 
            這種服務(wù)器模型是典型循環(huán)服務(wù),如果不加上多進程/線程技術(shù),此種服務(wù)吞吐量有限,大家都可以看到,如果前一個連接服務(wù)數(shù)據(jù)沒有收發(fā)完畢后面的連接沒辦法處理。所以一般有多進程技術(shù),對一個新連接啟用一個新進程去處理,而監(jiān)聽socket繼續(xù)監(jiān)聽。

            /************關(guān)于本文檔********************************************
            *filename: Linux下各類TCP網(wǎng)絡(luò)服務(wù)器的實現(xiàn)源代碼
            *purpose: 記錄Linux下各類tcp服務(wù)程序源代碼
            *wrote by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-04 22:00:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *********************************************************************/

            一個循環(huán)TCP服務(wù)源代碼(因為用fork進行多進程服務(wù)了,所以這種服務(wù)現(xiàn)實中也有用)如下:
            [CODE]
            /*----------------------源代碼開始--------------------------------------------*/
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/socket.h>
            #include <sys/wait.h>
            /*********************************************************************
            *filename: cycletcpserver.c
            *purpose: 循環(huán)tcp服務(wù)端程序
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-04 22:00:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Google.com
            *********************************************************************/
            int main(int argc, char ** argv)
            {
                int sockfd,new_fd; /* 監(jiān)聽socket: sock_fd,數(shù)據(jù)傳輸socket: new_fd */
                struct sockaddr_in my_addr; /* 本機地址信息 */
                struct sockaddr_in their_addr; /* 客戶地址信息 */
                unsigned int sin_size, myport, lisnum;

                if(argv[1])  myport = atoi(argv[1]);
                else myport = 7838;

                if(argv[2])  lisnum = atoi(argv[2]);
                else lisnum = 2;

                if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
                    perror("socket");
                    exit(1);
                }
                my_addr.sin_family=PF_INET;
                my_addr.sin_port=htons(myport);
                my_addr.sin_addr.s_addr = INADDR_ANY;
                bzero(&(my_addr.sin_zero), 0);
                if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
                    perror("bind");
                    exit(1);
                }

                if (listen(sockfd, lisnum) == -1) {
                    perror("listen");
                    exit(1);
                }
                while(1) {
                    sin_size = sizeof(struct sockaddr_in);
                    if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
                        perror("accept");
                        continue;
                    }
                    printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
                    if (!fork()) { /* 子進程代碼段 */
                        if (send(new_fd, "Hello, world!\n", 14, 0) == -1) {
                            perror("send");
                            close(new_fd);
                            exit(0);
                        }
                    }
                    close(new_fd); /*父進程不再需要該socket*/
                    waitpid(-1,NULL,WNOHANG);/*等待子進程結(jié)束,清除子進程所占用資源*/
                }
            }
            /*----------------------源代碼結(jié)束--------------------------------------------*/
            [/CODE]
            一個測試客戶端代碼如下:
            [CODE]
            /*----------------------源代碼開始--------------------------------------------*/
            #include <stdio.h>
            #include <stdlib.h>
            #include <errno.h>
            #include <string.h>
            #include <netdb.h>
            #include <sys/types.h>
            #include <netinet/in.h>
            #include <sys/socket.h>
            #define MAXDATASIZE 100 /*每次最大數(shù)據(jù)傳輸量 */
            /*********************************************************************
            *filename: cycletcpclient.c
            *purpose: 循環(huán)tcp客戶端程序
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-04 22:20:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Google.com
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *********************************************************************/

            int main(int argc, char *argv[])
            {
                int sockfd, numbytes;
                char buf[MAXDATASIZE];
                struct hostent *he;
                struct sockaddr_in their_addr;
                unsigned int myport;

                if(argv[2]) myport = atoi(argv[2]);
                else myport = 7838;

                if (argc != 3) {
                    fprintf(stderr,"usage: %s hostname port\n", argv[0]);
                    exit(1);
                }
                if((he=gethostbyname(argv[1]))==NULL) {
                    herror("gethostbyname");
                    exit(1);
                }
                if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
                    perror("socket");
                    exit(1);
                }
                their_addr.sin_family=PF_INET;
                their_addr.sin_port=htons(myport);
                their_addr.sin_addr = *((struct in_addr *)he->h_addr);
                bzero(&(their_addr.sin_zero),0);
                if (connect(sockfd, (struct sockaddr *)&their_addr, sizeof(struct sockaddr)) == -1) {
                    perror("connect");
                    exit(1);
                }
                if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
                    perror("recv");
                    exit(1);
                }
                buf[numbytes] = 0;
                printf("Received: %s\n",buf);
                close(sockfd);
                return 0;
            }
            /*----------------------源代碼結(jié)束--------------------------------------------*/
            [/CODE]
            用gcc cycletcpserver.c -o tcpserver和gcc cycletcpclient.c -o tcpclient分別編譯上述代碼后運行情況如下:
            服務(wù)端運行顯示:
            [QUOTE]
            administrator@ubuzlf:/data/example/c$ ./tcpserver
            server: got connection from 127.0.0.1
            server: got connection from 127.0.0.1
            server: got connection from 127.0.0.1
            [/QUOTE]
            客戶端運行顯示:
            [QUOTE]
            administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
            Received: Hello, world!

            administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
            Received: Hello, world!

            administrator@ubuzlf:/data/example/c$ ./tcpclient 127.0.0.1 7838
            Received: Hello, world!

            [/QUOTE]
            不得不說的一個概念性問題:阻塞與非阻塞
            在阻塞服務(wù)中,當(dāng)服務(wù)器運行到accept語句而沒有客戶連接服務(wù)請求到來,那么會發(fā)生什么情況? 這時服務(wù)器就會停止在accept語句上等待連接服務(wù)請求的到來;同樣,當(dāng)程序運行到接收數(shù)據(jù)語句recv時,如果沒有數(shù)據(jù)可以讀取,則程序同樣會停止在接收語句上。這種情況稱為阻塞(blocking)。
            但如果你希望服務(wù)器僅僅注意檢查是否有客戶在等待連接,有就接受連接;否則就繼續(xù)做其他事情,則可以通過將 socket設(shè)置為非阻塞方式來實現(xiàn):非阻塞socket在沒有客戶在等待時就使accept調(diào)用立即返回 。
            通過設(shè)置socket為非阻塞方式,可以實現(xiàn)“輪詢”若干socket。當(dāng)企圖從一個沒有數(shù)據(jù)等待處理的非阻塞socket讀入數(shù)據(jù)時,函數(shù)將立即返回,并且返回值置為-1,并且errno置為EWOULDBLOCK。但是這種“輪詢”會使CPU處于忙等待方式,從而降低性能。考慮到這種情況,假設(shè)你希望服務(wù)器監(jiān)聽連接服務(wù)請求的同時從已經(jīng)建立的連接讀取數(shù)據(jù),你也許會想到用一個accept語句和多個recv()語句,但是由于accept及recv都是會阻塞的,所以這個想法顯然不會成功。
            調(diào)用非阻塞的socket會大大地浪費系統(tǒng)資源。而調(diào)用select()會有效地解決這個問題,它允許你把進程本身掛起來,而同時使系統(tǒng)內(nèi)核監(jiān)聽所要求的一組文件描述符的任何活動,只要確認在任何被監(jiān)控的文件描述符上出現(xiàn)活動,select()調(diào)用將返回指示該文件描述符已準備好的信息,從而實現(xiàn)了為進程選出隨機的變化,而不必由進程本身對輸入進行測試而浪費CPU開銷。

            其次,并發(fā)服務(wù)器,在上述cycletcpserver.c中,由于使用了fork技術(shù)也可以稱之為并發(fā)服務(wù)器,但這種服務(wù)器并不是真正意義上的IO多路復(fù)用的并發(fā)服務(wù)器,并且由于沒有處理阻塞問題,實際應(yīng)用有各種各樣的問題。

            一個典型IO多路復(fù)用的單進程并發(fā)服務(wù)器流程如下:
            /*IO多路復(fù)用并發(fā)服務(wù)流程圖*/
            [IMG]http://zhoulifa.bokee.com/inc/simpleselect.png[/IMG]
            下面是一個演示IO多路復(fù)用的源程序,是一個端口轉(zhuǎn)發(fā)程序,但它的用處相當(dāng)大,實際應(yīng)用中的各類代理軟件或端口映射軟件都是基于這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎(chǔ)上實現(xiàn)的。源代碼如下:
            [CODE]
            /*----------------------源代碼開始--------------------------------------------*/
            #include <stdlib.h>
            #include <stdio.h>
            #include <unistd.h>
            #include <sys/time.h>
            #include <sys/types.h>
            #include <string.h>
            #include <signal.h>
            #include <sys/socket.h>
            #include <netinet/in.h>
            #include <arpa/inet.h>
            #include <errno.h>

            static int forward_port;

            #undef max
            #define max(x,y) ((x) > (y) ? (x) : (y))

            /*************************關(guān)于本文檔************************************
            *filename: tcpforwardport.c
            *purpose: 演示了select的用法,這是一個極好的代理軟件核心,專門作端口映射用
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-05 19:00:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *********************************************************************/

            static int listen_socket (int listen_port) {
                struct sockaddr_in a;
                int s;
                int yes;
                if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
                    perror ("socket");
                    return -1;
                }
                yes = 1;
                if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof (yes)) <
            0) {
                    perror ("setsockopt");
                    close (s);
                    return -1;
                }
                memset (&a, 0, sizeof (a));
                a.sin_port = htons (listen_port);
                a.sin_family = AF_INET;
                if (bind(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
                    perror ("bind");
                    close (s);
                    return -1;
                }
                printf ("accepting connections on port %d\n", (int) listen_port);
                listen (s, 10);
                return s;
            }

            static int connect_socket (int connect_port, char *address) {
                struct sockaddr_in a;
                int s;
                if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0) {
                    perror ("socket");
                    close (s);
                    return -1;
                }

                memset (&a, 0, sizeof (a));
                a.sin_port = htons (connect_port);
                a.sin_family = AF_INET;

                if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
                    perror ("bad IP address format");
                    close (s);
                    return -1;
                }

                if (connect(s, (struct sockaddr *) &a, sizeof (a)) < 0) {
                    perror ("connect()");
                    shutdown (s, SHUT_RDWR);
                    close (s);
                    return -1;
                }
                return s;
            }

            #define SHUT_FD1 { \
                if (fd1 >= 0) {   \
                    shutdown (fd1, SHUT_RDWR);  \
                    close (fd1);  \
                    fd1 = -1;     \
                }   \
            }

            #define SHUT_FD2 { \
                if (fd2 >= 0) {   \
                    shutdown (fd2, SHUT_RDWR);  \
                    close (fd2);  \
                    fd2 = -1;     \
                }   \
            }

            #define BUF_SIZE 1024

            int main (int argc, char **argv) {
                int h;
                int fd1 = -1, fd2 = -1;
                char buf1[BUF_SIZE], buf2[BUF_SIZE];
                int buf1_avail, buf1_written;
                int buf2_avail, buf2_written;

                if (argc != 4) {
                    fprintf (stderr, "Usage\n\tfwd   \n");
                    exit (1);
                }

                signal (SIGPIPE, SIG_IGN);

                forward_port = atoi (argv[2]);

                /*建立監(jiān)聽socket*/
                h = listen_socket (atoi (argv[1]));
                if (h < 0) exit (1);

                for (;;) {
                    int r, nfds = 0;
                    fd_set rd, wr, er;
                    FD_ZERO (&rd);
                    FD_ZERO (&wr);
                    FD_ZERO (&er);
                    FD_SET (h, &rd);

                    /*把監(jiān)聽socket和可讀socket三個一起放入select的可讀句柄列表里*/
                    nfds = max (nfds, h);
                    if (fd1 > 0 && buf1_avail < BUF_SIZE) {
                        FD_SET (fd1, &rd);
                        nfds = max (nfds, fd1);
                    }
                    if (fd2 > 0 && buf2_avail < BUF_SIZE) {
                        FD_SET (fd2, &rd);
                        nfds = max (nfds, fd2);
                    }

                    /*把可寫socket兩個一起放入select的可寫句柄列表里*/
                    if (fd1 > 0 && buf2_avail - buf2_written > 0) {
                        FD_SET (fd1, &wr);
                        nfds = max (nfds, fd1);
                    }
                    if (fd2 > 0 && buf1_avail - buf1_written > 0) {
                        FD_SET (fd2, &wr);
                        nfds = max (nfds, fd2);
                    }

                    /*把有異常數(shù)據(jù)的socket兩個一起放入select的異常句柄列表里*/
                    if (fd1 > 0) {
                        FD_SET (fd1, &er);
                        nfds = max (nfds, fd1);
                    }
                    if (fd2 > 0) {
                        FD_SET (fd2, &er);
                        nfds = max (nfds, fd2);
                    }

                    /*開始select*/
                    r = select (nfds + 1, &rd, &wr, &er, NULL);

                    if (r == -1 && errno == EINTR) continue;
                    if (r < 0) {
                        perror ("select()");
                        exit (1);
                    }

                    /*處理新連接*/
                    if (FD_ISSET (h, &rd)) {
                        unsigned int l;
                        struct sockaddr_in client_address;
                        memset (&client_address, 0, l = sizeof (client_address));
                        r = accept (h, (struct sockaddr *)&client_address, &l);
                        if (r < 0) {
                            perror ("accept()");
                        } else {
                            /*關(guān)閉原有連接,把新連接作為fd1,同時連接新的目標fd2*/
                            SHUT_FD1;
                            SHUT_FD2;
                            buf1_avail = buf1_written = 0;
                            buf2_avail = buf2_written = 0;
                            fd1 = r;
                            fd2 = connect_socket (forward_port, argv[3]);
                            if (fd2 < 0) {
                                SHUT_FD1;
                            } else
                                printf ("connect from %s\n", inet_ntoa(client_address.sin_addr));
                        }
                    }

                    /* NB: read oob data before normal reads */
                    if (fd1 > 0)
                    if (FD_ISSET (fd1, &er)) {
                        char c;
                        errno = 0;
                        r = recv (fd1, &c, 1, MSG_OOB);
                        if (r < 1) {
                            SHUT_FD1;
                        } else
                            send (fd2, &c, 1, MSG_OOB);
                    }

                    if (fd2 > 0)
                    if (FD_ISSET (fd2, &er)) {
                        char c;
                        errno = 0;
                        r = recv (fd2, &c, 1, MSG_OOB);
                        if (r < 1) {
                            SHUT_FD1;
                        } else
                            send (fd1, &c, 1, MSG_OOB);
                    }

                    /* NB: read data from fd1 */
                    if (fd1 > 0)
                    if (FD_ISSET (fd1, &rd)) {
                        r = read (fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
                        if (r < 1) {
                            SHUT_FD1;
                        } else
                            buf1_avail += r;
                    }

                    /* NB: read data from fd2 */
                    if (fd2 > 0)
                    if (FD_ISSET (fd2, &rd)) {
                        r = read (fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
                        if (r < 1) {
                            SHUT_FD2;
                        } else
                            buf2_avail += r;
                    }

                    /* NB: write data to fd1 */
                    if (fd1 > 0)
                    if (FD_ISSET (fd1, &wr)) {
                        r = write (fd1, buf2 + buf2_written, buf2_avail - buf2_written);
                        if (r < 1) {
                            SHUT_FD1;
                        } else
                            buf2_written += r;
                    }

                    /* NB: write data to fd1 */
                    if (fd2 > 0)
                    if (FD_ISSET (fd2, &wr)) {
                        r = write (fd2, buf1 + buf1_written, buf1_avail - buf1_written);
                        if (r < 1) {
                            SHUT_FD2;
                        } else
                            buf1_written += r;
                    }

                    /* check if write data has caught read data */
                    if (buf1_written == buf1_avail) buf1_written = buf1_avail = 0;
                    if (buf2_written == buf2_avail) buf2_written = buf2_avail = 0;

                    /* one side has closed the connection, keep writing to the other side until empty */
                    if (fd1 < 0 && buf1_avail - buf1_written == 0) {
                        SHUT_FD2;
                    }
                    if (fd2 < 0 && buf2_avail - buf2_written == 0) {
                        SHUT_FD1;
                    }
                }
                return 0;
            }
            /*----------------------源代碼結(jié)束--------------------------------------------*/
            [/CODE]  
            用gcc tcpforwardport.c -o MyProxy編譯此程序后運行效果如下:
            [QUOTE]
            ./MyProxy 8000 80 172.16.100.218
            accepting connections on port 8000
            connect from 127.0.0.1
            [/QUOTE]
            當(dāng)有用戶訪問本機的8000端口時,MyProxy程序?qū)汛苏埱筠D(zhuǎn)發(fā)到172.16.100.218主機的80端口,即實現(xiàn)了一個http代理。

            關(guān)于select函數(shù):
            其函數(shù)原型為:
            int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
            此函數(shù)的功能是由內(nèi)核檢測在timeout時間內(nèi),是否有readfds,writefds,exceptfds三個句柄集(file descriptors)里的某個句柄(file descriptor)的狀態(tài)符合尋求,即readfds句柄集里有句柄可讀或writefds句柄集里有可寫或exceptfds句柄集里有例外發(fā)生,任何一個有變化函數(shù)就立即返回,返回值為timeout發(fā)生狀態(tài)變化的句柄個數(shù)。
            n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)里編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內(nèi)是否分別可讀和可寫就可以這樣:
            先把兩個句柄集(file descriptors)清零:
                    FD_ZERO (&readfds);
                    FD_ZERO (&writefds);
            然后把fd1加入讀檢測集:
                    FD_SET (fd1, &readfds);
            然后把fd2加入寫檢測集:
                    FD_SET (fd2, &writefds);
            再給timeout設(shè)置值,timeout是這樣的一個結(jié)構(gòu):
                          struct timeval {
                              long    tv_sec;         /* seconds */
                              long    tv_usec;        /* microseconds */
                          };
            你可以這樣賦值:
                    timeout.tv_sec=1;
                    timeout.tv_uec=0;
            表示檢測在1秒鐘內(nèi)是否有句柄狀態(tài)發(fā)生變化。
            如果有句柄發(fā)生變化,就可以用FD_ISSET檢測各個句柄,比如:
                            FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
                            FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
            示意程序代碼如下:
            [CODE]
            /*----------------------示意代碼開始--------------------------------------------*/
                fd1 = socket();//創(chuàng)建一個socket
                fd2 = socket();//創(chuàng)建一個socket
                while(1)  {
                    FD_ZERO (&readfds);
                    FD_ZERO (&writefds);
                    FD_SET (fd1, &readfds);
                    FD_SET (fd2, &writefds);
                    timeout.tv_sec=1;
                    timeout.tv_uec=0;
                    ret = select(fd1>fd2?(fd1+1)fd2+1), &readfds, &writefds, NULL, &timeout);
                    if(ret < 0) {printf("系統(tǒng)錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}
                    else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態(tài)發(fā)生變化!");}
                    //有句柄狀態(tài)發(fā)生了變化
                    if(FD_ISSET(fd1, &readfds)) {
                        fd1有數(shù)據(jù)可讀;
                        fd1里的數(shù)據(jù)被讀出來;
                    }
                    if(FD_ISSET(fd2, &writefds)) {
                        fd2可寫;
                        fd2里發(fā)送數(shù)據(jù)給對方;
                    }
                }
            /*----------------------示意代碼結(jié)束--------------------------------------------*/
            [/CODE]
            經(jīng)常用到的幾個自定義函數(shù):
            1、開啟監(jiān)聽的函數(shù)
            [CODE]
            /*----------------------源代碼代碼開始--------------------------------------------*/
            int
            OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {
            /*************************關(guān)于本函數(shù)************************************
            *function_name: OpenSCPServer
            *參數(shù)說明:port整數(shù)型監(jiān)聽端口號,total整數(shù)型監(jiān)聽個數(shù),sendbuflen整數(shù)型發(fā)送緩沖區(qū)大小
            *          recvbuflen整數(shù)型接收緩沖區(qū)大小,blockORnot整數(shù)型是否阻塞,reuseORnot整數(shù)型是否端口重用
            *purpose: 用來建立一個tcp服務(wù)端socket
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-05 20:00:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *Note:要使用此函數(shù)需要自定義一個全局變量char errorMessage[1024];并包含GetCurrentTime.h頭文件
            *********************************************************************/
                int    sockfd = 0, ret = 0, opt = 0, flags=1;
                struct sockaddr_in    laddr;

                ret = sockfd = socket(PF_INET, SOCK_STREAM, 0);
                if(ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer socket() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    return -1;
                }

                ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseORnot, sizeof(int));
                if(ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer setsockopt() reuse error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    return -2;
                }

                ret = setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvbuflen, sizeof(int));
                if ( ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer setsockopt() recvbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    return -3;
                }

                ret = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &sendbuflen, sizeof(int));
                if (ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer setsockopt() sendbuf error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    return -4;
                }

                ioctl(sockfd,FIONBIO,&blockORnot);/*block or not*/

                laddr.sin_family = PF_INET;
                laddr.sin_port = htons(port);
                laddr.sin_addr.s_addr = INADDR_ANY;
                bzero(&(laddr.sin_zero), 8);

                ret = bind(sockfd, (struct sockaddr *)&laddr, sizeof(struct sockaddr));
                if(ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer bind() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    close(sockfd);
                    return -5;
                }
                ret = listen(sockfd, total);
                if(ret < 0)    {
                    sprintf(errorMessage, "OpenTCPServer listen() error! return:%d, errno=%d, errortext:'%s' %s", ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    close(sockfd);
                    return -6;
                }
                sprintf(errorMessage, "OpenTCPServer opened on port.%d(%d) OK, socket(%d), buf(%d:%d)! %s", port, total, sockfd, sendbuflen, recvbuflen, GetCurrentTime(0, 0));
                return sockfd;
            }
            /*----------------------源代碼代碼結(jié)束--------------------------------------------*/
            [/CODE]
            2、連接服務(wù)器的函數(shù)
            [CODE]
            /*----------------------源代碼代碼開始--------------------------------------------*/
            int
            ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {
            /*************************關(guān)于本函數(shù)************************************
            *function_name: ConnectSCPServer
            *參數(shù)說明:serverip服務(wù)器IP地址或主機名,serverport服務(wù)器端口,blockORnot整數(shù)型是否阻塞
            *purpose: 用來建立一個tcp客戶端socket
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-05 20:40:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *Note:要使用此函數(shù)需要自定義一個全局變量char errorMessage[1024];并包含自己編寫的GetCurrentTime.h頭文件
            *********************************************************************/
                int    serversock = 0, ret = 0;
                unsigned long    addr;
                struct sockaddr_in    sin;
                struct hostent *he;

                if((he=gethostbyname(serverip))== 0) {
                    sprintf(errorMessage, "ConnectSCPServer IP address '%s' error! return:-1 %s", serverip, GetCurrentTime(0, 0));
                    return -1;
                }

                serversock = socket(PF_INET, SOCK_STREAM, 0);
                if(serversock == -1)    {
                    sprintf(errorMessage, "ConnectSCPServer socket() error! return:-2, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
                    return -2;
                }

                ioctl(serversock, FIONBIO, &blockORnot);  //block or not

                memset((char*)&sin, 0, sizeof(struct sockaddr_in));
                sin.sin_family = PF_INET;
                sin.sin_port = htons(serverport);
                sin.sin_addr = *((struct in_addr *)he->h_addr);

                ret = connect(serversock, (struct sockaddr *)&sin, sizeof(sin));

                if(ret == -1)    {
                    sprintf(errorMessage, "ConnectSCPServer connect() error! return:-3, errno=%d, errortext:'%s' %s", errno, strerror(errno), GetCurrentTime(0, 0));
                    close(serversock);
                    return -3;
                }

                return serversock;
            }
            /*----------------------源代碼代碼結(jié)束--------------------------------------------*/
            [/CODE]
            3、發(fā)送數(shù)據(jù)函數(shù)Send
            [CODE]
            /*----------------------源代碼代碼開始--------------------------------------------*/
            int
            Send(int sock, char * buf, size_t size, int flag, int timeout)    {
            /*************************關(guān)于本函數(shù)************************************
            *function_name: Send
            *參數(shù)說明:sock整數(shù)型socket,buf待發(fā)送的內(nèi)容,size要發(fā)送的大小,flag發(fā)送選項,timeout超時時間值
            *purpose: 用來通過一個socket在指定時間內(nèi)發(fā)送數(shù)據(jù)
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-05 20:58:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *Note:要使用此函數(shù)需要自定義一個全局變量char errorMessage[1024];并包含自己編寫的GetCurrentTime.h頭文件
            *********************************************************************/
                int i = 0, ret = 0, intretry = 0;

                struct timeval tival;
                fd_set writefds;
                int maxfds = 0;

                tival.tv_sec = timeout;
                tival.tv_usec = 0;

                FD_ZERO(&writefds);

                if(sock > 0) {
                    FD_SET(sock, &writefds);
                    maxfds=((sock > maxfds)?sock:maxfds);
                }
                else    {
                    sprintf(errorMessage, "Send socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
                    return -2;
                }

                ret = select(maxfds + 1, NULL, &writefds, NULL, &tival);
                if(ret <= 0) {
                    if(ret < 0)    sprintf(errorMessage, "Send socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    else sprintf(errorMessage, "Send socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
                    close(sock);
                    return -3;
                }
                if(!(FD_ISSET(sock, &writefds)))    {
                    sprintf(errorMessage, "Send socket:%d not in writefds! %s", sock, GetCurrentTime(0, 0));
                    close(sock);
                    return -4;
                }

                while(i < size)    {
                    ret = send(sock, buf + i, size - i, flag);
                    if(ret <= 0)    {
                        sprintf(errorMessage, "Send socket:%d send() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));

                        if (EINTR == errno)
                          if(intretry < 10)  {intretry++;continue;}
                          else sprintf(errorMessage, "Send socket:%d send() error!EINTR 10 times! %s", sock, GetCurrentTime(0, 0));

                        close(sock);
                        return -1;
                    }
                    else i += ret;
                }
                sprintf(errorMessage, "Send socket:%d send() OK! %d/%d bytes sent! %s", sock, i, size, GetCurrentTime(0, 0));
                return i;
            }
            /*----------------------源代碼代碼結(jié)束--------------------------------------------*/
            [/CODE]
            4、接收數(shù)據(jù)函數(shù)Recv
            [CODE]
            /*----------------------源代碼代碼開始--------------------------------------------*/
            int
            Recv(int sock, char * buf, size_t size, int flag, int timeout)    {
            /*************************關(guān)于本函數(shù)************************************
            *function_name: Recv
            *參數(shù)說明:sock整數(shù)型socket,buf接收數(shù)據(jù)的緩沖區(qū),size要接收數(shù)據(jù)的大小,flag接收選項,timeout超時時間值
            *purpose: 用來從一個socket在指定時間內(nèi)讀取數(shù)據(jù)
            *tidied by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)
            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言
            *date time:2006-07-05 21:10:00
            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途
            * 但請遵循GPL
            *Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力
            *Note:要使用此函數(shù)需要自定義一個全局變量char errorMessage[1024];并包含自己編寫的GetCurrentTime.h頭文件
            *********************************************************************/
                int i = 0, ret = 0, intretry = 0;

                struct timeval tival;
                fd_set readfds;
                int maxfds = 0;

                tival.tv_sec = timeout;
                tival.tv_usec = 0;

                FD_ZERO(&readfds);

                if(sock > 0) {
                    FD_SET(sock, &readfds);
                    maxfds=((sock > maxfds)?sock:maxfds);
                }
                else    {
                    sprintf(errorMessage, "Recv socket:%d error! return:-2 %s", sock, GetCurrentTime(0, 0));
                    return -2;
                }

                ret = select(maxfds + 1, &readfds, NULL, NULL, &tival);
                if(ret <= 0) {
                    if(ret < 0)    sprintf(errorMessage, "Recv socket:%d select() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
                    else sprintf(errorMessage, "Recv socket:%d select timeout(%d)! %s", sock, timeout, GetCurrentTime(0, 0));
                    close(sock);
                    return -3;
                }
                if(!(FD_ISSET(sock, &readfds)))    {
                    sprintf(errorMessage, "Recv socket:%d not in readfds! %s", sock, GetCurrentTime(0, 0));
                    close(sock);
                    return -4;
                }
                while(i < size)    {
                    ret = recv(sock, buf + i, size - i, flag);
                    if(ret <= 0){
                        sprintf(errorMessage, "Recv socket:%d recv() error! return:%d, errno=%d, errortext:'%s' %s", sock, ret, errno, strerror(errno), GetCurrentTime(0, 0));
                        if(errno == EINTR)   
                            if(intretry < 10)  {intretry++;continue;}
                            else sprintf(errorMessage, "Recv socket:%d recv() error! EINTR 10 times! %s", sock, GetCurrentTime(0, 0));
                        close(sock);
                        return -1;
                    }
                    else i += ret;
                }
                sprintf(errorMessage, "Recv socket:%d recv() OK! %d/%d bytes received! %s", sock, i, size, GetCurrentTime(0, 0));
                return i;
            }
            [/CODE]
            最后需要說明的是:我這里講到的源程序并不能實際地作為一個產(chǎn)品程序來用,實際情況下可能會有其它許多工作要做,比如可能要建立共享隊列來存放 socket里讀到的消息,也可能把發(fā)送消息先進行排隊然后再調(diào)用Send函數(shù)。還有,如果不是全數(shù)字,在發(fā)送前一定要htonl轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序,同理接收到后一定要先ntohl由網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機字節(jié)序,否則對方發(fā)送過來的0x00000001在你這里可能是0x00010000,因為高低位順序不同。 
            Linux 2.6內(nèi)核中提高網(wǎng)絡(luò)I/O性能的新方法epoll



            正如我昨天在“Linux下各類TCP網(wǎng)絡(luò)服務(wù)器的實現(xiàn)源代碼”(http://zhoulifa.bokee.com/5345930.html)一文中提到的那樣,I/O多路復(fù)用技術(shù)在比較多的TCP網(wǎng)絡(luò)服務(wù)器中有使用,即比較多的用到select函數(shù)。

            感謝chinaunix.net上朋友safedead(http://bbs.chinaunix.net/viewpro.php?uid=407631)提醒,我今天仔細研究了一下,證實了在2.6內(nèi)核中的新的I/O技術(shù)epoll。



            1、為什么select是落后的?

            首先,在Linux內(nèi)核中,select所用到的FD_SET是有限的,即內(nèi)核中有個參數(shù)__FD_SETSIZE定義了每個FD_SET的句柄個數(shù),在我用的2.6.15-25-386內(nèi)核中,該值是1024,搜索內(nèi)核源代碼得到:

            include/linux/posix_types.h:#define __FD_SETSIZE        1024

            也就是說,如果想要同時檢測1025個句柄的可讀狀態(tài)是不可能用select實現(xiàn)的。或者同時檢測1025個句柄的可寫狀態(tài)也是不可能的。

            其次,內(nèi)核中實現(xiàn)select是用輪詢方法,即每次檢測都會遍歷所有FD_SET中的句柄,顯然,select函數(shù)執(zhí)行時間與FD_SET中的句柄個數(shù)有一個比例關(guān)系,即select要檢測的句柄數(shù)越多就會越費時。

            當(dāng)然,在前文中我并沒有提及poll方法,事實上用select的朋友一定也試過poll,我個人覺得select和poll大同小異,個人偏好于用select而已。



            /************關(guān)于本文檔********************************************

            *filename: Linux 2.6內(nèi)核中提高網(wǎng)絡(luò)I/O性能的新方法epoll

            *purpose: 補充“Linux下各類TCP網(wǎng)絡(luò)服務(wù)器的實現(xiàn)源代碼”一文的不足之處

            *wrote by: zhoulifa(zhoulifa@163.com) 周立發(fā)(http://zhoulifa.bokee.com)

            Linux愛好者 Linux知識傳播者 SOHO族 開發(fā)者 最擅長C語言

            *date time:2006-07-06 22:30:00

            *Note: 任何人可以任意復(fù)制代碼并運用這些文檔,當(dāng)然包括你的商業(yè)用途

            * 但請遵循GPL

            *Hope:希望越來越多的人貢獻自己的力量,為科學(xué)技術(shù)發(fā)展出力

            *********************************************************************/



            2、2.6內(nèi)核中提高I/O性能的新方法epoll



            epoll是什么?按照man手冊的說法:是為處理大批量句柄而作了改進的poll。要使用epoll只需要這三個系統(tǒng)調(diào)用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。

            當(dāng)然,這不是2.6內(nèi)核才有的,它是在2.5.44內(nèi)核中被引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44)



            以下文章轉(zhuǎn)自滕昱的Web Log http://mechgouki.spaces.msn.com/blog/PersonalSpace.aspx
            [QUOTE]

            /*********************************引用開始******************************/

            Linux2.6內(nèi)核epoll介紹---我的blog 2005/3/30



            [作者]:滕昱,2005/3/30,0.1版本



            [版權(quán)聲明]:此文檔遵循GNU自由文檔許可證(GNU Free Documentation License).任何人可以自由復(fù)制,分發(fā),修改,不過如果方便,請注明出處和作者



            (1)導(dǎo)言:



            首先,我強烈建議大家閱讀Richard Stevens著作《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。雖然他離開我們大家已經(jīng)5年多了,但是他的書依然是進入網(wǎng)絡(luò)編程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必讀-如果你不了解tcp協(xié)議各個選項的詳細定義,你就失去了優(yōu)化程序重要的一個手段。卷2,3可以選讀一下。比如卷2 講解的是4.4BSD內(nèi)核TCP/IP協(xié)議棧實現(xiàn)----這個版本的協(xié)議棧幾乎影響了現(xiàn)在所有的主流os,但是因為年代久遠,內(nèi)容不一定那么vogue. 在這里我多推薦一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4內(nèi)核講解Linux TCP/IP實現(xiàn),相當(dāng)不錯.作為一個現(xiàn)實世界中的實現(xiàn),很多時候你必須作很多權(quán)衡,這時候參考一個久經(jīng)考驗的系統(tǒng)更有實際意義。舉個例子,linux內(nèi)核中sk_buff結(jié)構(gòu)為了追求速度和安全,犧牲了部分內(nèi)存,所以在發(fā)送TCP包的時候,無論應(yīng)用層數(shù)據(jù)多大,sk_buff最小也有272的字節(jié).



            其實對于socket應(yīng)用層程序來說,《UNIX Network Programming Volume 1》意義更大一點.2003年的時候,這本書出了最新的第3版本,不過主要還是修訂第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens給出了網(wǎng)絡(luò)IO的基本模型。在這里最重要的莫過于select模型和Asynchronous I/O模型.從理論上說,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告訴你IO操作完成。但是一直以來,如何實現(xiàn)就沒有一個完美的方案。最著名的windows完成端口實現(xiàn)的AIO,實際上也是內(nèi)部用線程池實現(xiàn)的罷了,最后的結(jié)果是IO有個線程池,你應(yīng)用也需要一個線程池...... 很多文檔其實已經(jīng)指出了這帶來的線程context-switch帶來的代價。



            在linux 平臺上,關(guān)于網(wǎng)絡(luò)AIO一直是改動最多的地方,2.4的年代就有很多AIO內(nèi)核patch,最著名的應(yīng)該算是SGI那個。但是一直到2.6內(nèi)核發(fā)布,網(wǎng)絡(luò)模塊的AIO一直沒有進入穩(wěn)定內(nèi)核版本(大部分都是使用用戶線程模擬方法,在使用了NPTL的linux上面其實和windows的完成端口基本上差不多了)。2.6內(nèi)核所支持的AIO特指磁盤的AIO---支持io_submit(),io_getevents()以及對Direct IO的支持(就是繞過VFS系統(tǒng)buffer直接寫硬盤,對于流服務(wù)器在內(nèi)存平穩(wěn)性上有相當(dāng)幫助)。



            所以,剩下的select模型基本上就是我們在linux上面的唯一選擇,其實,如果加上no-block socket的配置,可以完成一個"偽"AIO的實現(xiàn),只不過推動力在于你而不是os而已。不過傳統(tǒng)的select/poll函數(shù)有著一些無法忍受的缺點,所以改進一直是2.4-2.5開發(fā)版本內(nèi)核的任務(wù),包括/dev/poll,realtime signal等等。最終,Davide Libenzi開發(fā)的epoll進入2.6內(nèi)核成為正式的解決方案



            (2)epoll的優(yōu)點



            <1>支持一個進程打開大數(shù)目的socket描述符(FD)



            select 最不能忍受的是一個進程所打開的FD是有一定限制的,由FD_SETSIZE設(shè)置,默認值是2048。對于那些需要支持的上萬連接數(shù)目的IM服務(wù)器來說顯然太少了。這時候你一是可以選擇修改這個宏然后重新編譯內(nèi)核,不過資料也同時指出這樣會帶來網(wǎng)絡(luò)效率的下降,二是可以選擇多進程的解決方案(傳統(tǒng)的Apache方案),不過雖然linux上面創(chuàng)建進程的代價比較小,但仍舊是不可忽視的,加上進程間數(shù)據(jù)同步遠比不上線程間同步的高效,所以也不是一種完美的方案。不過 epoll則沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于2048,舉個例子,在1GB內(nèi)存的機器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。



            <2>IO效率不隨FD數(shù)目增加而線性下降



            傳統(tǒng)的select/poll另一個致命弱點就是當(dāng)你擁有一個很大的socket集合,不過由于網(wǎng)絡(luò)延時,任一時間只有部分的socket是"活躍"的,但是select/poll每次調(diào)用都會線性掃描全部的集合,導(dǎo)致效率呈現(xiàn)線性下降。但是epoll不存在這個問題,它只會對"活躍"的socket進行操作---這是因為在內(nèi)核實現(xiàn)中epoll是根據(jù)每個fd上面的callback函數(shù)實現(xiàn)的。那么,只有"活躍"的socket才會主動的去調(diào)用 callback函數(shù),其他idle狀態(tài)socket則不會,在這點上,epoll實現(xiàn)了一個"偽"AIO,因為這時候推動力在os內(nèi)核。在一些 benchmark中,如果所有的socket基本上都是活躍的---比如一個高速LAN環(huán)境,epoll并不比select/poll有什么效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環(huán)境,epoll的效率就遠在select/poll之上了。



            <3>使用mmap加速內(nèi)核與用戶空間的消息傳遞。



            這點實際上涉及到epoll的具體實現(xiàn)了。無論是select,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點上,epoll是通過內(nèi)核于用戶空間mmap同一塊內(nèi)存實現(xiàn)的。而如果你想我一樣從2.5內(nèi)核就關(guān)注epoll的話,一定不會忘記手工 mmap這一步的。



            <4>內(nèi)核微調(diào)



            這一點其實不算epoll的優(yōu)點了,而是整個linux平臺的優(yōu)點。也許你可以懷疑linux平臺,但是你無法回避linux平臺賦予你微調(diào)內(nèi)核的能力。比如,內(nèi)核TCP/IP協(xié)議棧使用內(nèi)存池管理sk_buff結(jié)構(gòu),那么可以在運行時期動態(tài)調(diào)整這個內(nèi)存pool(skb_head_pool)的大小--- 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數(shù)的第2個參數(shù)(TCP完成3次握手的數(shù)據(jù)包隊列長度),也可以根據(jù)你平臺內(nèi)存大小動態(tài)調(diào)整。更甚至在一個數(shù)據(jù)包面數(shù)目巨大但同時每個數(shù)據(jù)包本身大小卻很小的特殊系統(tǒng)上嘗試最新的NAPI網(wǎng)卡驅(qū)動架構(gòu)。



            (3)epoll的使用



            令人高興的是,2.6內(nèi)核的epoll比其2.5開發(fā)版本的/dev/epoll簡潔了許多,所以,大部分情況下,強大的東西往往是簡單的。唯一有點麻煩是epoll有2種工作方式T和ET。



            LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket.在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統(tǒng)的select/poll都是這種模型的代表.



            ET (edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epoll告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時導(dǎo)致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會發(fā)送更多的通知(only once),不過在TCP協(xié)議中,ET模式的加速效用仍需要更多的benchmark確認。



            epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統(tǒng)調(diào)用,具體用法請參考http://www.xmailserver.org/linux-patches/nio-improve.html ,

            http://www.kegel.com/rn/也有一個 ... 豢淳橢廊綰問褂昧?



            (4)Leader/follower模式線程pool實現(xiàn),以及和epoll的配合



            .....未完成,主要是要避免過多的epoll_ctl調(diào)用,以及嘗試使用EPOLLONESHOT加速......



            (5)benchmark



            .......未完成

            /*********************************引用結(jié)束******************************/

            [/QUOTE]

            3、epoll的使用方法

            這是epoll的man手冊提供的一個例子,這段代碼假設(shè)一個非阻塞的socket監(jiān)聽listener被建立并且一個epoll句柄kdpfd已經(jīng)提前用epoll_create建立了:

            [CODE]
                   struct epoll_event ev, *events;



                   for(;;) {

                       nfds = epoll_wait(kdpfd, events, maxevents, -1);/*wait for an I/O event. All notes here added by zhoulifa(http://zhoulifa.bokee.com) on 2006-7-6 22:10:00*/



                       for(n = 0; n < nfds; ++n) {

                           if(events[n].data.fd == listener) {/*if listen socket has an I/O, accept the new connect*/

                               client = accept(listener, (struct sockaddr *) &local,

                                               &addrlen);

                               if(client < 0){

                                   perror("accept");

                                   continue;

                               }

                               setnonblocking(client);

                               ev.events = EPOLLIN | EPOLLET;/*EPOLLIN-available for read*/

                               ev.data.fd = client;

                               if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {/*add the new socket into the epoll file descriptors*/

                                   fprintf(stderr, "epoll set insertion error: fd=%d\n",

                                           client);

                                   return -1;

                               }

                           }

                           else

                               do_use_fd(events[n].data.fd);/*read from a socket which has data come*/

                       }

                   }

            [/CODE]
            4、epoll使用方法示意代碼

            以下代碼由chinaunix.net上BBS用戶safedead(http://bbs.chinaunix.net/viewpro.php?uid=407631)提供:



            [CODE]
            static int        s_epfd;//epoll描述字



            {//初始化epoll

                    struct epoll_event        ev;



                    //設(shè)置epoll

                    s_epfd = epoll_create(65535);



                    {//這個過程可以循環(huán)以便加入多個LISTEN套接字進入epoll事件集合

                            //服務(wù)器監(jiān)聽創(chuàng)建

                            rc = listen();//listen參數(shù)這里省略



                            //加入epoll事件集合

                            ev.events = EPOLLIN;

                            ev.data.fd = rc;

                            if (epoll_ctl(s_epfd, EPOLL_CTL_ADD, rc, &ev) < 0) {

                                    fprintf(stderr, "epoll set insertion error: fd=%d", rc);

                                    return(-1);

                            }

                    }

            }



            {//epoll事件處理

                    int        i, nfds, sock_new;

                    struct epoll_event        events[16384];

                    for( ; ; ) {

                            //等待epoll事件

                            nfds = epoll_wait(s_epfd, events, 16384, -1);

                            //處理epoll事件

                            for(i = 0; i < nfds; i++) {

                                    //events.data.fd是epoll事件中彈出的套接字

                                    //接收連接

                                    sock_new = accept(events.data.fd);//accept其它參數(shù)這里省略了

                                    if(0 > sock_new) {

                                            fprintf(stderr, "接收客戶端連接失敗\n");

                                            continue;

                                    }

                            }

                    }

            }

            [/CODE]
            對照safedead和前面的一份代碼,我想大家一定是明白了的。



            5、參考文檔

            Improving (network) I/O performance ...

            http://www.xmailserver.org/linux-patches/nio-improve.html   
            轉(zhuǎn)自:
            posted on 2010-01-07 23:22 chatler 閱讀(380) 評論(0)  編輯 收藏 引用 所屬分類: Socket
            <2025年8月>
            272829303112
            3456789
            10111213141516
            17181920212223
            24252627282930
            31123456

            常用鏈接

            留言簿(10)

            隨筆分類(307)

            隨筆檔案(297)

            algorithm

            Books_Free_Online

            C++

            database

            Linux

            Linux shell

            linux socket

            misce

            • cloudward
            • 感覺這個博客還是不錯,雖然做的東西和我不大相關(guān),覺得看看還是有好處的

            network

            OSS

            • Google Android
            • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
            • os161 file list

            overall

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            久久久久久久免费视频| 久久人人爽人人爽AV片| 久久人人爽人人爽人人爽| 亚洲人成网站999久久久综合| 99精品国产免费久久久久久下载| 久久精品国产亚洲AV嫖农村妇女| 亚洲AV日韩AV永久无码久久| 久久精品9988| 午夜精品久久影院蜜桃| 国产ww久久久久久久久久| 久久夜色精品国产www| 色综合久久无码五十路人妻| 91麻精品国产91久久久久| 色综合久久久久综合体桃花网| 51久久夜色精品国产| 亚洲国产一成人久久精品| 狠狠综合久久综合中文88| 久久国产精品久久国产精品| 久久久久无码国产精品不卡| 国产国产成人久久精品| 亚洲国产精品无码久久久蜜芽| 91精品国产91热久久久久福利 | 久久成人精品| 久久精品欧美日韩精品| 久久久久久免费视频| 久久嫩草影院免费看夜色| 99久久无码一区人妻| 狠狠色婷婷综合天天久久丁香 | 99热精品久久只有精品| 人妻无码αv中文字幕久久琪琪布 人妻无码久久一区二区三区免费 人妻无码中文久久久久专区 | 国产91色综合久久免费| 久久久人妻精品无码一区| 亚洲AV成人无码久久精品老人| 久久99精品久久久久久秒播| 精品国产福利久久久| 国内精品久久九九国产精品| 国产精品一区二区久久国产| 777久久精品一区二区三区无码| 久久久久亚洲Av无码专| 99久久精品影院老鸭窝| 精品一区二区久久|