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

圖解 | 深入理解高性能網絡開發路上的絆腳石 - 同步阻塞網絡 IO

網絡 通信技術
在網絡開發模型中,有一種非常易于開發同學使用的方式,那就是同步阻塞的網絡 IO(在 Java 中習慣叫 BIO)。

[[386495]]

本文轉載自微信公眾號「開發內功修煉」,作者張彥飛allen 。轉載本文請聯系開發內功修煉公眾號。

在網絡開發模型中,有一種非常易于開發同學使用的方式,那就是同步阻塞的網絡 IO(在 Java 中習慣叫 BIO)。

例如我們想請求服務器上的一段數據,那么 C 語言的一段代碼 demo 大概是下面這樣:

  1. int main() 
  2.  int sk = socket(AF_INET, SOCK_STREAM, 0); 
  3.  connect(sk, ...) 
  4.  recv(sk, ...) 

但是在高并發的服務器開發中,這種網絡 IO 的性能奇差。因為

1.進程在 recv 的時候大概率會被阻塞掉,導致一次進程切換

2.當連接上數據就緒的時候進程又會被喚醒,又是一次進程切換

3.一個進程同時只能等待一條連接,如果有很多并發,則需要很多進程

如果用一句話來概括,那就是:同步阻塞網絡 IO 是高性能網絡開發路上的絆腳石! 俗話說得好,知己知彼方能百戰百勝。所以我們今天先不講優化,只深入分析同步阻塞網絡 IO 的內部實現。

在上面的 demo 中雖然只是簡單的兩三行代碼,但實際上用戶進程和內核配合做了非常多的工作。先是用戶進程發起創建 socket 的指令,然后切換到內核態完成了內核對象的初始化。接下來 Linux 在數據包的接收上,是硬中斷和 ksoftirqd 進程在進行處理。當 ksoftirqd 進程處理完以后,再通知到相關的用戶進程。

從用戶進程創建 socket,到一個網絡包抵達網卡到被用戶進程接收到,總體上的流程圖如下:

 

我們今天用圖解加源碼分析的方式來詳細拆解一下上面的每一個步驟,來看一下在內核里是它們是怎么實現的。閱讀完本文,你將深刻地理解在同步阻塞的網絡 IO 性能低下的原因!

一、創建一個 socket

開篇源碼中的 socket 函數調用執行完以后,內核在內部創建了一系列的 socket 相關的內核對象(是的,不是只有一個)。它們互相之間的關系如圖。當然了,這個對象比圖示的還要更復雜。我只在圖中把和今天的主題相關的內容展現了出來。

 

我們來翻翻源碼,看下上面的結構是如何被創造出來的。

  1. //file:net/socket.c 
  2. SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 
  3.  ...... 
  4.  retval = sock_create(family, type, protocol, &sock); 

sock_create 是創建 socket 的主要位置。其中 sock_create 又調用了 __sock_create。

  1. //file:net/socket.c 
  2. int __sock_create(struct net *net, int family, int type, int protocol, 
  3.     struct socket **res, int kern) 
  4.  struct socket *sock; 
  5.  const struct net_proto_family *pf; 
  6.  
  7.  ...... 
  8.  
  9.  //分配 socket 對象 
  10.  sock = sock_alloc(); 
  11.  
  12.  //獲得每個協議族的操作表 
  13.  pf = rcu_dereference(net_families[family]); 
  14.  
  15.  //調用每個協議族的創建函數, 對于 AF_INET 對應的是 
  16.  err = pf->create(net, sock, protocol, kern); 

在 __sock_create 里,首先調用 sock_alloc 來分配一個 struct sock 對象。接著在獲取協議族的操作函數表,并調用其 create 方法。對于 AF_INET 協議族來說,執行到的是 inet_create 方法。

  1. //file:net/ipv4/af_inet.c 
  2. tatic int inet_create(struct net *net, struct socket *sock, int protocol, 
  3.          int kern) 
  4.  struct sock *sk; 
  5.  
  6.  //查找對應的協議,對于TCP SOCK_STREAM 就是獲取到了 
  7.  //static struct inet_protosw inetsw_array[] = 
  8.     //{ 
  9.  //    { 
  10.  //     .type =       SOCK_STREAM, 
  11.  //     .protocol =   IPPROTO_TCP, 
  12.  //     .prot =       &tcp_prot, 
  13.  //     .ops =        &inet_stream_ops, 
  14.  //     .no_check =   0, 
  15.  //     .flags =      INET_PROTOSW_PERMANENT | 
  16.  //            INET_PROTOSW_ICSK, 
  17.  //    }, 
  18.  //} 
  19.     list_for_each_entry_rcu(answer, &inetsw[sock->type], list) { 
  20.  
  21.  //將 inet_stream_ops 賦到 socket->ops 上  
  22.  sock->ops = answer->ops; 
  23.  
  24.  //獲得 tcp_prot 
  25.  answer_prot = answer->prot; 
  26.  
  27.  //分配 sock 對象, 并把 tcp_prot 賦到 sock->sk_prot 上 
  28.  sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); 
  29.  
  30.  //對 sock 對象進行初始化 
  31.  sock_init_data(sock, sk); 

在 inet_create 中,根據類型 SOCK_STREAM 查找到對于 tcp 定義的操作方法實現集合 inet_stream_ops 和 tcp_prot。并把它們分別設置到 socket->ops 和 sock->sk_prot 上。

 

我們再往下看到了 sock_init_data。在這個方法中將 sock 中的 sk_data_ready 函數指針進行了初始化,設置為默認 sock_def_readable()。

  1. //file: net/core/sock.c 
  2. void sock_init_data(struct socket *sock, struct sock *sk)  
  3.     sk->sk_data_ready   =   sock_def_readable; 
  4.     sk->sk_write_space  =   sock_def_write_space; 
  5.     sk->sk_error_report =   sock_def_error_report; 

當軟中斷上收到數據包時會通過調用 sk_data_ready 函數指針(實際被設置成了 sock_def_readable()) 來喚醒在 sock 上等待的進程。這個咱們后面介紹軟中斷的時候再說,這里記住這個就行了。

至此,一個 tcp對象,確切地說是 AF_INET 協議族下 SOCK_STREAM對象就算是創建完成了。這里花費了一次 socket 系統調用的開銷

二、等待接收消息

接著我們來看 recv 函數依賴的底層實現。首先通過 strace 命令跟蹤,可以看到 clib 庫函數 recv 會執行到 recvfrom 系統調用。

進入系統調用后,用戶進程就進入到了內核態,通過執行一系列的內核協議層函數,然后到 socket 對象的接收隊列中查看是否有數據,沒有的話就把自己添加到 socket 對應的等待隊列里。最后讓出CPU,操作系統會選擇下一個就緒狀態的進程來執行。整個流程圖如下:

 

看完了整個流程圖,接下來讓我們根據源碼來看更詳細的細節。其中我們今天要關注的重點是 recvfrom 最后是怎么把自己的進程給阻塞掉的(假如我們沒有使用 O_NONBLOCK 標記)。

  1. //file: net/socket.c 
  2. SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size
  3.   unsigned int, flags, struct sockaddr __user *, addr, 
  4.   int __user *, addr_len) 
  5.  struct socket *sock; 
  6.  
  7.  //根據用戶傳入的 fd 找到 socket 對象 
  8.  sock = sockfd_lookup_light(fd, &err, &fput_needed); 
  9.  ...... 
  10.  err = sock_recvmsg(sock, &msg, size, flags); 
  11.  ...... 

sock_recvmsg ==> __sock_recvmsg => __sock_recvmsg_nosec

  1. static inline int __sock_recvmsg_nosec(struct kiocb *iocb, struct socket *sock, 
  2.            struct msghdr *msg, size_t sizeint flags) 
  3.  ...... 
  4.  return sock->ops->recvmsg(iocb, sock, msg, size, flags); 

調用 socket 對象 ops 里的 recvmsg, 回憶我們上面的 socket 對象圖,從圖中可以看到 recvmsg 指向的是 inet_recvmsg 方法。

  1. //file: net/ipv4/af_inet.c 
  2. int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, 
  3.    size_t sizeint flags) 
  4.  ... 
  5.  
  6.  err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT, 
  7.        flags & ~MSG_DONTWAIT, &addr_len); 

這里又遇到一個函數指針,這次調用的是 socket 對象里的 sk_prot 下面的 recvmsg方法。同上,得出這個 recvmsg 方法對應的是 tcp_recvmsg 方法。

  1. //file: net/ipv4/tcp.c 
  2. int tcp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, 
  3.   size_t len, int nonblock, int flags, int *addr_len) 
  4.  int copied = 0; 
  5.  ... 
  6.  do { 
  7.   //遍歷接收隊列接收數據 
  8.   skb_queue_walk(&sk->sk_receive_queue, skb) { 
  9.    ... 
  10.   } 
  11.   ... 
  12.  } 
  13.  
  14.  if (copied >= target) { 
  15.   release_sock(sk); 
  16.   lock_sock(sk); 
  17.  } else //沒有收到足夠數據,啟用 sk_wait_data 阻塞當前進程 
  18.   sk_wait_data(sk, &timeo); 

終于看到了我們想要看的東西,skb_queue_walk 是在訪問 sock 對象下面的接收隊列了。

 

如果沒有收到數據,或者收到不足夠多,則調用 sk_wait_data 把當前進程阻塞掉。

  1. //file: net/core/sock.c 
  2. int sk_wait_data(struct sock *sk, long *timeo) 
  3.  //當前進程(current)關聯到所定義的等待隊列項上 
  4.  DEFINE_WAIT(wait); 
  5.  
  6.  // 調用 sk_sleep 獲取 sock 對象下的 wait 
  7.  // 并準備掛起,將進程狀態設置為可打斷 INTERRUPTIBLE 
  8.  prepare_to_wait(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE); 
  9.  set_bit(SOCK_ASYNC_WAITDATA, &sk->sk_socket->flags); 
  10.  
  11.  // 通過調用schedule_timeout讓出CPU,然后進行睡眠 
  12.  rc = sk_wait_event(sk, timeo, !skb_queue_empty(&sk->sk_receive_queue)); 
  13.  ... 

我們再來詳細看下 sk_wait_data 是怎么把當前進程給阻塞掉的。

 

首先在 DEFINE_WAIT 宏下,定義了一個等待隊列項 wait。在這個新的等待隊列項上,注冊了回調函數 autoremove_wake_function,并把當前進程描述符 current 關聯到其 .private成員上。

  1. //file: include/linux/wait.h 
  2. #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) 
  3.  
  4. #define DEFINE_WAIT_FUNC(namefunction)    \ 
  5.  wait_queue_t name = {      \ 
  6.   .private = current,    \ 
  7.   .func  = function,    \ 
  8.   .task_list = LIST_HEAD_INIT((name).task_list), \ 
  9.  } 

緊接著在 sk_wait_data 中 調用 sk_sleep 獲取 sock 對象下的等待隊列列表頭 wait_queue_head_t。sk_sleep 源代碼如下:

  1. //file: include/net/sock.h 
  2. static inline wait_queue_head_t *sk_sleep(struct sock *sk) 
  3.  BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0); 
  4.  return &rcu_dereference_raw(sk->sk_wq)->wait; 

接著調用 prepare_to_wait 來把新定義的等待隊列項 wait 插入到 sock 對象的等待隊列下。

  1. //file: kernel/wait.c 
  2. void 
  3. prepare_to_wait(wait_queue_head_t *q, wait_queue_t *wait, int state) 
  4.  unsigned long flags; 
  5.  
  6.  wait->flags &= ~WQ_FLAG_EXCLUSIVE; 
  7.  spin_lock_irqsave(&q->lock, flags); 
  8.  if (list_empty(&wait->task_list)) 
  9.   __add_wait_queue(q, wait); 
  10.  set_current_state(state); 
  11.  spin_unlock_irqrestore(&q->lock, flags); 

這樣后面當內核收完數據產生就緒時間的時候,就可以查找 socket 等待隊列上的等待項,進而就可以找到回調函數和在等待該 socket 就緒事件的進程了。

最后再調用 sk_wait_event 讓出 CPU,進程將進入睡眠狀態,這會導致一次進程上下文的開銷。

接下來的小節里我們將能看到進程是如何被喚醒的了。

三、軟中斷模塊

接著我們再轉換一下視角,來看負責接收和處理數據包的軟中斷這邊。關于網絡包到網卡后是怎么被網卡接收,最后在交由軟中斷處理的,這里就不多贅述了。感興趣的請看之前的文章《圖解Linux網絡包接收過程》。我們今天直接從 tcp 協議的接收函數 tcp_v4_rcv 看起。

 

軟中斷(也就是 Linux 里的 ksoftirqd 進程)里收到數據包以后,發現是 tcp 的包的話就會執行到 tcp_v4_rcv 函數。接著走,如果是 ESTABLISH 狀態下的數據包,則最終會把數據拆出來放到對應 socket 的接收隊列中。然后調用 sk_data_ready 來喚醒用戶進程。

我們看更詳細一點的代碼:

  1. // file: net/ipv4/tcp_ipv4.c 
  2. int tcp_v4_rcv(struct sk_buff *skb) 
  3.  ...... 
  4.  th = tcp_hdr(skb); //獲取tcp header 
  5.  iph = ip_hdr(skb); //獲取ip header 
  6.  
  7.  //根據數據包 header 中的 ip、端口信息查找到對應的socket 
  8.  sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); 
  9.  ...... 
  10.  
  11.  //socket 未被用戶鎖定 
  12.  if (!sock_owned_by_user(sk)) { 
  13.   { 
  14.    if (!tcp_prequeue(sk, skb)) 
  15.     ret = tcp_v4_do_rcv(sk, skb); 
  16.   } 
  17.  } 

在 tcp_v4_rcv 中首先根據收到的網絡包的 header 里的 source 和 dest 信息來在本機上查詢對應的 socket。找到以后,我們直接進入接收的主體函數 tcp_v4_do_rcv 來看。

  1. //file: net/ipv4/tcp_ipv4.c 
  2. int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) 
  3.  if (sk->sk_state == TCP_ESTABLISHED) {  
  4.  
  5.   //執行連接狀態下的數據處理 
  6.   if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) { 
  7.    rsk = sk; 
  8.    goto reset; 
  9.   } 
  10.   return 0; 
  11.  } 
  12.  
  13.  //其它非 ESTABLISH 狀態的數據包處理 
  14.  ...... 

我們假設處理的是 ESTABLISH 狀態下的包,這樣就又進入 tcp_rcv_established 函數中進行處理。

  1. //file: net/ipv4/tcp_input.c 
  2. int tcp_rcv_established(struct sock *sk, struct sk_buff *skb, 
  3.    const struct tcphdr *th, unsigned int len) 
  4.  ...... 
  5.  
  6.  //接收數據到隊列中 
  7.  eaten = tcp_queue_rcv(sk, skb, tcp_header_len, 
  8.             &fragstolen); 
  9.  
  10.  //數據 ready,喚醒 socket 上阻塞掉的進程 
  11.  sk->sk_data_ready(sk, 0); 

在 tcp_rcv_established 中通過調用 tcp_queue_rcv 函數中完成了將接收數據放到 socket 的接收隊列上。

 

如下源碼所示

  1. //file: net/ipv4/tcp_input.c 
  2. static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, int hdrlen, 
  3.     bool *fragstolen) 
  4.  //把接收到的數據放到 socket 的接收隊列的尾部 
  5.  if (!eaten) { 
  6.   __skb_queue_tail(&sk->sk_receive_queue, skb); 
  7.   skb_set_owner_r(skb, sk); 
  8.  } 
  9.  return eaten; 

調用 tcp_queue_rcv 接收完成之后,接著再調用 sk_data_ready 來喚醒在socket上等待的用戶進程。 這又是一個函數指針。回想上面我們在 創建 socket 流程里執行到的 sock_init_data 函數,在這個函數里已經把 sk_data_ready 設置成 sock_def_readable 函數了(可以ctrl + f 搜索前文)。它是默認的數據就緒處理函數。

  1. //file: net/core/sock.c 
  2. static void sock_def_readable(struct sock *sk, int len) 
  3.  struct socket_wq *wq; 
  4.  
  5.  rcu_read_lock(); 
  6.  wq = rcu_dereference(sk->sk_wq); 
  7.  
  8.  //有進程在此 socket 的等待隊列 
  9.  if (wq_has_sleeper(wq)) 
  10.   //喚醒等待隊列上的進程 
  11.   wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI | 
  12.       POLLRDNORM | POLLRDBAND); 
  13.  sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN); 
  14.  rcu_read_unlock(); 

在 sock_def_readable 中再一次訪問到了 sock->sk_wq 下的wait。回憶下我們前面調用 recvfrom 執行的最后,通過 DEFINE_WAIT(wait) 將當前進程關聯的等待隊列添加到 sock->sk_wq 下的 wait 里了。

那接下來就是調用 wake_up_interruptible_sync_poll 來喚醒在 socket 上因為等待數據而被阻塞掉的進程了。

  1. //file: include/linux/wait.h 
  2. #define wake_up_interruptible_sync_poll(x, m)    \ 
  3.  __wake_up_sync_key((x), TASK_INTERRUPTIBLE, 1, (void *) (m)) 
  1. //file: kernel/sched/core.c 
  2. void __wake_up_sync_key(wait_queue_head_t *q, unsigned int mode, 
  3.    int nr_exclusive, void *key
  4.  unsigned long flags; 
  5.  int wake_flags = WF_SYNC; 
  6.  
  7.  if (unlikely(!q)) 
  8.   return
  9.  
  10.  if (unlikely(!nr_exclusive)) 
  11.   wake_flags = 0; 
  12.  
  13.  spin_lock_irqsave(&q->lock, flags); 
  14.  __wake_up_common(q, mode, nr_exclusive, wake_flags, key); 
  15.  spin_unlock_irqrestore(&q->lock, flags); 

__wake_up_common 實現喚醒。這里注意下, 該函數調用是參數 nr_exclusive 傳入的是 1,這里指的是即使是有多個進程都阻塞在同一個 socket 上,也只喚醒 1 個進程。其作用是為了避免驚群。

  1. //file: kernel/sched/core.c 
  2. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, 
  3.    int nr_exclusive, int wake_flags, void *key
  4.  wait_queue_t *curr, *next
  5.  
  6.  list_for_each_entry_safe(curr, next, &q->task_list, task_list) { 
  7.   unsigned flags = curr->flags; 
  8.  
  9.   if (curr->func(curr, mode, wake_flags, key) && 
  10.     (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive) 
  11.    break; 
  12.  } 

在 __wake_up_common 中找出一個等待隊列項 curr,然后調用其 curr->func。回憶我們前面在 recv 函數執行的時候,使用 DEFINE_WAIT() 定義等待隊列項的細節,內核把 curr->func 設置成了 autoremove_wake_function。

  1. //file: include/linux/wait.h 
  2. #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function) 
  3.  
  4. #define DEFINE_WAIT_FUNC(namefunction)    \ 
  5.  wait_queue_t name = {      \ 
  6.   .private = current,    \ 
  7.   .func  = function,    \ 
  8.   .task_list = LIST_HEAD_INIT((name).task_list), \ 
  9.  } 

在 autoremove_wake_function 中,調用了 default_wake_function。

  1. //file: kernel/sched/core.c 
  2. int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags, 
  3.      void *key
  4.  return try_to_wake_up(curr->private, mode, wake_flags); 

調用 try_to_wake_up 時傳入的 task_struct 是 curr->private。這個就是當時因為等待而被阻塞的進程項。當這個函數執行完的時候,在 socket 上等待而被阻塞的進程就被推入到可運行隊列里了,這又將是一次進程上下文切換的開銷。

小結

好了,我們把上面的流程總結一下。內核在通知網絡包的運行環境分兩部分:

第一部分是我們自己代碼所在的進程,我們調用的 socket() 函數會進入內核態創建必要內核對象。recv() 函數在進入內核態以后負責查看接收隊列,以及在沒有數據可處理的時候把當前進程阻塞掉,讓出 CPU。

第二部分是硬中斷、軟中斷上下文(系統進程 ksoftirqd)。在這些組件中,將包處理完后會放到 socket 的接收隊列中。然后再根據 socket 內核對象找到其等待隊列中正在因為等待而被阻塞掉的進程,然后把它喚醒。

 

每次一個進程專門為了等一個 socket 上的數據就得被從 CPU 上拿下來。然后再換上另一個進程。等到數據 ready 了,睡眠的進程又會被喚醒。總共兩次進程上下文切換開銷,根據之前的測試來看,每一次切換大約是 3-5 us(微秒)左右。如果是網絡 IO 密集型的應用的話,CPU 就不停地做進程切換這種無用功。

在服務端角色上,這種模式完全沒辦法使用。因為這種簡單模型里的 socket 和進程是一對一的。我們現在要在單臺機器上承載成千上萬,甚至十幾、上百萬的用戶連接請求。如果用上面的方式,那就得為每個用戶請求都創建一個進程。相信你在無論多原始的服務器網絡編程里,都沒見過有人這么干吧。

如果讓我給它起一個名字的話,它就叫單路不復用(飛哥自創名詞)。那么有沒有更高效的網絡 IO 模型呢?當然有,那就是你所熟知的 select、poll 和 epoll了。下次飛哥再開始拆解 epoll 的實現源碼,敬請期待!

這種模式在客戶端角色上,現在還存在使用的情形。因為你的進程可能確實得等 Mysql 的數據返回成功之后,才能渲染頁面返回給用戶,否則啥也干不了。

注意一下,我說的是角色,不是具體的機器。例如對于你的 php/java/golang 接口機,你接收用戶請求的時候,你是服務端角色。但當你再請求 redis 的時候,就變為客戶端角色了。

 

不過現在有一些封裝的很好的網絡框架例如 Sogou Workflow,Golang 的 net 包等在網絡客戶端角色上也早已摒棄了這種低效的模式!

 

責任編輯:武曉燕 來源: 開發內功修煉
相關推薦

2020-12-04 11:40:53

Linux

2025-01-13 13:00:00

Go網絡框架nbio

2009-07-07 18:08:03

刀片服務器刀片IDC

2013-09-10 10:04:05

云計算大數據NoSQL

2015-09-09 13:38:59

2015-04-14 10:34:02

微軟AzurePaaS云應用

2024-01-29 14:21:51

2012-05-24 10:53:19

云應用安全云計算

2009-03-03 12:48:01

2018-05-25 09:00:00

2013-09-17 09:30:15

云項目實施云項目云計算技術

2022-04-24 10:42:59

Kubernete容器網絡Linux

2022-05-05 15:13:59

數字化轉型組織文化組織

2010-08-25 09:07:03

2013-07-31 10:04:42

hadoopHadoop集群集群和網絡

2012-08-31 10:00:12

Hadoop云計算群集網絡

2012-11-08 14:47:52

Hadoop集群

2017-09-04 19:08:05

上線直播技術浪live

2020-06-17 16:43:40

網絡IO框架

2016-02-15 10:30:24

大數據大數據實施實施戰略
點贊
收藏

51CTO技術棧公眾號

伊是香蕉大人久久| 国产欧美精品一区| 久久久久久国产免费| 亚洲婷婷在线观看| 日韩免费电影| 亚洲欧洲三级电影| 精品免费视频123区| 99久久久无码国产精品免费蜜柚 | 在线电影国产精品| 日本一区午夜艳熟免费| 在线免费观看黄| 国产二区国产一区在线观看| 国产99久久精品一区二区| 小泽玛利亚一区| 最新精品国偷自产在线| 91麻豆精品91久久久久久清纯| 久久国产亚洲精品无码| jizzjizz亚洲| 国产午夜三级一区二区三| 成人av电影免费| 中文字幕激情视频| 99视频在线精品国自产拍免费观看| 色偷偷噜噜噜亚洲男人的天堂| 欧美在线一级片| 日韩区一区二| 欧美日韩国产免费一区二区| 1024精品视频| wwwww亚洲| 91video| 亚洲欧美日韩精品永久在线| 成人精品视频在线观看| 哺乳一区二区三区中文视频| 午夜精品爽啪视频| 国产高清精品软男同| 美女毛片在线看| 成人av网站免费观看| 亚洲一区二区自拍| 亚洲自拍偷拍另类| 日韩精彩视频在线观看| 日本韩国欧美精品大片卡二| 日韩福利片在线观看| 欧美日韩18| 美日韩在线视频| 影音先锋男人资源在线观看| 成人羞羞网站入口免费| 国产亚洲欧洲在线| 无码 人妻 在线 视频| 天堂成人娱乐在线视频免费播放网站 | 亚洲第一会所001| 日本高清无吗v一区| 欧美日韩国产精品激情在线播放| 春色校园综合激情亚洲| 亚洲国产中文字幕在线视频综合| av动漫在线播放| av片在线观看永久免费| 亚洲精品国产精品乱码不99| 国产人妻人伦精品| 黄污视频在线观看| 亚洲sss视频在线视频| 国产人妻777人伦精品hd| 2020国产在线| 天天操天天色综合| 久久精品香蕉视频| 成人在线免费电影网站| 欧美日韩成人综合| 国模大尺度视频| 最新亚洲国产| 日韩久久久久久| 在线看黄色的网站| 免费看成人吃奶视频在线| 亚洲视频在线观看视频| 黄色av片三级三级三级免费看| 久久大综合网| 欧美多人爱爱视频网站| 国产精彩视频在线| 久久成人免费| 国产有码一区二区| 成人免费公开视频| 久久嫩草精品久久久精品| 色就是色欧美| 色呦呦在线看| 欧美色播在线播放| 亚洲激情在线观看视频| 国产一区 二区| 亚洲精品黄网在线观看| 影音先锋男人在线| 国产精品亚洲四区在线观看| 欧美一级xxx| aaaaaav| 日韩在线观看| 欧美高清一级大片| 一本一道无码中文字幕精品热| 蜜桃视频在线观看一区二区| 91九色对白| 日本韩国精品一区二区| 中文字幕一区免费在线观看| 久久精品xxx| jvid一区二区三区| 亚洲成人在线视频播放| 国产综合精品久久久久成人av| 91精品亚洲| 欧美一区二区视频97| 国产精品久久久午夜夜伦鲁鲁| eeuss影院一区二区三区| 亚洲欧洲免费无码| 日韩激情电影| 欧美videofree性高清杂交| 成人无码av片在线观看| 在线成人国产| 成人写真视频福利网| 四虎成人免费在线| 亚洲视频你懂的| 91淫黄看大片| 欧美人成在线观看ccc36| 精品国产一区二区三区久久狼5月| 日本视频www| 狠狠色综合色综合网络| 欧美影视一区二区| av剧情在线观看| 日韩一级视频免费观看在线| 老司机福利在线观看| 99国产精品| 99精品99久久久久久宅男| h视频在线观看免费| 欧美午夜女人视频在线| 97超碰人人看| 婷婷另类小说| 国产精品自拍视频| 黄色国产在线| 欧美性猛交xxxx乱大交3| 中文字幕一区二区三区人妻在线视频| 水蜜桃久久夜色精品一区| 日韩免费在线看| 青青免费在线视频| 欧美日韩精品二区| 亚洲av成人无码一二三在线观看| 午夜精品视频| 亚洲bt天天射| 日韩在线观看www| 在线观看日韩电影| a级大片在线观看| 在线成人国产| 久久99精品久久久久久青青日本 | 国产欧美日韩视频| 国产三级电影在线| 在线中文字幕一区二区| 在线观看福利片| 久久三级福利| 亚洲精品二区| 午夜精品久久久久久毛片| 日韩在线免费av| 一区二区的视频| 亚洲欧美韩国综合色| 久久精品国产99久久99久久久| 国产一区二区久久久久| 久久精品视频在线播放| 国产乱色精品成人免费视频| 中文字幕综合网| 激情在线观看视频| 午夜国产欧美理论在线播放| 国产精品区一区二区三含羞草| 国产福利片在线观看| 日韩精品有码在线观看| 男人天堂视频网| 国产精品污网站| 久久久精品视频国产| 激情欧美国产欧美| 久久青青草综合| yiren22亚洲综合| 久久在精品线影院精品国产| 99久久亚洲精品日本无码| 亚洲国产视频直播| av网站有哪些| 另类综合日韩欧美亚洲| 人人妻人人澡人人爽欧美一区| 岛国精品一区| 国产成人精品午夜| 黄色免费网站在线观看| 亚洲精品国产欧美| 中文有码在线播放| 洋洋成人永久网站入口| 国产ts在线播放| 久草热8精品视频在线观看| 女人帮男人橹视频播放| 国产探花在线精品| 91久久综合亚洲鲁鲁五月天| 理论不卡电影大全神| 中文字幕日韩综合av| 亚洲国产综合网| 91成人国产精品| 国产乡下妇女做爰视频| 中文字幕电影一区| 久久久久国产免费| 日韩av一区二区三区四区| www.激情网| 国产成人精品999在线观看| 不卡视频一区二区三区| 亚洲www啪成人一区二区| 欧美国产精品人人做人人爱| 国产视频福利在线| 欧美mv日韩mv国产网站| 亚洲在线精品视频| 欧美日韩国产精品一区二区不卡中文| www.涩涩爱| 91视频一区二区| 26uuu国产| 激情小说亚洲一区| 欧美一级黄色影院| 亚洲人体偷拍| 久久视频免费在线| 成人午夜国产| 久久资源亚洲| 国产精品极品在线观看| 成人黄色av免费在线观看| 人人视频精品| 97欧美精品一区二区三区| а√天堂资源地址在线下载| 一区二区三区精品99久久| 天堂中文在线看| 日韩欧美电影在线| 国产免费黄色网址| 欧美日韩国产在线播放网站| 国产伦精品一区二区三区视频我| 亚洲午夜av在线| 色在线观看视频| 国产精品理伦片| 夫妇交换中文字幕| 久久久久久久精| 91传媒理伦片在线观看| 国产精品亚洲视频| 天堂网成人在线| 久久国产视频网| 欧美成人黄色网址| 日韩国产精品大片| www.日日操| 三级成人在线视频| www.欧美日本| 久久亚洲色图| 激情视频综合网| 日韩电影一区二区三区四区| 成年人网站大全| 日韩av电影免费观看高清完整版| 韩国日本美国免费毛片| 日日嗨av一区二区三区四区| 国产精品少妇在线视频| 丝袜美腿高跟呻吟高潮一区| 男人操女人免费| 爽好久久久欧美精品| www.xxx亚洲| 久久精品国产一区二区三| 无需播放器的av| 国产一区二区三区在线观看精品| 国产高清999| 国产高清无密码一区二区三区| 激情图片中文字幕| 国产传媒久久文化传媒| 久久久精品人妻一区二区三区| 国产91高潮流白浆在线麻豆 | 最新国产中文字幕| 欧美日韩一区二区电影| 国产又黄又粗又长| 日韩一级免费一区| 天堂av在线资源| 国产一区二区黑人欧美xxxx| 在线国产情侣| 欧美国产激情18| 涩涩网在线视频| 国产精品狠色婷| 亚洲精品aa| 国产精品日韩高清| 免费一区二区三区视频导航| 亚洲欧美日韩国产成人综合一二三区| 国产高清久久| 国产欧美日韩小视频| 三级在线观看一区二区| 亚洲一区精品视频在线观看| 成人黄色国产精品网站大全在线免费观看 | 国产自产在线视频| 久久精品午夜| 亚洲欧美日韩一二三区| 99免费精品视频| 女人裸体性做爰全过| 亚洲精品亚洲人成人网| 毛片在线免费视频| 欧美日韩电影在线播放| 天堂在线观看免费视频| 啊v视频在线一区二区三区| 国产夫妻在线播放| 成人做爰www免费看视频网站| 国产美女撒尿一区二区| 一本色道久久综合亚洲精品婷婷 | 国产aaaaa毛片| 国产精品一二一区| 国产精品无码久久久久一区二区| √…a在线天堂一区| 欧美bbbbbbbbbbbb精品| 91精选在线观看| 青青草免费在线| 欧美猛少妇色xxxxx| 51一区二区三区| 激情视频在线观看一区二区三区| 欧美3p在线观看| 日韩精品xxxx| 国产一区二区不卡在线| 亚洲区自拍偷拍| 精品国产日韩欧美| 欧美精品激情在线| 99久久er| 久久99精品久久久久久久青青日本 | 亚洲网址你懂得| av成人影院在线| 91在线国产电影| 欧美一区二区性| 人妻熟妇乱又伦精品视频| 国产精品正在播放| 999久久久国产| 一本色道久久综合亚洲aⅴ蜜桃| 亚洲国产一二三区| 欧美成人亚洲成人| 欧美黄色成人| 日韩欧美精品在线不卡 | 黄色aaa级片| 91网站在线播放| 国产在线视频你懂的| 欧美一区二区三区视频免费播放| 成人午夜影视| 国产999在线观看| 要久久电视剧全集免费| 国产精品无码人妻一区二区在线| 国产乱码字幕精品高清av | 亚洲国产wwwccc36天堂| 国产伦理吴梦梦伦理| 深夜福利91大全| 国产精品无码久久久久| 日韩精品伦理第一区| 午夜影院日韩| 久久人人爽人人爽人人片| 午夜精品久久久| 无码精品在线观看| 97精品国产97久久久久久| 久久久久97| 奇米精品一区二区三区| 91影院在线免费观看| 精品少妇一二三区| 国产精品的网站| 无码人妻久久一区二区三区| 亚洲精品有码在线| 自拍网站在线观看| 欧美高清性xxxxhd| 久久久青草婷婷精品综合日韩| 久久偷拍免费视频| 福利视频第一区| www日韩tube| 成人a在线观看| 欧美精品成人| 理论片大全免费理伦片| 欧美视频免费在线| 精品三级久久久久久久电影聊斋| 国产精品扒开腿做爽爽爽的视频| 精品国产乱码久久久久久蜜坠欲下| 男女男精品视频站| 成人欧美一区二区三区白人 | 日韩国产成人在线| 欧美精品久久久久久久多人混战| 欧美另类高清视频在线| 午夜影院日韩| 亚洲色图欧美色| 91精品国产色综合久久ai换脸 | 国模极品一区二区三区| 欧美激情15p| 校园春色 亚洲色图| 成人免费在线观看入口| 亚洲国产精彩视频| 欧美在线视频网站| 爽成人777777婷婷| 2018国产精品| 欧美性猛交xxxx久久久| 久草资源在线| 精品久久蜜桃| 青青草97国产精品免费观看| 国产精品白嫩白嫩大学美女| 日韩成人中文电影| 色综合视频一区二区三区日韩| 国产精品无码免费专区午夜| 久久免费看少妇高潮| av中文字幕免费| 日本国产欧美一区二区三区| 亚洲国产一区二区在线观看| 加勒比精品视频| 欧美精品v日韩精品v韩国精品v| 久久免费电影| 亚洲日本精品| 99国产精品久| 91精品在线视频观看| 91av在线播放视频| 91精品二区| 久久婷婷五月综合| 亚洲第一区第一页| www.久久久.com| 成人在线看视频|