青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 297,  comments - 15,  trackbacks - 0
Linux下各類TCP網絡服務器的實現源代碼

大家都知道各類網絡服務器程序的編寫步驟,并且都知道網絡服務器就兩大類:循環服務和并發服務。這里附上源代碼來個小結吧。

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

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

一個循環TCP服務源代碼(因為用fork進行多進程服務了,所以這種服務現實中也有用)如下:
[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: 循環tcp服務端程序
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-04 22:00:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Google.com
*********************************************************************/
int main(int argc, char ** argv)
{
    int sockfd,new_fd; /* 監聽socket: sock_fd,數據傳輸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);/*等待子進程結束,清除子進程所占用資源*/
    }
}
/*----------------------源代碼結束--------------------------------------------*/
[/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 /*每次最大數據傳輸量 */
/*********************************************************************
*filename: cycletcpclient.c
*purpose: 循環tcp客戶端程序
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-04 22:20:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Google.com
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
*********************************************************************/

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

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

一個典型IO多路復用的單進程并發服務器流程如下:
/*IO多路復用并發服務流程圖*/
[IMG]http://zhoulifa.bokee.com/inc/simpleselect.png[/IMG]
下面是一個演示IO多路復用的源程序,是一個端口轉發程序,但它的用處相當大,實際應用中的各類代理軟件或端口映射軟件都是基于這樣的代碼的,比如Windows下的WinGate、WinProxy等都是在此基礎上實現的。源代碼如下:
[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))

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

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]);

    /*建立監聽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);

        /*把監聽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);
        }

        /*把有異常數據的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 {
                /*關閉原有連接,把新連接作為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;
}
/*----------------------源代碼結束--------------------------------------------*/
[/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]
當有用戶訪問本機的8000端口時,MyProxy程序將把此請求轉發到172.16.100.218主機的80端口,即實現了一個http代理。

關于select函數:
其函數原型為:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
此函數的功能是由內核檢測在timeout時間內,是否有readfds,writefds,exceptfds三個句柄集(file descriptors)里的某個句柄(file descriptor)的狀態符合尋求,即readfds句柄集里有句柄可讀或writefds句柄集里有可寫或exceptfds句柄集里有例外發生,任何一個有變化函數就立即返回,返回值為timeout發生狀態變化的句柄個數。
n是所有readfds,writefds,exceptfds三個句柄集(file descriptors)里編號最大值加1。比如:要檢測兩個socket句柄fd1和fd2在timeout時間內是否分別可讀和可寫就可以這樣:
先把兩個句柄集(file descriptors)清零:
        FD_ZERO (&readfds);
        FD_ZERO (&writefds);
然后把fd1加入讀檢測集:
        FD_SET (fd1, &readfds);
然后把fd2加入寫檢測集:
        FD_SET (fd2, &writefds);
再給timeout設置值,timeout是這樣的一個結構:
              struct timeval {
                  long    tv_sec;         /* seconds */
                  long    tv_usec;        /* microseconds */
              };
你可以這樣賦值:
        timeout.tv_sec=1;
        timeout.tv_uec=0;
表示檢測在1秒鐘內是否有句柄狀態發生變化。
如果有句柄發生變化,就可以用FD_ISSET檢測各個句柄,比如:
                FD_ISSET (fd1, &readfds);//檢測是否fd1變成可讀的了
                FD_ISSET (fd2, &writefds);//檢測是否fd2變成可寫的了
示意程序代碼如下:
[CODE]
/*----------------------示意代碼開始--------------------------------------------*/
    fd1 = socket();//創建一個socket
    fd2 = socket();//創建一個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("系統錯誤,select出錯,錯誤代碼:%d, 錯誤信息:%s", errno, strerror(errno));}
        else if(ret == 0) {printf("select超時返回,沒有任何句柄狀態發生變化!");}
        //有句柄狀態發生了變化
        if(FD_ISSET(fd1, &readfds)) {
            fd1有數據可讀;
            fd1里的數據被讀出來;
        }
        if(FD_ISSET(fd2, &writefds)) {
            fd2可寫;
            fd2里發送數據給對方;
        }
    }
/*----------------------示意代碼結束--------------------------------------------*/
[/CODE]
經常用到的幾個自定義函數:
1、開啟監聽的函數
[CODE]
/*----------------------源代碼代碼開始--------------------------------------------*/
int
OpenSCPServer(int port, int total, int sendbuflen, int recvbuflen, int blockORnot, int reuseORnot)    {
/*************************關于本函數************************************
*function_name: OpenSCPServer
*參數說明:port整數型監聽端口號,total整數型監聽個數,sendbuflen整數型發送緩沖區大小
*          recvbuflen整數型接收緩沖區大小,blockORnot整數型是否阻塞,reuseORnot整數型是否端口重用
*purpose: 用來建立一個tcp服務端socket
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:00:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量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;
}
/*----------------------源代碼代碼結束--------------------------------------------*/
[/CODE]
2、連接服務器的函數
[CODE]
/*----------------------源代碼代碼開始--------------------------------------------*/
int
ConnectSCPServer(char * serverip, int serverport, int blockORnot)    {
/*************************關于本函數************************************
*function_name: ConnectSCPServer
*參數說明:serverip服務器IP地址或主機名,serverport服務器端口,blockORnot整數型是否阻塞
*purpose: 用來建立一個tcp客戶端socket
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:40:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量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;
}
/*----------------------源代碼代碼結束--------------------------------------------*/
[/CODE]
3、發送數據函數Send
[CODE]
/*----------------------源代碼代碼開始--------------------------------------------*/
int
Send(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************關于本函數************************************
*function_name: Send
*參數說明:sock整數型socket,buf待發送的內容,size要發送的大小,flag發送選項,timeout超時時間值
*purpose: 用來通過一個socket在指定時間內發送數據
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 20:58:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量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;
}
/*----------------------源代碼代碼結束--------------------------------------------*/
[/CODE]
4、接收數據函數Recv
[CODE]
/*----------------------源代碼代碼開始--------------------------------------------*/
int
Recv(int sock, char * buf, size_t size, int flag, int timeout)    {
/*************************關于本函數************************************
*function_name: Recv
*參數說明:sock整數型socket,buf接收數據的緩沖區,size要接收數據的大小,flag接收選項,timeout超時時間值
*purpose: 用來從一個socket在指定時間內讀取數據
*tidied by: zhoulifa(zhoulifa@163.com) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2006-07-05 21:10:00
*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途
* 但請遵循GPL
*Thanks to: Paul Sheer 感謝Paul Sheer在select_tut的man手冊里提供了這份源代碼
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
*Note:要使用此函數需要自定義一個全局變量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]
最后需要說明的是:我這里講到的源程序并不能實際地作為一個產品程序來用,實際情況下可能會有其它許多工作要做,比如可能要建立共享隊列來存放 socket里讀到的消息,也可能把發送消息先進行排隊然后再調用Send函數。還有,如果不是全數字,在發送前一定要htonl轉換為網絡字節序,同理接收到后一定要先ntohl由網絡字節序轉換為主機字節序,否則對方發送過來的0x00000001在你這里可能是0x00010000,因為高低位順序不同。 
Linux 2.6內核中提高網絡I/O性能的新方法epoll



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

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



1、為什么select是落后的?

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

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

也就是說,如果想要同時檢測1025個句柄的可讀狀態是不可能用select實現的?;蛘咄瑫r檢測1025個句柄的可寫狀態也是不可能的。

其次,內核中實現select是用輪詢方法,即每次檢測都會遍歷所有FD_SET中的句柄,顯然,select函數執行時間與FD_SET中的句柄個數有一個比例關系,即select要檢測的句柄數越多就會越費時。

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



/************關于本文檔********************************************

*filename: Linux 2.6內核中提高網絡I/O性能的新方法epoll

*purpose: 補充“Linux下各類TCP網絡服務器的實現源代碼”一文的不足之處

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

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

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

*Note: 任何人可以任意復制代碼并運用這些文檔,當然包括你的商業用途

* 但請遵循GPL

*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力

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



2、2.6內核中提高I/O性能的新方法epoll



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

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



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

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

Linux2.6內核epoll介紹---我的blog 2005/3/30



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



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



(1)導言:



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



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



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



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



(2)epoll的優點



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



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



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



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



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



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



<4>內核微調



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



(3)epoll的使用



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



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



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



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

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



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



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



(5)benchmark



.......未完成

/*********************************引用結束******************************/

[/QUOTE]

3、epoll的使用方法

這是epoll的man手冊提供的一個例子,這段代碼假設一個非阻塞的socket監聽listener被建立并且一個epoll句柄kdpfd已經提前用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;



        //設置epoll

        s_epfd = epoll_create(65535);



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

                //服務器監聽創建

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



                //加入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其它參數這里省略了

                        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   
轉自:
posted on 2010-01-07 23:22 chatler 閱讀(382) 評論(0)  編輯 收藏 引用 所屬分類: Socket
<2008年7月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

常用鏈接

留言簿(10)

隨筆分類(307)

隨筆檔案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

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

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

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美日韩久久不卡| 一区二区国产在线观看| 在线午夜精品自拍| 国产日韩av一区二区| 久久精品国产91精品亚洲| 午夜精品久久久久久99热软件 | 欧美精品在线视频| 亚欧成人在线| 国产日韩欧美日韩| 久久精品视频在线看| 亚洲电影av| 亚洲福利视频一区| 免费观看成人www动漫视频| 欧美成人一区在线| 99re6热只有精品免费观看| 欧美日韩大片一区二区三区| 最新日韩av| 欧美中文在线观看国产| 欧美激情亚洲另类| 蜜臀a∨国产成人精品| 久久婷婷久久| 狠狠干狠狠久久| 亚洲一区二区三区四区五区午夜 | 亚洲一区美女视频在线观看免费| 国产亚洲视频在线观看| 一区二区三区免费看| 一本到12不卡视频在线dvd| 久久免费视频在线| 亚洲国产精品免费| 亚洲高清资源| 欧美日韩国产三区| 99国内精品久久久久久久软件| 99综合电影在线视频| 欧美日韩国产免费| 亚洲视频免费在线| 性欧美暴力猛交69hd| 国产精品麻豆va在线播放| 亚洲欧美综合精品久久成人 | 亚洲伊人观看| 久久综合伊人77777蜜臀| 亚洲国产精品女人久久久| 麻豆精品视频在线观看视频| 亚洲黄色在线| 久久免费99精品久久久久久| 亚洲日本va在线观看| 亚洲一区二区在线视频| 激情欧美一区二区三区在线观看| 欧美伦理一区二区| 久久久九九九九| 亚洲视频在线免费观看| 欧美成人a∨高清免费观看| 亚洲免费视频在线观看| 在线成人av.com| 国产精品揄拍一区二区| 欧美国产一区二区在线观看 | 国产精品色婷婷久久58| 欧美777四色影视在线| 欧美在线影院在线视频| 亚洲欧美精品suv| 亚洲另类视频| 亚洲国产另类精品专区| 国内外成人免费激情在线视频| 国产精品高潮呻吟久久av黑人| 欧美国产日韩在线观看| 奶水喷射视频一区| 美国十次了思思久久精品导航| 久久精品亚洲一区二区| 久久久久综合网| 欧美激情免费观看| 欧美精品电影| 国产精品久久久久久久久久ktv| 欧美理论电影网| 国产精品盗摄一区二区三区| 欧美日韩调教| 国产一区二区高清| 91久久夜色精品国产网站| 99国产精品99久久久久久粉嫩 | 一区二区电影免费观看| 亚洲麻豆av| 亚欧成人在线| 欧美黄色影院| 国产午夜久久久久| 亚洲精品久久久久久一区二区| 日韩亚洲精品电影| 久久精品免费观看| 91久久夜色精品国产九色| 先锋影音久久| 欧美日韩国产综合在线| 国产欧美亚洲视频| 一区二区三区欧美激情| 久久国产夜色精品鲁鲁99| 亚洲黄色一区二区三区| 欧美一区二区在线播放| 欧美日韩在线播放三区| 亚洲高清毛片| 久久人人爽人人| 亚洲欧美在线播放| 欧美日韩视频在线第一区| 亚洲欧洲综合另类| 亚洲成人资源| 久久综合九色九九| 久久精品久久99精品久久| 国产欧美精品日韩区二区麻豆天美| 亚洲精品一区二| 亚洲精品一区二区三区婷婷月 | 国产偷自视频区视频一区二区| 一区二区日韩欧美| 日韩一级在线观看| 欧美视频官网| 欧美一级专区| 久久精品论坛| 亚洲黑丝在线| 亚洲乱码精品一二三四区日韩在线| 久久久久久综合网天天| 最新日韩在线| 一区二区日本视频| 国产一区激情| 亚洲人成在线播放| 国产麻豆9l精品三级站| 久久资源av| 欧美天堂亚洲电影院在线观看 | 亚洲国产一区二区三区a毛片| 欧美高清视频在线| 久久精品在线观看| 欧美另类在线播放| 久久九九精品99国产精品| 欧美精品首页| 老司机精品视频一区二区三区| 欧美精品久久久久a| 久久久久九九视频| 欧美巨乳波霸| 亚洲国产欧美久久| 国产一区二区观看| 日韩一区二区电影网| 亚洲高清在线| 欧美在线综合视频| 欧美综合第一页| 国产精品久久久久久久7电影| 麻豆精品视频在线| 亚洲永久免费观看| 亚洲一区亚洲二区| 欧美精品www在线观看| 欧美二区在线| 亚洲区免费影片| 美女啪啪无遮挡免费久久网站| 久久亚洲综合| 激情综合视频| 欧美~级网站不卡| 欧美激情一区二区三区在线视频观看 | 每日更新成人在线视频| 欧美bbbxxxxx| 亚洲日本中文字幕区| 欧美精品七区| 亚洲一区二区三区三| 亚洲欧美日韩成人高清在线一区| 欧美日韩在线播放一区| 亚洲午夜激情在线| 久久精品视频一| 亚洲国产精品一区二区三区| 久久亚洲综合色| 在线亚洲成人| 麻豆91精品| 亚洲嫩草精品久久| 亚洲电影在线| 国产精品久久一卡二卡| 欧美在线免费观看亚洲| 亚洲美女毛片| 久久男人av资源网站| 亚洲夜晚福利在线观看| 在线观看欧美精品| 国产女优一区| 欧美日韩国产大片| 免费观看成人鲁鲁鲁鲁鲁视频| 亚洲午夜性刺激影院| 亚洲精品乱码久久久久久蜜桃麻豆| 欧美一区三区二区在线观看| 日韩亚洲欧美成人一区| 91久久极品少妇xxxxⅹ软件| 国产午夜精品全部视频在线播放| 欧美91精品| 欧美国产先锋| 欧美精品七区| 欧美午夜寂寞影院| 欧美涩涩网站| 亚洲视频 欧洲视频| 亚洲精美视频| 亚洲免费精品| 亚洲一级在线观看| 亚洲综合久久久久| 久久9热精品视频| 久久av一区二区三区亚洲| 久久久噜噜噜久久久| 蜜臀久久99精品久久久久久9 | 欧美日本成人| 国产精品久久久久影院色老大| 国产精品视频男人的天堂| 国产一区二区三区在线免费观看| 亚洲韩国一区二区三区| 日韩视频一区二区| 亚洲美女黄色|