精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

日本三级视频在线| 久久午夜电影网| 亚洲夂夂婷婷色拍ww47| 亚洲欧洲第一视频| av在线免费观看国产| 国模私拍一区二区| 欧美日韩一区二区三区不卡视频| 18欧美亚洲精品| 日韩免费在线播放| 你懂得在线视频| 美女尤物在线视频| 综合天堂av久久久久久久| 色视频一区二区| 国产在线一区二区三区欧美| 欧美精品xxxxx| 精品国产鲁一鲁****| 中文在线一区二区| 国产精品国语对白| 国产免费无遮挡吸奶头视频| 白浆视频在线观看| 成人av网站免费| 欧美激情一区二区三区在线视频观看| 一区二区三区 欧美| 九色网友自拍视频手机在线| 亚洲女优在线| 亚洲女成人图区| 男人亚洲天堂网| 欧美日韩国产中文字幕在线| 免费在线成人| 欧美日韩成人在线观看| 人妻 丝袜美腿 中文字幕| 三级网站视频在在线播放| 国产老肥熟一区二区三区| 久久精品一区中文字幕| 第一区免费在线观看| 久草资源在线观看| 国产伦精品一区二区三区免费 | 四虎成人精品永久免费av九九| 欧美视频在线观看免费| 久久久av水蜜桃| 中文字幕视频网站| 欧美一区2区| 8x8x8国产精品| 欧美黑人在线观看| 九七久久人人| 国产精品乱码妇女bbbb| 亚洲专区中文字幕| 一区二区三区免费高清视频| 老牛国内精品亚洲成av人片| 日韩欧美在线字幕| 国产天堂视频在线观看| 全部免费毛片在线播放网站| 日韩av一区二| 欧美美女操人视频| 日本黄色录像视频| 国产精品一区二区中文字幕 | 青青草视频在线视频| 人妻少妇一区二区三区| 日韩精品一卡二卡三卡四卡无卡| 日韩午夜在线视频| 一级黄色免费毛片| 在线中文字幕播放| 一区在线中文字幕| 综合国产精品久久久| 欧美一级特黄aaaaaa| 国产ts人妖一区二区| 热久久免费视频精品| 久久精品在线观看视频| av成人资源网| 欧美日韩视频在线第一区 | 欧美三级电影网站| 网站一区二区三区| 激情小说亚洲| 亚洲第一搞黄网站| 性欧美videosex高清少妇| 精品人妻一区二区三区日产乱码| 水蜜桃久久夜色精品一区的特点 | 日韩一级成人av| 北条麻妃在线观看| 不卡一二三区| 亚洲午夜私人影院| 亚洲精品乱码久久久久久蜜桃91| 乱精品一区字幕二区| 麻豆国产精品视频| 欧洲永久精品大片ww免费漫画| www.国产高清| 狠色狠色综合久久| 久久精品久久久久电影| 欧美成人黄色网| 久久亚洲国产| 欧美激情18p| 91精品国产闺蜜国产在线闺蜜| 蜜乳av综合| 亚洲第一黄色网| 精品国产鲁一鲁一区二区三区| 欧美色网在线| 色综合天天综合狠狠| 国产精品久久久久7777| 巨茎人妖videos另类| 欧美日韩成人综合天天影院| 老熟妇仑乱视频一区二区| jizz一区二区三区| 一区二区高清免费观看影视大全| 欧美三级一级片| 黄色在线免费观看网站| 91高清在线观看| 日本中文字幕精品| 国产美女视频一区二区| 欧美日韩小视频| 日本人妻一区二区三区| 午夜视频一区二区在线观看| 欧美一级高清片在线观看| 免费中文字幕av| 婷婷成人影院| 亚洲欧美国产精品va在线观看| 四季av中文字幕| 国产一区二区三区探花 | 福利在线观看| 国产欧美日韩视频在线观看| 日韩中文字幕一区| 1024视频在线| 国产精品理论在线观看| 91成人在线观看喷潮教学| av中文字幕在线看| 欧美精品一卡两卡| 交换做爰国语对白| 精品视频久久| 最近2019中文字幕在线高清| 日本一道本视频| 国产日韩一区| 国产精品777| 一区二区精品视频在线观看| 激情久久久久久久久久久久久久久久| 国产精品一区二区三区成人| 在线观看中文字幕码| 久久成人羞羞网站| 99视频免费观看蜜桃视频| 风流老熟女一区二区三区| 中文欧美字幕免费| 国产xxxxx视频| 日本免费在线一区| 日韩一区二区三区电影在线观看| 欧美激情 一区| 日韩精品福利网| 蜜桃传媒视频麻豆一区| 中文日本在线观看| 在线免费观看成人短视频| 五月天婷婷在线观看视频| 欧美丝袜一区| 国产精品视频在线播放| 国产片在线播放| 99久久99久久精品国产片果冻| 日韩av电影免费观看| 国产精品专区免费| 亚洲视频欧洲视频| 精品无码一区二区三区的天堂| 老司机午夜精品| 亚洲精品美女久久7777777| 国产成人精品一区二区三区视频| 欧美成人乱码一区二区三区| 中文字幕第4页| 久久婷婷一区| 香蕉久久夜色| 中文字幕综合| 亚洲欧美一区二区激情| 五月婷婷视频在线| 国产亚洲午夜高清国产拍精品| 水蜜桃在线免费观看| 亚洲国产福利| 亚洲人成网站999久久久综合| 黄色av一级片| 不卡的电视剧免费网站有什么| 18禁网站免费无遮挡无码中文| 久久99国产精品久久99大师 | 亚洲激情 国产| 成人免费视频入口| 国产视频欧美| 日韩激情视频| www.久久热| 久久欧美在线电影| av中文字幕免费在线观看| 久久亚洲精品小早川怜子| 91蝌蚪视频在线观看| 99久久夜色精品国产亚洲96| 欧美最猛性xxxxx(亚洲精品)| 国产乱理伦片a级在线观看| 欧美日韩国产首页| 精品少妇久久久| 久久久久久久久久久电影| www.国产在线播放| 免费成人结看片| 成人性生交大片免费看视频直播 | 国产精品久久婷婷| 91丨九色porny丨蝌蚪| 日本黄网站色大片免费观看| 9l视频自拍九色9l视频成人| 日本欧美在线视频| 51xtv成人影院| 91精品免费在线观看| 日本天堂网在线观看| 国产欧美精品区一区二区三区 | 美女av一区| 国产在线98福利播放视频| 极品白浆推特女神在线观看 | 国产第一区电影| 4438x成人网全国最大| 亚洲免费电影在线观看| av中文在线观看| 欧美性xxxxxx少妇| 丰满的亚洲女人毛茸茸| 成人午夜av电影| 大陆极品少妇内射aaaaaa| 九色精品91| 岛国一区二区三区高清视频| 污视频在线看网站| 有码中文亚洲精品| 一级黄色免费看| 欧美视频在线观看免费| 久久久久久蜜桃| 中文字幕永久在线不卡| 伊人五月天婷婷| 视频一区国产视频| 国产精品无码av在线播放| 希岛爱理av免费一区二区| 999视频在线免费观看| 欧美hdxxx| 久久久av电影| 福利在线午夜| 亚洲色图校园春色| 日韩精品视频无播放器在线看| 日韩三级视频在线看| 91国产精品一区| 欧美日韩精品欧美日韩精品一综合| 日日骚av一区二区| 精品久久久久久| 一级在线观看视频| 2欧美一区二区三区在线观看视频 337p粉嫩大胆噜噜噜噜噜91av | 国产日韩成人精品| 男女黄床上色视频| 日本在线不卡视频| 欧美黄网站在线观看| 亚洲免费精品| 亚洲精品久久区二区三区蜜桃臀 | 亚洲美女自拍偷拍| 中文字幕一区图| 人九九综合九九宗合| yellow字幕网在线| 久久久久久成人| 狂野欧美性猛交xxxxx视频| 欧美成人精品一区二区| 亚洲欧美自偷自拍| 欧美军同video69gay| 亚洲天堂视频在线| 欧美日韩夫妻久久| 一级视频在线播放| 91麻豆精品国产无毒不卡在线观看 | 欧美少妇xxxx| 视频一区二区三区在线观看| 九九热线有精品视频99| 日韩视频精品| 午夜av一区| 亚洲自拍小视频免费观看| 在线观看欧美| 91视频免费进入| 粉嫩久久久久久久极品| 精品日本一区二区三区在线观看| 日本综合久久| 国产精品一区二区3区| 亚洲视频自拍| 91偷拍精品一区二区三区| 97成人在线| 欧美日韩一区二区三区在线视频| 精品国产伦一区二区三区观看说明 | 亚洲午夜精品国产| 国产乱人伦精品一区| 精品一区久久久| 精品无人区麻豆乱码久久久| 亚洲午夜精品久久久中文影院av | 亚洲乱码国产一区三区| 九九视频精品免费| av不卡中文字幕| 久久精品网站免费观看| 色哟哟一一国产精品| 亚洲福利一区二区三区| 国产字幕在线观看| 精品久久香蕉国产线看观看gif| 欧美日韩综合一区二区三区| 欧美日韩免费观看一区三区| 亚洲精品国产一区二| 欧美精品乱码久久久久久| 成人高潮片免费视频| 亚洲精品www久久久| 91女主播在线观看| 欧美激情奇米色| 免费成人美女女| 51色欧美片视频在线观看| 精精国产xxxx视频在线中文版| 欧美一级电影久久| 成人动漫视频在线观看| 久久99精品久久久久久久青青日本| 日韩精品电影| 五月天国产一区| 激情视频一区二区三区| 国产91视频一区| 久久精品道一区二区三区| 欧美性久久久久| 黄色小说综合网站| 国产中年熟女高潮大集合| 亚洲一区二区四区蜜桃| 中文字幕av久久爽| 欧美日韩视频第一区| 人妻中文字幕一区| 精品国内产的精品视频在线观看| 麻豆视频在线观看免费网站黄| 91精品国产色综合久久不卡98口 | 人妻激情偷乱视频一区二区三区| 久久天堂av综合合色蜜桃网| 久久精品99国产精| 欧美日韩国产高清一区二区三区 | 77777亚洲午夜久久多人| 亚洲日本中文| 亚洲午夜精品福利| 丝袜亚洲另类欧美| 亚洲国产第一区| 亚洲综合视频在线| 99国产精品久久久久久久成人 | 麻豆传媒一区| 狠狠久久婷婷| 日本人dh亚洲人ⅹxx| 国产精品国产自产拍高清av| 亚洲 欧美 日韩 在线| 亚洲福利在线视频| 女人天堂av在线播放| 亚洲精品欧美极品| 偷拍欧美精品| 中文字幕 欧美日韩| 国产精品中文有码| 九九热视频在线免费观看| 欧美午夜精品久久久| 久久精品蜜桃| 琪琪第一精品导航| 啪啪激情综合网| 精品人妻少妇一区二区| 久久国产精品久久w女人spa| bl动漫在线观看| 久久精品免费在线观看| 韩国av中文字幕| 亚洲精品美女免费| 四虎久久免费| 久久乐国产精品| 91成人精品在线| 999一区二区三区| 成人深夜福利app| 久久草视频在线| 欧美精品乱码久久久久久| 麻豆免费在线观看| 国产69精品久久久久久| 欧美交a欧美精品喷水| 少妇高潮喷水在线观看| 97久久久精品综合88久久| 国产超碰人人爽人人做人人爱| 日韩国产精品亚洲а∨天堂免| 欧美少妇网站| 欧洲高清一区二区| 亚洲午夜黄色| 日本少妇毛茸茸| 欧美视频在线免费| а天堂8中文最新版在线官网| 久久久久久噜噜噜久久久精品| 粉嫩一区二区三区四区公司1| 91九色在线观看视频| 久久久久久久久久电影| 一区二区视频免费观看| 美女国内精品自产拍在线播放| 最新国产精品精品视频| 日本午夜激情视频| 久久精品男人的天堂| 国产精品国产精品国产专区| 久久免费高清视频| 免费成人网www| 在线视频一二区| 午夜一区二区三区在线观看| 国产露脸91国语对白| 欧美肥婆姓交大片| 亚洲欧洲美洲国产香蕉| 欧美日韩福利在线| 国产亚洲欧洲997久久综合| 国产又大又粗又硬| 午夜精品久久久久久久久久久久| 国产亚洲高清一区| 国产黄色片免费在线观看| 久久久一区二区| 国产乱码精品一区二三区蜜臂 | 国产91富婆露脸刺激对白| 欧美国产成人精品一区二区三区| 在线观看久久av| 开心激情综合| 亚洲xxx在线观看| 精品久久久一区| 国产黄色小视频在线|