精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Reactor網絡模型核心思想探秘

網絡 網絡管理
reactor網絡模型是網絡編程中非常重要的一種編程思想,本文通過一個簡短的示例試圖講明白reactor網絡編程模型的核心思想。當然,本文的實現還不是很完善,比如在調用回調函數的時候還是傳入了fd,我們是否可以不需要這個參數,徹徹底底地和IO進行分離呢?

在網絡編程系列文章中,我們實現了一個基于epoll的網絡框架,并在此基礎上開發了一個簡單的HTTP服務,在那個系列文章中我們使用了讀、寫兩個buffer將網絡IO和數據的讀寫進行了分離,它們之間的扭轉完全通過epoll事件通知,如果你認真研究過源碼,會發現,所有針對網絡IO的操作都是由事件觸發的。這種基于事件觸發的網絡模型通常我們叫做Reactor網絡模型。

由于網絡編程系列文章中代碼實現相對比較復雜,不太好講清楚。所以,我決定單獨出幾篇文章對那個系列文章進行一些拓展,主要涉及到網絡編程思想和性能測試。

這篇文章我們通過實現一個簡單的網絡框架,來說明Reactor網絡模型實現的一般思路,其本質思想和x-net項目基本上是一樣的,只是在代碼上做了非常大的精簡,理解起來會輕松很多。

首先,我們來看一段代碼

#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>




int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);


    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(struct sockaddr_in));


    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(2048);


    if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {
        perror("bind fail");
        return -1;
    }


    listen(sockfd, 10);


    printf("sock-fd:%d\n", sockfd);


    int epfd = epoll_create(1);


    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;


    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);


    struct epoll_event events[1024] = {0};


    while(1) {
        int nready = epoll_wait(epfd, events, 1024, -1);


        int i = 0;
        for (i = 0; i < nready; i++) {
            int connfd = events[i].data.fd;
            if (events[i].events & EPOLLIN && sockfd == connfd) {
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);


                int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);


                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);


                printf("clientfd: %d\n", clientfd);
            } else if (events[i].events & EPOLLIN) {


                char buffer[10] = {0};


                int count = recv(connfd, buffer, 10, 0);
                if (count == 0) {
                    printf("discounnect\n");


                    epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
                    close(i);


                    continue;
                }


                send(connfd, buffer, count, 0);
                printf("clientfd: %d, count: %d, buffer: %s\n", connfd, count, buffer);
            }
        }
    }
}

熟悉epoll的人應該對上面的代碼比較熟悉,這段代碼的核心在下面的while主循環,如果是當前Server的Socket說明有新的連接進來,調用accept拿到客戶端的fd,將其放在epoll的events中,并注冊EPOLLIN事件,一般我們理解為可讀事件。

如果不是sockfd,說明是客戶端的fd可讀,我們將數據讀出來再原樣發送回去。

上面的代碼存在的主要問題在于,套接字的accept和讀寫操作我們是直接寫在主循環里了,這將會讓代碼的邏輯變得難以琢磨。

對于一個套接字,最直接的操作就是讀和寫。所以,最容易想到的就是將讀和寫分離開。為了實現讀和寫分離我們封裝兩個回調函數,如下:

int recv_callback(int fd, char *buffer, int size);
int send_callback(int fd, char *buffer, int size);

你可以想一下,這兩個函數應該怎么寫?下面是根據原有的邏輯將讀和寫封裝在了recv_callback和send_callback兩個函數中,代碼如下:

int recv_callback(int fd, char *buffer, int size) {
    int count = recv(fd, buffer, size, 0);


    send_callback(fd, buffer, count, 0);


    return count;
}
int send_callback(int fd, char *buffer, int size) {
    int count = send(fd, buffer, size, 0);


    return count;
}

然后,在主循環中就可以這樣使用

int main() {


    ...


    while(1) {
        int nready = epoll_wait(epfd, events, 1024, -1);


        int i = 0;
        for (i = 0; i < nready; i++) {
            int connfd = events[i].data.fd;


            if (events[i].events & EPOLLIN && sockfd == connfd) {
                ...
            } else if (events[i].events & EPOLLIN) {
                char buffer[10] = {0};


                int count = recv_callback(fd, buffer, 10);
                if (count == 0) {
                    printf("disconnect\\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);
                    clise(i);
                    continue;
                }
            }
        }
    } 
}

雖然我們將讀和寫拆成了兩個方法,但讀和寫并沒有分離開,我們在recv_callback中每次收到數據之后調用send_callback將數據原樣又發回去,在這里我們希望recv_callback和send_callback各管各的互不干擾,比如像下面這樣

int recv_callback(int fd, char *buffer, int size) {
    int count = recv(fd, buffer, size, 0);


    return count;
}
int send_callback(int fd, char *buffer, int size) {
    int count = send(fd, buffer, size, 0);


    return count;
}

但這樣明顯也是有問題的,在recv_callback中讀完了之后,如何發送數據呢?這里,我們可以想一下,圍繞著一個套接字都有哪些部分呢?是不是可以設計出一個類似字典的結構,這個字典的key對應的就是套接字,而value對應的就是圍繞套接字相關的各個組件。

我們將recv_callback和send_callback放在了一個conn_channel結構體中,并且設計了兩個buffer,一個用來讀數據,另一個用來發數據,conn_channel便是這個字典對應的value,代碼如下:

#define BUF_LEN   1024


typedef int(*callback)(int fd);


struct conn_channel {
    int fd;


    callback recv_call;
    callback send_call;


    char wbuf[BUF_LEN];
    int wlen;
    char rbuf[BUF_LEN];
    int rlen;
};

其中,fd表示的是當前客戶端套接字。然后我們定義一個數組來表示套接字到套接字value的映射關系,代碼如下:

struct conn_channel conn_map[1024] = {0};

這樣,我們在主循環中,就可以像下面這樣,往conn_map中添加對應的套接字了,代碼如下:

int main() {
    ...


    while(1) {
        int nready = epoll_wait(epfd, events, 1024, -1);


        int i = 0;
        for (i = 0; i < nready; i++) {
            int connfd = events[i].data.fd;


            if (events[i].events & EPOLLIN && sockfd == connfd) {
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);


                int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);


                ev.events = EPOLLIN;
                ev.data.fd = clientaddr;


                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);


                conn_map[clientfd].fd = clientfd;
                conn_map[clientfd].rlen = 0;
                conn_map[clientfd].wlen = 0;
                conn_map[clientfd].recv_call = recv_callback;
                conn_map[clientfd].send_call = send_callback;
                memset(conn_map[clientfd].rbuf, 0, BUF_LEN);
                memset(conn_map[clientfd].wbuf, 0, BUF_LEN);


                printf("clientfd:%d\\n", clientfd);
            } else if (events[i].events & EPOLLIN) {
                ...
            }
        }
    } 
}

在上面的代碼中,每當accept出來一個客戶端的套接字,我們就將它放到conn_map中,設置好讀寫buffer和回調函數。但如果你細心點會發現,recv_callback、send_callback和conn_channel中的回調函數簽名是不一樣的。所以,我們要調整一下這兩個函數的實現,調整之后代碼如下:

int recv_callback(int fd) {
    int count = recv(fd, conn_map[fd].rbuf + conn_map[fd].rlen, BUF_LEN - conn_map[fd].rlen, 0);
    // do something


    memcpy(conn_map[fd].wbuf, conn_map[fd].rbuf, conn_map[fd].rlen);
    conn_map[fd].wlen = conn_map[fd].rlen;
    conn_map[fd].rlen = 0;


    return count;
}
int send_callback(int fd) {
    int count = send(fd, conn_map[fd].wbuffer, conn_map[fd].wlen, 0);


    return count;
}

因為有了conn_map,所以原來傳進來的buffer和size都不需要了,在conn_channel中已經有記錄了。所以只需要一個fd參數就可以了。我們在recv_callback中模擬了回復消息,強行將讀到的數據寫到了wbuffer中。這里補充一下,conn_channel中的rbuffer是用來從套接字中讀數據的,wbuffer表示的是將要發送到套接字的數據。

你可以試著把上面的代碼跑起來,然后你會發現,并沒有按我們的預期執行,send_callback中的send似乎沒有起作用。這是因為我們只是將數據從rbuffer寫到了wbuffer中,而send_callback并沒有機會調用。你可以想一想send_callback放在哪里調用比較合適呢?

在上面的例子中,顯然放在主循環中執行比較合適,在epoll中,EPOLLOUT表示可寫事件,我們可以利用這個事件。在recv_callback執行完之后我們注冊一個EPOLLOUT事件,然后在主循環中我們去監聽EPOLLOUT事件。這樣,當recv_callback將rbuffer的數據復制到wbuffer中之后,send_callback通過EPOLLOUT事件就可以在主循環中得以執行。

為了實現上面的效果我們要修改兩個地方,一個是recv_callback中我們要注冊一下EPOLLOUT事件,代碼如下:

int recv_callback(int fd) {
    int count = recv(fd, conn_map[fd].rbuf + conn_map[fd].rlen, BUF_LEN - conn_map[fd].rlen, 0);
    // do something


    memcpy(conn_map[fd].wbuf, conn_map[fd].rbuf, conn_map[fd].rlen);
    conn_map[fd].wlen = conn_map[fd].rlen;
    conn_map[fd].rlen = 0;


    struct epoll_event ev;
    ev.events = EPOLLOUT;
    ev.data.fd = fd;


    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);


    return count;
}

我們在rbuf拷貝到wbuf之后,給當前fd注冊了EPOLLOUT事件,然后我們在主循環中要處理EPOLLOUT事件,代碼如下:

int main() {
    ...


    while(1) {
        int nready = epoll_wait(epfd, events, 1024, -1);


        int i = 0;
        for (i = 0; i < nready; i++) {
            int connfd = events[i].data.fd;


            if (events[i].events & EPOLLIN && sockfd == connfd) {
                struct sockaddr_in clientaddr;
                socklen_t len = sizeof(clientaddr);


                int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);


                ev.events = EPOLLIN;
                ev.data.fd = clientaddr;


                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);


                conn_map[clientfd].fd = clientfd;
                conn_map[clientfd].rlen = 0;
                conn_map[clientfd].wlen = 0;
                conn_map[clientfd].recv_call = recv_callback;
                conn_map[clientfd].send_call = send_callback;
                memset(conn_map[clientfd].rbuf, 0, BUF_LEN);
                memset(conn_map[clientfd].wbuf, 0, BUF_LEN);


                printf("clientfd:%d\\n", clientfd);
            } else if (events[i].events & EPOLLIN) {
                int count = conn_map[connfd].recv_call(connfd);
                printf("recv-count:%d\\n", count);
            } else if (events[i].events & EPOLLOUT) { // 處理EPOLLOUT事件
                int count  = conn_map[connfd].send_call(connfd);
                printf("send-count:%d\\n", count);
            }
        }
    } 
}

要注意的是,epfd是在main函數中定義的,而我們在recv_callback中有使用,所以我們可以暫時將epfd聲明成一個全局變量,放在外面。

上面的代碼有一個問題,EPOLLOUT事件觸發之后你會發現再向當前fd發送數據,就沒響應了,這是因為epoll事件被我們修改了,為了解決這個問題我們可以在send_callback執行完之后再設置回去,如下:

int send_callback(int fd) {
    int count = send(fd, conn_map[fd].wbuffer, conn_map[fd].wlen, 0);


    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = fd;


    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);


    return count;
}

這樣,我們就將IO操作給屏蔽了,在主循環中我們只關注事件,不同的事件調用不同的回調函數。在對應的回調函數中只做自己該做的,做完之后注冊事件通知其它的回調函數。

但是,上面的代碼還不夠優雅,對于accept和讀事件來講在epoll中都是EPOLLIN事件,這兩個是不是可以合并在一起處理呢?答案是可以的,首先,我們要將accept相關的邏輯給拆出來,拆解之后的代碼如下:

int accept_callback(int fd) {
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);


    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);


    ev.events = EPOLLIN;
    ev.data.fd = clientaddr;


    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);


    conn_map[clientfd].fd = clientfd;
    conn_map[clientfd].rlen = 0;
    conn_map[clientfd].wlen = 0;
    conn_map[clientfd].recv_call = recv_callback;
    conn_map[clientfd].send_call = send_callback;
    memset(conn_map[clientfd].rbuf, 0, BUF_LEN);
    memset(conn_map[clientfd].wbuf, 0, BUF_LEN);


    return clientfd;
}

我們發現,accept_callback和recv_callback以及send_callback的簽名是一樣的,這樣我們可以在conn_channel用一個union,將accept_callback也放到conn_channel中來。如下:

struct conn_channel {
    int fd;


    union {
        callback accept_call;
        callback recv_call;
    } call_t;
    callback send_call;


    char wbuf[BUF_LEN];
    int wlen;
    char rbuf[BUF_LEN];
    int rlen;
};

在主循環中,我們就可以先給sockfd注冊好accept回調函數,然后我們只需要在主循環中保留兩個邏輯就可以了,代碼如下:

int main() {
    int sockfd = create_serv(9000);
    if (sockfd == -1) {
        perror("create-server-fail");
        return -1;
    }


    make_nonblocking(sockfd);


    epfd = epoll_create1(1);


    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;


    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);


    struct epoll_event events[1024] = {0}; 


    conn_map[sockfd].rlen = 0;
    conn_map[sockfd].wlen = 0;
    conn_map[sockfd].fd = sockfd;
    conn_map[sockfd].call_t.accept_call = accept_callback;
    conn_map[sodkfd].send_call = send_callback;
    memset(conn_map[sockfd].rbuf, 0, BUF_LEN);
    memset(conn_map[sockfd].wbuf, 0, BUF_LEN);


    while(1) {
        int nready = epoll_wait(epfd, events, 1024, -1);


        int i = 0;
        for (i = 0; i < nready; i++) {
            int connfd = events[i].data.fd;
            if (events[i].events & EPOLLIN) {
                int count = conn_map[connfd].call_t.recv_call(connfd);
                printf("recv-count:%d\\n", count);
            } else if (events[i].events & EPOLLOUT) {
                int count  = conn_map[connfd].send_call(connfd);
                printf("send-count:%d\\n", count);
            }
        }
    } 
}

你可以想一下,我們注冊的是call_t.accept_call,但在調用的時候確是call_t.recv_call,為什么這樣可行?

我們在網絡編程系列文章中,單獨為accept抽象出了一個對象,你可以對比一下這兩種實現方式,看看它們有什么區別?在系列文件中我們為什么要單獨抽象出一個accepter對象呢?

可以看到,最后主循環中的邏輯,只有兩個分支,這兩個分支代表了兩種事件,這種通過事件驅動的網絡模型便是Reactor網絡模型。本文為了容易理解,將代碼進行了精簡。在實際的工程中我們還要考慮諸多情況。比如,上面的代碼只支持epoll,我們是不是可以將事件驅動相關的代碼抽象成單獨的組件,讓其可以支持其它的事件模型。

本文雖然代碼簡單,但Reactor網絡模型的實現基本上都逃脫不了這個套路,只是在此基礎上可能會將各個部分進行單獨的封裝,比如我們在網絡編程系列文章中就將channel和map進行了抽象,讓它能適配各種場景。

總結

reactor網絡模型是網絡編程中非常重要的一種編程思想,本文通過一個簡短的示例試圖講明白reactor網絡編程模型的核心思想。當然,本文的實現還不是很完善,比如在調用回調函數的時候還是傳入了fd,我們是否可以不需要這個參數,徹徹底底地和IO進行分離呢?

責任編輯:武曉燕 來源: 程序員班吉
相關推薦

2022-05-12 09:00:50

動態規劃算法項目

2022-05-09 09:03:04

SQL數據流數據

2015-08-26 09:18:16

云原生云原生機制云原生框架

2020-12-01 07:08:23

Linux網絡I

2024-05-31 08:10:58

Netty線程模型多路復用模型

2025-03-21 00:00:05

Reactor設計模式I/O 機制

2010-08-18 10:13:55

IntentAndroid

2023-12-06 09:33:54

Reactor網絡

2010-01-27 17:38:58

Windows Emb

2010-03-12 17:09:18

2022-01-04 11:11:32

Redis單線程Reactor

2024-04-18 09:34:28

Reactor項目異步編程

2023-02-01 18:31:03

陳峰 數倉寶貝庫

2024-02-27 22:31:00

Feign動態代理核心

2018-07-20 14:30:15

2010-08-27 10:41:41

iPhone核心應用程序

2023-11-30 11:39:52

Rust生態框架

2022-09-29 15:39:10

服務器NettyReactor

2010-09-26 14:08:41

Java垃圾回收

2021-06-16 14:18:37

NettyReactor線程模型
點贊
收藏

51CTO技術棧公眾號

国产精品专区一| 国产毛片精品久久| 久久99国产精品久久99大师| 久久综合狠狠综合久久激情 | 久久视频在线免费观看| 欧美日韩成人免费视频| 中文字幕av久久爽一区| 精品孕妇一区二区三区| 香蕉一区二区| 亚洲欧美激情插| 国产精品日本精品| 91网站免费视频| 日韩伦理福利| youjizz国产精品| 欧美成人小视频| 最新免费av网址| 99免在线观看免费视频高清| 久久精品女人天堂| 精品一区二区亚洲| 国产精品久久久久一区二区三区共| 亚洲免费电影在线观看| 免费特级黄色片| 99久久久久成人国产免费| 色综合咪咪久久网| 欧美日韩黄色一区二区| 亚洲视频在线二区| 亚洲性在线观看| 欧美激情黄色片| 欧美卡1卡2卡| 一级黄色录像免费看| 一级黄色大毛片| 国产日韩高清一区二区三区在线| 日韩理论片久久| 久久久久亚洲av片无码v| www国产在线观看| 国产一区视频导航| 久久亚洲综合国产精品99麻豆精品福利| 北京富婆泄欲对白| 亚洲啊v在线| 久久女同性恋中文字幕| yy111111少妇影院日韩夜片| 国产主播在线观看| 日韩极品少妇| 精品国产一区二区三区av性色 | 欧美性猛交xxxxx水多| 麻豆传媒一区二区| 国产免费a视频| 日韩一区三区| 亚洲另类xxxx| 在线精品视频播放| 天堂va欧美ⅴa亚洲va一国产| 亚洲综合一二区| 久久伦理网站| 久草热在线观看| 亚洲91久久| 欧美精品一区二区高清在线观看 | 在线视频不卡国产| 国产一区二区三区三州| 久久久久久久久丰满| 中文字幕日韩综合av| 亚洲在线观看网站| gogo久久| 国产清纯白嫩初高生在线观看91 | 久久国产精品网| 深夜福利视频在线免费观看| 国产精品成人一区二区网站软件| 亚洲国产免费av| 久久久久久久久久久久久国产精品| 可以在线观看的av| 国产在线视频一区二区| 91热福利电影| 六月丁香婷婷综合| 91不卡在线观看| 久久高清视频免费| 青青草原免费观看| 欧美亚洲高清| 精品国产成人在线影院| 一级黄色电影片| 99只有精品| 欧美日韩精品在线| 青青草免费在线视频观看| 免费av在线电影| 中日韩免费视频中文字幕| 激情视频在线观看一区二区三区| 91精品中文字幕| 国产美女精品人人做人人爽| 国产91对白在线播放| 国产精品视频一区二区三 | 天天躁日日躁aaaa视频| 日韩精品一区二区三区中文 | 国自产拍偷拍福利精品免费一 | 成人看片黄a免费看在线| 国产成人综合精品| 日本一区二区不卡在线| 久久精品免费一区二区三区| 欧美福利小视频| 黄色录像免费观看| 日韩情爱电影在线观看| 欧美激情国产精品| 亚洲国产精品无码久久久| 激情综合亚洲| 欧美成人三级视频网站| 99视频在线看| 影音先锋日韩精品| 久久精品精品电影网| 18精品爽国产三级网站| 国产一区二区三区电影在线观看| 亚洲国产精彩中文乱码av在线播放| 国产大片一区二区三区| 伦理一区二区| www.久久撸.com| 欧美极品jizzhd欧美18| 亚洲网站视频| 国产专区欧美专区| 一道本在线视频| 成人黄色777网| 99久久免费国| eeuss影院在线播放| 国产精品午夜在线观看| 女人被男人躁得好爽免费视频| a级网站在线播放| 色哟哟一区二区| 成人3d动漫一区二区三区| 黄色漫画在线免费看| 精品美女国产在线| 北条麻妃在线一区| 欧美va在线观看| 一本在线高清不卡dvd| 在线视频观看一区二区| 欧美日韩激情在线一区二区三区| 一本色道久久综合亚洲精品小说| 美国黄色特级片| 一级全黄裸体片| 六月婷婷综合| 欧美午夜精品一区二区三区 | 欧美aaaaaa午夜精品| 国产精品国产亚洲伊人久久| 在线观看黄色国产| 久久众筹精品私拍模特| 亚洲人精品午夜射精日韩| 日韩成人视屏| 麻豆乱码国产一区二区三区| 亚洲午夜精品久久久| 久久久精品综合| 香蕉久久夜色| 性欧美ⅴideo另类hd| 亚洲成人自拍网| 青青草原av在线播放| 国产成人一二| 国产午夜精品全部视频在线播放| 小向美奈子av| 毛片不卡一区二区| 国产精品福利视频| 国产中文在线观看| 亚洲精品乱码久久久久久| 一二三四视频社区在线| 国产ts一区| 午夜伦理精品一区| 一道本在线视频| 一色桃子久久精品亚洲| 国产二区视频在线| 国产一区二区在线视频你懂的| 欧美精品videosex牲欧美| 波多野结衣人妻| 国产日韩欧美精品综合| 日韩 欧美 视频| 国内精品偷拍| 欧美一级高清免费| 国产青青草在线| 亚洲一区国产视频| 最新天堂中文在线| 日韩欧美天堂| 国产成人短视频| 97最新国自产拍视频在线完整在线看| 欧美吻胸吃奶大尺度电影| 黑人と日本人の交わりビデオ| 麻豆一区二区在线| 国产人妻互换一区二区| 日韩在线一区视频| 免费高清视频在线一区| 日韩欧美在线123| 中文字幕在线看高清电影| 久久一区国产| 国产在线一区二| 精品黄色免费中文电影在线播放 | 手机看片福利日韩| 超碰精品在线观看| 久久久国产精品免费| 亚洲h视频在线观看| 国产精品人成在线观看免费| 三级av免费看| 久久国产99| 超碰97免费观看| 日本久久成人网| 久久久影视精品| www.av导航| 亚洲欧美另类小说视频| 丝袜熟女一区二区三区 | 亚洲午夜精品17c| 一区二区精品免费| 国产成人在线影院| 日本xxx免费| 夜色77av精品影院| 欧美亚洲另类在线| 麻豆网在线观看| 欧美人与禽zozo性伦| 久久久久久国产精品免费播放| 韩国精品在线观看| 中文有码久久| 一区二区美女| 99re资源| 国产成人a视频高清在线观看| 欧美第一页在线| 大片免费播放在线视频| 亚洲电影天堂av| 国产情侣一区二区| 欧洲人成人精品| 中国毛片在线观看| 国产白丝精品91爽爽久久| 日韩精品一区二区在线视频| av伊人久久| 久久精品久久精品国产大片| 日本一区二区三区视频在线看 | 久久奇米777| 亚洲女则毛耸耸bbw| 精品一区二区三区视频在线观看| 亚洲sss视频在线视频| 久久黄色免费看| 激情五月***国产精品| 自拍偷拍99| 不卡中文字幕| 欧美一区亚洲二区| 成人在线网站| 91精品国产高清久久久久久91| 亚洲欧美日韩成人在线| 色婷婷国产精品| 国产夫妻性爱视频| 天堂影院一区二区| 国产精品专区在线| 欧美日韩1区2区3区| 激情小说网站亚洲综合网 | 国产欧美一区二区三区另类精品 | 久久在线91| 国内性生活视频| 成人毛片在线| 欧美一区二区综合| 国产精品欧美日韩一区| 欧美欧美一区二区| 日韩成人在线电影| 欧美日韩福利在线观看| 亚洲色偷精品一区二区三区| 精品国产免费人成在线观看| 亚洲av无码一区二区三区性色 | 凸凹人妻人人澡人人添| 精品国产伦一区二区三区观看方式 | 中文字幕一区二区三区久久网站| 亚洲综合网中心| 久久精品一区二区不卡| 国产系列第一页| 欧美1区免费| 日韩欧美亚洲v片| 日韩欧美一级| 国产传媒一区| 日本综合视频| 国产精品黄页免费高清在线观看| 日韩不卡在线| 成人乱人伦精品视频在线观看| cao在线视频| 7777kkkk成人观看| 欧美色999| 国产精品网红直播| 国产精品3区| 国产成人精品一区二区在线| 国产极品久久久久久久久波多结野| 国产精品成人v| 日本欧美在线| 一本在线高清不卡dvd| 国模无码视频一区二区三区| 中日韩视频在线观看| 亚洲欧美久久234| 琪琪久久久久日韩精品| 日本免费高清一区二区| 91日韩视频| 超碰成人免费在线| 日韩精品久久久久久| 91精品国产91久久久久麻豆 主演| 日韩一级在线| 少妇一级淫免费播放| 国产成人免费xxxxxxxx| 一二三不卡视频| 成人免费在线播放视频| 成人免费看片98| 在线免费观看一区| 国产v在线观看| 538在线一区二区精品国产| 少妇高潮av久久久久久| 大桥未久av一区二区三区| 337p粉嫩色噜噜噜大肥臀| 欧美一级生活片| 国产又黄又粗又猛又爽| 欧美色综合网站| 91porny九色| 欧美成人在线直播| 第一福利在线| 久久青草福利网站| 亚洲人体在线| 国产在线观看一区二区三区 | 国产成人在线网址| 国产色婷婷亚洲99精品小说| 中文字幕在线有码| 在线观看av不卡| 婷婷av一区二区三区| 亚洲国产成人久久综合一区| 91社区在线观看| 97在线观看视频国产| 国产日本亚洲| 亚洲 国产 欧美一区| 在线视频亚洲| 日本wwww色| 专区另类欧美日韩| 无码人妻熟妇av又粗又大| 欧美精品一区男女天堂| 国产黄a三级三级三级av在线看 | 中文一区一区三区免费| 久久国产日韩| 国产一级伦理片| 国产成人在线网站| 久久视频一区二区三区| 色综合欧美在线视频区| 手机看片1024日韩| 欧美日韩一区二区在线| 精品人妻一区二区三区免费| 免费人成在线不卡| 3d动漫精品啪啪一区二区下载 | 日韩影视一区二区三区| 色综合色狠狠天天综合色| 视频福利在线| 69久久夜色精品国产69乱青草| 成人看片爽爽爽| 免费在线看黄色片| 福利电影一区二区三区| 欧美成人一二三区| 亚洲成a天堂v人片| 亚洲第一大网站| 色综合色综合网色综合| 国产精品欧美一区二区三区不卡| 亚洲一区二区三区免费观看| 免费观看在线色综合| 林心如三级全黄裸体| 欧美日韩亚洲丝袜制服| 亚洲图片88| 久久露脸国产精品| 日韩有吗在线观看| 欧美人与动牲交xxxxbbbb| 亚洲综合激情| 婷婷免费在线观看| 国产精品系列在线| 国产精品一区二区人人爽| 久久久av电影| julia中文字幕一区二区99在线| 波多野结衣与黑人| 成人黄页毛片网站| 在线观看国产亚洲| 日韩一区二区三区四区五区六区| 四虎精品成人免费网站| 欧美中文字幕视频在线观看| 警花av一区二区三区| 欧美一区二区三区在线播放| 全国精品久久少妇| 星空大象在线观看免费播放| 欧美日韩亚洲视频| 成人免费在线视频网| 成人夜晚看av| 国内精品美女在线观看| 9.1在线观看免费| 欧美日韩在线影院| av中文资源在线| 91九色视频在线观看| 成人在线免费小视频| 欧美一级片中文字幕| 中文字幕亚洲电影| 国产综合视频在线| 久久精品国产久精国产思思| 日韩不卡在线视频| 精品人妻一区二区三区四区在线| 国产传媒日韩欧美成人| 日本一级淫片色费放| 最新91在线视频| 色香欲www7777综合网| 中文字幕一区二区三区四区五区| 国产成人在线观看免费网站| 国产成人在线视频观看| 爱福利视频一区| 免费看久久久| 天天摸天天舔天天操| 欧美日韩国产色视频| 黄色网页网址在线免费| 精品福利影视| 激情综合一区二区三区| 国产手机在线视频|