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

陳碩的Blog

Muduo 多線程模型:一個 Sudoku 服務器演變

陳碩 (giantchen AT gmail)

blog.csdn.net/Solstice

Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

本文以一個 Sudoku Solver 為例,回顧了并發網絡服務程序的多種設計方案,并介紹了使用 muduo 網絡庫編寫多線程服務器的兩種最常用手法。以往的例子展現了 Muduo 在編寫單線程并發網絡服務程序方面的能力與便捷性,今天我們看一看它在多線程方面的表現。

本文代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/

Sudoku Solver

假設有這么一個網絡編程任務:寫一個求解數獨的程序 (Sudoku Solver),并把它做成一個網絡服務。

Sudoku Solver 是我喜愛的網絡編程例子,它曾經出現在《分布式系統部署、監控與進程管理的幾重境界》、《Muduo 設計與實現之一:Buffer 類的設計》、《〈多線程服務器的適用場合〉例釋與答疑》等文中,它也可以看成是 echo 服務的一個變種(《談一談網絡編程學習經驗》把 echo 列為三大 TCP 網絡編程案例之一)。

寫這么一個程序在網絡編程方面的難度不高,跟寫 echo 服務差不多(從網絡連接讀入一個 Sudoku 題目,算出答案,再發回給客戶),挑戰在于怎樣做才能發揮現在多核硬件的能力?在談這個問題之前,讓我們先寫一個基本的單線程版。

協議

一個簡單的以 \r\n 分隔的文本行協議,使用 TCP 長連接,客戶端在不需要服務時主動斷開連接。

請求:[id:]〈81digits〉\r\n

響應:[id:]〈81digits〉\r\n 或者 [id:]NoSolution\r\n

其中[id:]表示可選的 id,用于區分先后的請求,以支持 Parallel Pipelining,響應中會回顯請求中的 id。Parallel Pipelining 的意義見賴勇浩的《以小見大——那些基于 protobuf 的五花八門的 RPC(2) 》,或者見我寫的《分布式系統的工程化開發方法》第 54 頁關于 out-of-order RPC 的介紹。

〈81digits〉是 Sudoku 的棋盤,9x9 個數字,未知數字以 0 表示。

如果 Sudoku 有解,那么響應是填滿數字的棋盤;如果無解,則返回 NoSolution。

例子1:

請求:000000010400000000020000000000050407008000300001090000300400200050100000000806000\r\n

響應:693784512487512936125963874932651487568247391741398625319475268856129743274836159\r\n

例子2:

請求:a:000000010400000000020000000000050407008000300001090000300400200050100000000806000\r\n

響應:a:693784512487512936125963874932651487568247391741398625319475268856129743274836159\r\n

例子3:

請求:b:000000010400000000020000000000050407008000300001090000300400200050100000000806005\r\n

響應:b:NoSolution\r\n

基于這個文本協議,我們可以用 telnet 模擬客戶端來測試 sudoku solver,不需要單獨編寫 sudoku client。SudokuSolver 的默認端口號是 9981,因為它有 9x9=81 個格子。

基本實現

Sudoku 的求解算法見《談談數獨(Sudoku)》一文,這不是本文的重點。假設我們已經有一個函數能求解 Sudoku,它的原型如下

string solveSudoku(const string& puzzle);

函數的輸入是上文的"〈81digits〉",輸出是"〈81digits〉"或"NoSolution"。這個函數是個 pure function,同時也是線程安全的。

有了這個函數,我們以《Muduo 網絡編程示例之零:前言》中的 EchoServer 為藍本,稍作修改就能得到 SudokuServer。這里只列出最關鍵的 onMessage() 函數,完整的代碼見 http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_basic.cc 。onMessage() 的主要功能是處理協議格式,并調用 solveSudoku() 求解問題。

 // muduo/examples/sudoku/server_basic.cc

  const int kCells = 81;

  void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  {
    LOG_DEBUG << conn->name();
    size_t len = buf->readableBytes();
    while (len >= kCells + 2)
    {
      const char* crlf = buf->findCRLF();
      if (crlf)
      {
        string request(buf->peek(), crlf);
        string id;
        buf->retrieveUntil(crlf + 2);
        string::iterator colon = find(request.begin(), request.end(), ':');
        if (colon != request.end())
        {
          id.assign(request.begin(), colon);
          request.erase(request.begin(), colon+1);
        }
        if (request.size() == implicit_cast<size_t>(kCells))
        {
          string result = solveSudoku(request);
          if (id.empty())
          {
            conn->send(result+"\r\n");
          }
          else
          {
            conn->send(id+":"+result+"\r\n");
          }
        }
        else
        {
          conn->send("Bad Request!\r\n");
          conn->shutdown();
        }
      }
      else
      {
        break;
      }
    }
  }

server_basic.cc 是一個并發服務器,可以同時服務多個客戶連接。但是它是單線程的,無法發揮多核硬件的能力。

Sudoku 是一個計算密集型的任務(見《Muduo 設計與實現之一:Buffer 類的設計》中關于其性能的分析),其瓶頸在 CPU。為了讓這個單線程 server_basic 程序充分利用 CPU 資源,一個簡單的辦法是在同一臺機器上部署多個 server_basic 進程,讓每個進程占用不同的端口,比如在一臺 8 核機器上部署 8 個 server_basic 進程,分別占用 9981、9982、……、9988 端口。這樣做其實是把難題推給了客戶端,因為客戶端(s)要自己做負載均衡。再想得遠一點,在 8 個 server_basic 前面部署一個 load balancer?似乎小題大做了。

能不能在一個端口上提供服務,并且又能發揮多核處理器的計算能力呢?當然可以,辦法不止一種。

常見的并發網絡服務程序設計方案

W. Richard Stevens 的 UNP2e 第 27 章 Client-Server Design Alternatives 介紹了十來種當時(90 年代末)流行的編寫并發網絡程序的方案。UNP3e 第 30 章,內容未變,還是這幾種。以下簡稱 UNP CSDA 方案。UNP 這本書主要講解阻塞式網絡編程,在非阻塞方面著墨不多,僅有一章。正確使用 non-blocking IO 需要考慮的問題很多,不適宜直接調用 Sockets API,而需要一個功能完善的網絡庫支撐。

隨著 2000 年前后第一次互聯網浪潮的興起,業界對高并發 http 服務器的強烈需求大大推動了這一領域的研究,目前高性能 httpd 普遍采用的是單線程 reactor 方式。另外一個說法是 IBM Lotus 使用 TCP 長連接協議,而把 Lotus 服務端移植到 Linux 的過程中 IBM 的工程師們大大提高了 Linux 內核在處理并發連接方面的可伸縮性,因為一個公司可能有上萬人同時上線,連接到同一臺跑著 Lotus server 的 Linux 服務器。

可伸縮網絡編程這個領域其實近十年來沒什么新東西,POSA2 已經作了相當全面的總結,另外以下幾篇文章也值得參考。

http://bulk.fefe.de/scalable-networking.pdf

http://www.kegel.com/c10k.html

http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

下表是陳碩總結的 10 種常見方案。其中“多連接互通”指的是如果開發 chat 服務,多個客戶連接之間是否能方便地交換數據(chat 也是《談一談網絡編程學習經驗》中舉的三大 TCP 網絡編程案例之一)。對于 echo/http/sudoku 這類“連接相互獨立”的服務程序,這個功能無足輕重,但是對于 chat 類服務至關重要。“順序性”指的是在 http/sudoku 這類請求-響應服務中,如果客戶連接順序發送多個請求,那么計算得到的多個響應是否按相同的順序發還給客戶(這里指的是在自然條件下,不含刻意同步)。reactor_comparison

UNP CSDA 方案歸入 0~5。5 也是目前用得很多的單線程 reactor 方案,muduo 對此提供了很好的支持。6 和 7 其實不是實用的方案,只是作為過渡品。8 和 9 是本文重點介紹的方案,其實這兩個方案已經在《多線程服務器的常用編程模型》一文中提到過,只不過當時我還沒有寫 muduo,無法用具體的代碼示例來說明。

在對比各方案之前,我們先看看基本的 micro benchmark 數據(前三項由 lmbench 測得):

  • fork()+exit(): 160us
  • pthread_create()+pthread_join(): 12us
  • context switch : 1.5us
  • sudoku resolve: 100us (根據題目難度不同,浮動范圍 20~200us)

方案 0:這其實不是并發服務器,而是 iterative 服務器,因為它一次只能服務一個客戶。代碼見 UNP figure 1.9,UNP 以此為對比其他方案的基準點。這個方案不適合長連接,到是很適合 daytime 這種 write-only 服務。

方案 1:這是傳統的 Unix 并發網絡編程方案,UNP 稱之為 child-per-client 或 fork()-per-client,另外也俗稱 process-per-connection。這種方案適合并發連接數不大的情況。至今仍有一些網絡服務程序用這種方式實現,比如 PostgreSQL 和 Perforce 的服務端。這種方案適合“計算響應的工作量遠大于 fork() 的開銷”這種情況,比如數據庫服務器。這種方案適合長連接,但不太適合短連接,因為 fork() 開銷大于求解 sudoku 的用時。

方案 2:這是傳統的 Java 網絡編程方案 thread-per-connection,在 Java 1.4 引入 NIO 之前,Java 網絡服務程序多采用這種方案。它的初始化開銷比方案 1 要小很多。這種方案的伸縮性受到線程數的限制,一兩百個還行,幾千個的話對操作系統的 scheduler 恐怕是個不小的負擔。

方案 3:這是針對方案 1 的優化,UNP 詳細分析了幾種變化,包括對 accept 驚群問題的考慮。

方案 4:這是對方案 2 的優化,UNP 詳細分析了它的幾種變化。

以上幾種方案都是阻塞式網絡編程,程序(thread-of-control)通常阻塞在 read() 上,等待數據到達。但是 TCP 是個全雙工協議,同時支持 read() 和 write() 操作,當一個線程/進程阻塞在 read() 上,但程序又想給這個 TCP 連接發數據,那該怎么辦?比如說 echo client,既要從 stdin 讀,又要從網絡讀,當程序正在阻塞地讀網絡的時候,如何處理鍵盤輸入?又比如 proxy,既要把連接 a 收到的數據發給連接 b,又要把從連接 b 收到的數據發給連接 a,那么到底讀哪個?(proxy 是《談一談網絡編程學習經驗》中舉的三大 TCP 網絡編程案例之一。)

一種方法是用兩個線程/進程,一個負責讀,一個負責寫。UNP 也在實現 echo client 時介紹了這種方案。另外見 Python Pinhole 的代碼:http://code.activestate.com/recipes/114642/

另一種方法是使用 IO multiplexing,也就是 select/poll/epoll/kqueue 這一系列的“多路選擇器”,讓一個 thread-of-control 能處理多個連接。“IO 復用”其實復用的不是 IO 連接,而是復用線程。使用 select/poll 幾乎肯定要配合 non-blocking IO,而使用 non-blocking IO 肯定要使用應用層 buffer,原因見《Muduo 設計與實現之一:Buffer 類的設計》。這就不是一件輕松的事兒了,如果每個程序都去搞一套自己的 IO multiplexing 機制(本質是 event-driven 事件驅動),這是一種很大的浪費。感謝 Doug Schmidt 為我們總結出了 Reactor 模式,讓 event-driven 網絡編程有章可循。繼而出現了一些通用的 reactor 框架/庫,比如 libevent、muduo、Netty、twisted、POE 等等,有了這些庫,我想基本不用去編寫阻塞式的網絡程序了(特殊情況除外,比如 proxy 流量限制)。

單線程 reactor 的程序結構是(圖片取自 Doug Lea 的演講):

reactor_basic

方案 5:基本的單線程 reactor 方案,即前面的 server_basic.cc 程序。本文以它作為對比其他方案的基準點。這種方案的優點是由網絡庫搞定數據收發,程序只關心業務邏輯;缺點在前面已經談了:適合 IO 密集的應用,不太適合 CPU 密集的應用,因為較難發揮多核的威力。

方案 6:這是一個過渡方案,收到 Sudoku 請求之后,不在 reactor 線程計算,而是創建一個新線程去計算,以充分利用多核 CPU。這是非常初級的多線程應用,因為它為每個請求(而不是每個連接)創建了一個新線程。這個開銷可以用線程池來避免,即方案 8。這個方案還有一個特點是 out-of-order,即同時創建多個線程去計算同一個連接上收到的多個請求,那么算出結果的次序是不確定的,可能第 2 個 Sudoku 比較簡單,比第 1 個先算出結果。這也是為什么我們在一開始設計協議的時候使用了 id,以便客戶端區分 response 對應的是哪個 request。

方案 7:為了讓返回結果的順序確定,我們可以為每個連接創建一個計算線程,每個連接上的請求固定發給同一個線程去算,先到先得。這也是一個過渡方案,因為并發連接數受限于線程數目,這個方案或許還不如直接使用阻塞 IO 的 thread-per-connection 方案2。方案 7 與方案 6 的另外一個區別是一個 client 的最大 CPU 占用率,在方案 6 中,一個 connection 上發來的一長串突發請求(burst requests) 可以占滿全部 8 個 core;而在方案 7 中,由于每個連接上的請求固定由同一個線程處理,那么它最多占用 12.5% 的 CPU 資源。這兩種方案各有優劣,取決于應用場景的需要,到底是公平性重要還是突發性能重要。這個區別在方案 8 和方案 9 中同樣存在,需要根據應用來取舍。

方案 8:為了彌補方案 6 中為每個請求創建線程的缺陷,我們使用固定大小線程池,程序結構如下圖。全部的 IO 工作都在一個 reactor 線程完成,而計算任務交給 thread pool。如果計算任務彼此獨立,而且 IO 的壓力不大,那么這種方案是非常適用的。Sudoku Solver 正好符合。代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_threadpool.cc 后文給出了它與方案 9 的區別。

reactor_threadpool

如果 IO 的壓力比較大,一個 reactor 忙不過來,可以試試 multiple reactors 的方案 9。

方案 9:這是 muduo 內置的多線程方案,也是 Netty 內置的多線程方案。這種方案的特點是 one loop per thread,有一個 main reactor 負責 accept 連接,然后把連接掛在某個 sub reactor 中(muduo 采用 round-robin 的方式來選擇 sub reactor),這樣該連接的所有操作都在那個 sub reactor 所處的線程中完成。多個連接可能被分派到多個線程中,以充分利用 CPU。Muduo 采用的是固定大小的 reactor pool,池子的大小通常根據 CPU 核數確定,也就是說線程數是固定的,這樣程序的總體處理能力不會隨連接數增加而下降。另外,由于一個連接完全由一個線程管理,那么請求的順序性有保證,突發請求也不會占滿全部 8 個核(如果需要優化突發請求,可以考慮方案 10)。這種方案把 IO 分派給多個線程,防止出現一個 reactor 的處理能力飽和。與方案 8 的線程池相比,方案 9 減少了進出 thread pool 的兩次上下文切換。我認為這是一個適應性很強的多線程 IO 模型,因此把它作為 muduo 的默認線程模型。

reactor_multiple

代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_multiloop.cc

server_multiloop.cc 與 server_basic.cc 的區別很小,關鍵只有一行代碼:server_.setThreadNum(numThreads);

$ diff server_basic.cc server_multiloop.cc -up
--- server_basic.cc     2011-06-15 13:40:59.000000000 +0800
+++ server_multiloop.cc 2011-06-15 13:39:53.000000000 +0800
@@ -21,19 +21,22 @@ using namespace muduo::net;
 class SudokuServer
 {
  public:
-  SudokuServer(EventLoop* loop, const InetAddress& listenAddr)
+  SudokuServer(EventLoop* loop, const InetAddress& listenAddr, int numThreads)
     : loop_(loop),
       server_(loop, listenAddr, "SudokuServer"),
+      numThreads_(numThreads),
       startTime_(Timestamp::now())
   {
     server_.setConnectionCallback(
         boost::bind(&SudokuServer::onConnection, this, _1));
     server_.setMessageCallback(
         boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
+    server_.setThreadNum(numThreads);
   }

方案 8 使用 thread pool 的代碼與使用多 reactors 的方案 9 相比變化不大,只是把原來 onMessage() 中涉及計算和發回響應的部分抽出來做成一個函數,然后交給 ThreadPool 去計算。記住方案 8 有 out-of-order 的可能,客戶端要根據 id 來匹配響應。

$ diff server_multiloop.cc server_threadpool.cc -up
--- server_multiloop.cc 2011-06-15 13:39:53.000000000 +0800
+++ server_threadpool.cc        2011-06-15 14:07:52.000000000 +0800
@@ -31,12 +32,12 @@ class SudokuServer
         boost::bind(&SudokuServer::onConnection, this, _1));
     server_.setMessageCallback(
         boost::bind(&SudokuServer::onMessage, this, _1, _2, _3));
-    server_.setThreadNum(numThreads);
   }

   void start()
   {
     LOG_INFO << "starting " << numThreads_ << " threads.";
+    threadPool_.start(numThreads_);
     server_.start();
   }

@@ -68,15 +69,7 @@ class SudokuServer
         }
         if (request.size() == implicit_cast<size_t>(kCells))
         {
-          string result = solveSudoku(request);
-          if (id.empty())
-          {
-            conn->send(result+"\r\n");
-          }
-          else
-          {
-            conn->send(id+":"+result+"\r\n");
-          }
+          threadPool_.run(boost::bind(solve, conn, request, id));
         }
         else
         {
@@ -91,8 +84,23 @@ class SudokuServer
     }
   }

+  static void solve(const TcpConnectionPtr& conn, const string& request, const string& id)
+  {
+    LOG_DEBUG << conn->name();
+    string result = solveSudoku(request);
+    if (id.empty())
+    {
+      conn->send(result+"\r\n");
+    }
+    else
+    {
+      conn->send(id+":"+result+"\r\n");
+    }
+  }
+
   EventLoop* loop_;
   TcpServer server_;
+  ThreadPool threadPool_;
   int numThreads_;
   Timestamp startTime_;
 };

完整代碼見:http://code.google.com/p/muduo/source/browse/trunk/examples/sudoku/server_threadpool.cc

方案 10:把方案 8 和方案 9 混合,既使用多個 reactors 來處理 IO,又使用線程池來處理計算。這種方案適合既有突發 IO (利用多線程處理多個連接上的 IO),又有突發計算的應用(利用線程池把一個連接上的計算任務分配給多個線程去做)。

reactor_hybrid

這種其實方案看起來復雜,其實寫起來很簡單,只要把方案 8 的代碼加一行 server_.setThreadNum(numThreads); 就行,這里就不舉例了。

結語

我在《多線程服務器的常用編程模型》一文中說

總結起來,我推薦的多線程服務端編程模式為:event loop per thread + thread pool。

  • event loop 用作 non-blocking IO 和定時器。
  • thread pool 用來做計算,具體可以是任務隊列或消費者-生產者隊列。

當時(2010年2月)我還說“以這種方式寫服務器程序,需要一個優質的基于 Reactor 模式的網絡庫來支撐,我只用過in-house的產品,無從比較并推薦市面上常見的 C++ 網絡庫,抱歉。

現在有了 muduo 網絡庫,我終于能夠用具體的代碼示例把思想完整地表達出來。

posted on 2011-06-16 12:58 陳碩 閱讀(5940) 評論(2)  編輯 收藏 引用 所屬分類: muduo

評論

# re: Muduo 多線程模型:一個 Sudoku 服務器演變 2011-06-16 21:18 Skill

陳碩 你的文章欣賞價值超過了很多書籍 很敬仰你  回復  更多評論   

# re: Muduo 多線程模型:一個 Sudoku 服務器演變 2011-06-17 23:45 haskell

出書吧,我買:)  回復  更多評論   

<2010年4月>
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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| 亚洲电影天堂av| 亚洲视频在线观看| 欧美午夜视频| 久久久成人精品| 久久躁狠狠躁夜夜爽| 亚洲国产1区| 亚洲美女区一区| 国产精品嫩草99a| 久久久久国内| 免费91麻豆精品国产自产在线观看| 亚洲国产一区二区视频| 日韩天堂在线观看| 国产一区二区三区的电影 | 久久国产精彩视频| 欧美在线一二三区| 亚洲人成网站精品片在线观看| 亚洲人午夜精品| 国产丝袜一区二区三区| 美腿丝袜亚洲色图| 欧美三级电影大全| 久久精品在线视频| 欧美日韩激情网| 久久久久久网| 欧美性猛交xxxx乱大交退制版| 久久精品人人爽| 欧美精品一区二区在线观看| 欧美在线免费看| 欧美精品手机在线| 可以免费看不卡的av网站| 欧美日韩在线电影| 猛干欧美女孩| 国产区精品视频| 亚洲欧洲日产国产网站| 精品88久久久久88久久久| 9久re热视频在线精品| 在线观看精品一区| 亚洲综合精品| 亚洲素人一区二区| 欧美成人精品激情在线观看| 欧美福利视频在线| 午夜天堂精品久久久久| 久久亚洲电影| 欧美专区福利在线| 欧美日韩在线一区二区三区| 牛牛国产精品| 韩国三级在线一区| 亚洲欧美国产三级| 亚洲午夜在线观看| 欧美国产日韩a欧美在线观看| 久久天天躁狠狠躁夜夜爽蜜月| 国产精品久久久久久久久搜平片| 亚洲欧洲综合另类| 在线日韩中文字幕| 久久av一区二区三区漫画| 校园激情久久| 国产精品理论片| 99精品久久久| 在线亚洲一区二区| 欧美人与禽猛交乱配视频| 亚洲国产精品美女| 亚洲精品日日夜夜| 欧美激情综合五月色丁香小说| 欧美成熟视频| 亚洲激情成人| 欧美精品在线播放| 日韩一二在线观看| 亚洲一区二区三区免费观看| 欧美日韩亚洲视频| 日韩一本二本av| 亚洲视频中文字幕| 欧美日韩综合| 一区二区三区精品视频| 午夜一区二区三视频在线观看| 国产精品成人播放| 性刺激综合网| 欧美.com| 一区二区三区导航| 国产精品海角社区在线观看| 亚洲无吗在线| 欧美一级二级三级蜜桃| 国产亚洲精品福利| 久久久久www| 亚洲国产精品精华液2区45| 最新国产成人av网站网址麻豆| 欧美国产精品中文字幕| 日韩亚洲一区二区| 午夜欧美理论片| 国模吧视频一区| 另类酷文…触手系列精品集v1小说| 欧美不卡在线| 中日韩高清电影网| 国产日韩在线看| 欧美激情片在线观看| 亚洲视频在线一区| 久久免费视频在线观看| 亚洲精品欧美一区二区三区| 欧美午夜不卡视频| 久久精品国产v日韩v亚洲| 亚洲电影在线播放| 亚洲一区高清| 一区在线播放| 国产精品白丝av嫩草影院| 欧美综合国产| 亚洲一区二区视频在线观看| 久久亚洲不卡| 亚洲一区二区在线| 好看的日韩视频| 欧美日韩一二三四五区| 欧美自拍偷拍| av不卡免费看| 欧美成人午夜激情在线| 亚洲综合丁香| 亚洲精品综合久久中文字幕| 国产欧美日韩在线播放| 免费欧美网站| 久久久999国产| 亚洲女女做受ⅹxx高潮| 亚洲国产乱码最新视频| 久久久国产精品一区二区中文| aa级大片欧美| 在线观看亚洲视频| 国产日韩一区二区| 国产精品久久久爽爽爽麻豆色哟哟 | 91久久线看在观草草青青| 久久久久青草大香线综合精品| 一本色道久久88综合亚洲精品ⅰ | 欧美激情欧美激情在线五月| 久久激情五月激情| 亚洲免费影视| 制服丝袜激情欧洲亚洲| 亚洲肉体裸体xxxx137| 亚洲电影免费| 欧美大片一区二区| 久久一区视频| 久久亚洲风情| 久久人人看视频| 久久精品视频在线播放| 欧美综合国产| 午夜精品国产精品大乳美女| 亚洲一区二区三区精品在线观看| 亚洲精品视频一区二区三区| 亚洲第一在线综合网站| 18成人免费观看视频| 狠狠色丁香久久婷婷综合丁香| 国产美女一区二区| 国产精品夜夜嗨| 国产日韩欧美在线播放| 国产亚洲欧美另类中文| 国内偷自视频区视频综合| 国产伪娘ts一区| 黑人操亚洲美女惩罚| 伊人久久综合| 亚洲人体一区| 亚洲一区二区三区视频| 亚洲欧美999| 欧美一区2区三区4区公司二百 | 亚洲国产精品ⅴa在线观看| 亚洲国产免费| 亚洲伦伦在线| 亚洲一区二区三区在线| 午夜一区二区三区不卡视频| 欧美一区二区在线播放| 久久精视频免费在线久久完整在线看| 久久久久久夜精品精品免费| 免费在线看一区| 亚洲日本黄色| 亚洲字幕在线观看| 久久久99国产精品免费| 欧美高清视频免费观看| 欧美日韩国内| 国内自拍亚洲| 亚洲精品免费一二三区| 亚洲愉拍自拍另类高清精品| 久久av一区二区| 亚洲国产另类久久久精品极度| av成人免费在线| 久久www成人_看片免费不卡| 欧美欧美午夜aⅴ在线观看| 一区二区三区成人| 亚洲视频导航| 久久一区中文字幕| 亚洲精品国精品久久99热| 亚洲在线1234| 免费久久99精品国产| 国产精品日韩欧美一区二区三区| 在线观看视频一区二区欧美日韩| 中文av一区二区| 美女视频黄 久久| 亚洲午夜av在线| 狂野欧美性猛交xxxx巴西| 国产精品久久久久久久久久三级 | 国产欧美一区二区精品秋霞影院| 亚洲激情电影中文字幕| 欧美亚洲三级| 99精品热视频只有精品10| 久久久91精品国产一区二区三区| 国产精品黄色在线观看| 亚洲精品久久视频| 久久久噜噜噜久久中文字免|