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

Go的Net/Http有哪些值得關注的細節?

開發 后端
Golang的Net/Http部分有不少細節點,直接上源碼分析怕勸退不少人,所以希望以幾個例子作為引子展開話題然后深入了解它的內部實現。總體內容比較碎片化,但這個庫的重點知識點基本都在這里面了。希望對大家后續排查問題有幫助。

golang的net/http庫是我們平時寫代碼中,非常常用的標準庫。由于go語言擁有goroutine,goroutine的上下文切換成本比普通線程低很多,net/http庫充分利用了這個優勢,因此,它的內部實現跟其他語言會有一些區別。

其中最大的區別在于,其他語言中,一般是多個網絡句柄共用一個或多個線程,以此來減少線程之間的切換成本。而golang則會為每個網絡句柄創建兩個goroutine,一個用于讀數據,一個用于寫數據。

讀寫協程

下圖是net/http源碼中創建這兩個goroutine的地方。

源碼中創建兩個協程的地方

了解它的內部實現原理,可以幫助我們寫出更高性能的代碼,以及避免協程泄露造成的內存泄漏問題。

這篇文章是希望通過幾個例子讓大家對net/http的內部實現有更直觀的理解。

連接與協程數量的關系

首先我們來看一個例子。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
            Timeout:   3 * time.Second,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 5)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

上面的代碼做的事情很簡單,執行5次循環http請求,最終通過runtime.NumGoroutine()方法打印當前的goroutine數量。

代碼里只有三個地方需要注意:

  • Transport設置了一個3s的空閑連接超時。
  •  for循環執行了5次http請求。
  • 程序退出前執行了5s sleep。

答案輸出1。也就是說當程序退出的時候,當前的goroutine數量為1,毫無疑問它指的是正在運行main方法的goroutine,后面我們都叫它main goroutine。

再來看個例子。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
            Timeout:   3 * time.Second,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

在原來的基礎上,我們程序退出前的睡眠時間,從5s改成1s,此時輸出3。也就是說除了main方法所在的goroutine,還多了兩個goroutine,我們大概也能猜到,這就是文章開頭提到的讀goroutine和寫goroutine。也就是說程序在退出時,還有一個網絡連接沒有斷開。

這是一個TCP長連接。

HTTP1.1底層依賴TCP

網絡五層模型中,HTTP處于應用層,它的底層依賴了傳輸層的TCP協議。

當我們發起http請求時,如果每次都要建立新的TCP協議,那就需要每次都經歷三次握手,這會影響性能,因此更好的方式就是在http請求結束后,不立馬斷開TCP連接,將它放到一個空閑連接池中,后續有新的http請求時就復用該連接。

像這種長時間存活,被多個http請求復用的TCP連接,就是所謂的長連接。反過來,如果每次HTTP請求結束就將TCP連接進行四次揮手斷開,下次有需要執行HTTP調用時就再建立,這樣的TCP連接就是所謂的短連接

HTTP1.1之后默認使用長連接。

連接池復用連接

那為什么這跟5s和1s有關系?

這是因為長連接在空閑連接池也不能一直存放著,如果一直沒被使用放著也是浪費資源,因此會有個空閑回收時間,也就是上面代碼中的IdleConnTimeout,我們設置的是3s,當代碼在結束前sleep了5s后,長連接就已經被釋放了,因此輸出結果是只剩一個main goroutine。當sleep 1s時,長連接還在空閑連接池里,因此程序結束時,就還剩3個goroutine(main goroutine+網絡讀goroutine+網絡寫goroutine)。

我們可以改下代碼下驗證這個說法。我們知道,HTTP可以通過connection的header頭來控制這次的HTTP請求是用的長連接還是短連接。connection:keep-alive 表示http請求結束后,tcp連接保持存活,也就是長連接, connection:close則是短連接。

req.Header.Add("connection", "close")

就像下面這樣。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        req.Header.Add("connection", "close")
        client := &http.Client{
            Transport: tr,
            Timeout:   3 * time.Second,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

此時,會發現,程序重新輸出1。完全符合我們預期。

resp.body是否讀取對連接復用的影響

func main() {
   n := 5
   for i := 0; i < n; i++ {
      resp, _ := http.Get("https://www.baidu.com")
      _ = resp.Body.Close()
   }
   fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

注意這里沒有執行 ioutil.ReadAll(resp.Body)。也就是說http請求響應的結果并沒有被讀取的情況下,net/http庫會怎么處理。

上面的代碼最終輸出3,分別是main goroutine,read goroutine 以及write goroutine。也就是說長連接沒有斷開,那長連接是會在下一次http請求中被復用嗎?先說答案,不會復用。

我們可以看代碼。resp.Body.Close() 會執行到 func (es * bodyEOFSignal) Close() error 中,并執行到es.earlyCloseFn()中。

earlyCloseFn的邏輯也非常簡單,就是將一個false傳入到waitForBodyRead的channel中。那寫入通道后的數據會在另外一個地方被讀取,我們來看下讀取的地方。

bodyEOF為false, 也就不需要執行 tryPutIdleConn()方法。

tryPutIdleConn會將連接放到長連接池中備用)。

最終就是alive=bodyEOF ,也就是false,字面意思就是該連接不再存活。因此該長連接并不會復用,而是會釋放。

那為什么output輸出為3?這是因為長連接釋放需要時間。

我們可以在結束前加一個休眠,比如再執行休眠1毫秒。

func main() {
    n := 5
    for i := 0; i < n; i++ {
        resp, _ := http.Get("https://www.baidu.com")
        _ = resp.Body.Close()
    }
    time.Sleep(time.Millisecond * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

此時就會輸出1。說明協程是退出中的,只是沒來得及完全退出,休眠1ms后徹底退出了。

如果我們,將在代碼中重新加入 ioutil.ReadAll(resp.Body),就像下面這樣。

func main() {
    n := 5
    for i := 0; i < n; i++ {
        resp, _ := http.Get("https://www.baidu.com")
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

此時,output還是輸出3,但這個3跟上面的3不太一樣,休眠5s后還是輸出3。這是因為長連接被推入到連接池了,連接會重新復用。

下面是源碼的解釋。


body.close()不執行會怎么樣

網上都說不執行body.close()會協程泄漏(導致內存泄露),真的會出現協程泄漏嗎,如果泄漏,會泄漏多少?

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        //_ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

我們可以運行這段代碼,代碼中將resp.body.close()注釋掉,結果輸出3。debug源碼,會發現連接其實復用了。代碼執行到tryPutIdleConn函數中,會將連接歸還到空閑連接池中。

休眠5s,結果輸出1,這說明達到idleConnTimeout,空閑連接斷開。看起來一切正常。

將resp.Body.Close()那一行代碼重新加回來,也就是下面這樣,會發現代碼結果依然輸出3。我們是否刪除這行代碼,對結果沒有任何影響。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

既然執不執行body.close()都沒啥區別,那body.close()的作用是什么呢?

它是為了標記當前連接請求中,response.body是否使用完畢,如果不執行body.close(),則resp.Body中的數據是可以不斷重復讀且不報錯的(但不一定能讀到數據),執行了body.close(),再次去讀取resp.Body則會報錯,如果resp.body數據讀一半,處理代碼邏輯就報錯了,此時你不希望其他地方繼續去讀,那就需要使用body.close()去關閉它。這更像是一種規范約束,它可以更好的保證數據正確。

也就是說不執行body.close(),并不一定會內存泄露。那么什么情況下會協程泄露呢?

直接說答案,既不執行 ioutil.ReadAll(resp.Body) 也不執行resp.Body.Close(),并且不設置http.Client內timeout的時候,就會導致協程泄露。

比如下面這樣。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
        }
        resp, _ := client.Do(req)
        _ = resp
    }
    time.Sleep(time.Second * 5)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

最終結果會輸出11,也就是1個main goroutine + (1個read goroutine + 1個read goroutine)* 5次http請求。

前面提到,不執行ioutil.ReadAll(resp.Body),網絡連接無法歸還到連接池。不執行resp.Body.Close(),網絡連接就無法為標記為關閉,也就無法正常斷開。因此能導致協程泄露,非常好理解。

但http.Client內timeout有什么關系?這是因為timeout是指,從發起請求到從resp.body中讀完響應數據的總時間,如果超過了,網絡庫會自動斷開網絡連接,并釋放read+write goroutine。因此如果設置了timeout,則不會出現協程泄露的問題。

另外值得一提的是,我看到有不少代碼都是直接用下面的方式去做網絡請求的。

resp, _ := http.Get("https://www.baidu.com")

這種方式用的是DefaultClient,是沒有設置超時的,生產環境中使用不當,很容易出現問題。

func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

var DefaultClient = &Client{}

連接池的結構

我們了解到連接池可以復用網絡連接,接下來我們通過一個例子來看看網絡連接池的結構。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "http://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
            Timeout:   3 * time.Second,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

注意這里請求的不是https,而是http。最終結果輸出5,為什么?

這是因為,http://www.baidu.com會返回307,重定向到https://www.baidu.com。

http重定向為https

在網絡中,我們可以通過一個五元組來唯一確定一個TCP連接。

五元組

它們分別是源ip,源端口,協議,目的ip,目的端口。只有當多次請求的五元組一樣的情況下,才有可能復用連接。

放在我們這個場景下,源ip、源端口、協議都是確定的,也就是兩次http請求的目的ip或目的端口有區別的時候,就需要使用不同的TCP長連接。

而http用的是80端口,https用的是443端口。于是連接池就為不同的網絡目的地建立不同的長連接。

因此最終結果5個goroutine,其實2個goroutine來自http,2個goroutine來自https,1個main goroutine。

我們來看下源碼的具體實現。net/http底層通過一個叫idleConn的map去存空閑連接,也就是空閑連接池。

idleConn這個map的key是協議和地址,其實本質上就是ip和端口。map的value是長連接的數組([]*persistConn),說明net/http支持為同一個地址建立多個TCP連接,這樣可以提升傳輸的吞吐。

連接池的結構和邏輯

Transport是什么?

Transport本質上是一個用來控制http調用行為的一個組件,里面包含超時控制,連接池等,其中最重要的是連接池相關的配置。

我們通過下面的例子感受下。

func main() {
    n := 5
    for i := 0; i < n; i++ {
        httpClient := &http.Client{}
        resp, _ := httpClient.Get("https://www.baidu.com")
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}
func main() {
    n := 5
    for i := 0; i < n; i++ {
        httpClient := &http.Client{
            Transport:  &http.Transport{},
        }
        resp, _ := httpClient.Get("https://www.baidu.com")
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

上面的代碼第一個例子的代碼會輸出3。分別是main goroutine + read goroutine + write goroutine,也就是有一個被不斷復用的TCP連接。

在第二例子中,當我們在每次client中都創建一個新的http.Transport,此時就會輸出11。

說明TCP連接沒有復用,每次請求都會產生新的連接。這是因為每個http.Transport內都會維護一個自己的空閑連接池,如果每個client都創建一個新的http.Transport,就會導致底層的TCP連接無法復用。如果網絡請求過大,上面這種情況會導致協程數量變得非常多,導致服務不穩定。

因此,最佳實踐是所有client都共用一個transport。

func main() {
    tr := &http.Transport{
        MaxIdleConns:    100,
        IdleConnTimeout: 3 * time.Second,
    }

    n := 5
    for i := 0; i < n; i++ {
        req, _ := http.NewRequest("POST", "https://www.baidu.com", nil)
        req.Header.Add("content-type", "application/json")
        client := &http.Client{
            Transport: tr,
            Timeout:   3 * time.Second,
        }
        resp, _ := client.Do(req)
        _, _ = ioutil.ReadAll(resp.Body)
        _ = resp.Body.Close()
    }
    time.Sleep(time.Second * 1)
    fmt.Printf("goroutine num is %d\n", runtime.NumGoroutine())
}

如果創建客戶端的時候不指定http.Client,會默認所有http.Client都共用同一個DefaultTransport。這一點可以從源碼里看出。

默認使用DefaultTransport

DefaultTransport

因此當第二段代碼中,每次都重新創建一個Transport的時候,每個Transport內都會各自維護一個空閑連接池。因此每次建立長連接后都會多兩個協程(讀+寫),對應1個main goroutine+(read goroutine + write goroutine)* 5 =11。

別設置 Transport.Dail里的SetDeadline

http.Transport.Dial的配置里有個SetDeadline,它表示連接建立后發送接收數據的超時時間。聽起來跟client.Timeout很像。

那么他們有什么區別呢?我們通過一個例子去看下。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "time"
)

var tr *http.Transport

func init() {
    tr = &http.Transport{
        MaxIdleConns: 100,
        Dial: func(netw, addr string) (net.Conn, error) {
            conn, err := net.DialTimeout(netw, addr, time.Second*2) //設置建立連接超時
            if err != nil {
                return nil, err
            }
            err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設置發送接受數據超時
            if err != nil {
                return nil, err
            }
            return conn, nil
        },
    }
}

func main() {
    for {
        _, err := Get("http://www.baidu.com/")
        if err != nil {
            fmt.Println(err)
            break
        }
    }
}


func Get(url string) ([]byte, error) {
    m := make(map[string]interface{})
    data, err := json.Marshal(m)
    if err != nil {
        return nil, err
    }
    body := bytes.NewReader(data)
    req, _ := http.NewRequest("Get", url, body)
    req.Header.Add("content-type", "application/json")

    client := &http.Client{
        Transport: tr,
    }
    res, err := client.Do(req)
    if res != nil {
        defer res.Body.Close()
    }
    if err != nil {
        return nil, err
    }
    resBody, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return nil, err
    }
    return resBody, nil
}

上面這段代碼,我們設置了SetDeadline為3s,當你執行一段時間,會發現請求baidu會超時,但其實baidu的接口很快,不可能超過3s。

在生產環境中,假如是你的服務調用下游服務,你看到的現象就是,你的服務顯示3s超時了,但下游服務可能只花了200ms就已經響應你的請求了,并且這是隨機發生的問題。遇到這種情況,我們一般會認為是“網絡波動”。

但如果我們去對網絡抓包,就很容易發現問題的原因 。

抓包結果

可以看到,在tcp三次握手之后,就會開始多次網絡請求。直到3s的時候,就會觸發RST包,斷開連接。也就是說,我們設置的SetDeadline,并不是指單次http請求的超時是3s,而是指整個tcp連接的存活時間是3s,計算長連接被連接池回收,這個時間也不會重置。

SetDeadline的解釋

我實在想不到什么樣的場景會需要這個功能,因此我的建議是,不要使用它。

下面是修改后的代碼。這個問題其實在我另外一篇文章有過詳細的解釋,如果你對源碼解析感興趣的話,可以去看看。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    "time"
)

var tr *http.Transport

func init() {
    tr = &http.Transport{
        MaxIdleConns: 100,
        // 下面的代碼被干掉了
        //Dial: func(netw, addr string) (net.Conn, error) {
        // conn, err := net.DialTimeout(netw, addr, time.Second*2) //設置建立連接超時
        // if err != nil {
        //  return nil, err
        // }
        // err = conn.SetDeadline(time.Now().Add(time.Second * 3)) //設置發送接受數據超時
        // if err != nil {
        //  return nil, err
        // }
        // return conn, nil
        //},
    }
}
func Get(url string) ([]byte, error) {
    m := make(map[string]interface{})
    data, err := json.Marshal(m)
    if err != nil {
        return nil, err
    }
    body := bytes.NewReader(data)
    req, _ := http.NewRequest("Get", url, body)
    req.Header.Add("content-type", "application/json")

    client := &http.Client{
        Transport: tr,
        Timeout: 3*time.Second,  // 超時加在這里,是每次調用的超時
    }
    res, err := client.Do(req) 
    if res != nil {
        defer res.Body.Close()
    }
    if err != nil {
        return nil, err
    }
    resBody, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return nil, err
    }
    return resBody, nil
}

func main() {
    for {
        _, err := Get("http://www.baidu.com/")
        if err != nil {
            fmt.Println(err)
            break
        }
    }
}

總結

golang的net/http部分有不少細節點,直接上源碼分析怕勸退不少人,所以希望以幾個例子作為引子展開話題然后深入了解它的內部實現。總體內容比較碎片化,但這個庫的重點知識點基本都在這里面了。希望對大家后續排查問題有幫助。

責任編輯:姜華 來源: 小白debug
相關推薦

2020-07-14 08:45:13

Flink特性jira

2023-07-31 14:01:25

數據中心SDN

2012-12-28 18:09:21

2025-04-24 09:01:46

2025-04-21 08:00:56

2025-04-14 00:00:04

2025-04-23 08:02:40

2025-04-27 08:00:35

2025-04-30 09:02:46

2025-04-22 08:02:23

2025-04-27 00:00:01

Go 1.16Go 1.15接口

2025-04-21 00:00:00

Go 開發Go 語言Go 1.9

2025-04-21 00:05:00

2019-09-20 08:47:51

微信表情小程序

2025-04-29 08:03:18

2025-04-18 08:07:12

2025-04-17 08:00:48

2025-04-28 08:00:56

2025-05-06 08:00:35

2025-05-06 05:00:00

點贊
收藏

51CTO技術棧公眾號

免费一级片在线观看| 天美星空大象mv在线观看视频| 国产精品1234区| 狼人天天伊人久久| 91成人在线观看喷潮| 深田咏美在线x99av| 中文字幕在线观看你懂的| 日韩国产一区二区| 精品久久国产字幕高潮| 日韩av播放器| 天堂8中文在线| 91女神在线视频| 成人女保姆的销魂服务| 国产第一页第二页| 精品美女久久久| 欧美精品一区二区三区视频 | 亚洲AV成人精品| 亚洲人体视频| 亚洲一区二区三区四区在线观看| 精品视频第一区| 一级黄色片在线观看| 在线综合视频| 欧美巨乳美女视频| 婷婷丁香综合网| 日韩影视高清在线观看| 91精品国产麻豆国产自产在线| 国产一级爱c视频| 黄色免费网站在线观看| 久久精品人人爽人人爽| 国内精品二区| 性中国古装videossex| 青娱乐精品视频在线| 97在线观看视频| 一区二区三区在线观看免费视频| 天堂久久av| 在线成人免费视频| 婷婷激情四射五月天| 深夜av在线| 亚洲大片在线观看| 大陆极品少妇内射aaaaaa| 国产三区四区在线观看| 91美女蜜桃在线| 精品国产中文字幕| 人妻一区二区三区免费| 国产91精品在线观看| 成人a在线观看| 中文字幕日韩经典| 日本91福利区| 日本精品一区二区三区在线播放视频| 国产日产在线观看| 成人激情视频| 中文字幕亚洲欧美在线| 亚洲av无码一区二区三区人| 亚洲尤物av| 国产偷亚洲偷欧美偷精品| 国产黑丝一区二区| 女同另类激情重口| 日韩精品中文字| 中文字幕av网址| 久久av资源| 一区二区在线视频| 伊人久久久久久久久久久久久久| 国产亚洲一卡2卡3卡4卡新区| 亚洲精品v天堂中文字幕| 国产视频久久久久久| 人妖一区二区三区| 亚洲三级黄色在线观看| 成人黄色免费网址| 日本久久精品| 欧美成年人视频网站欧美| 玖玖爱免费视频| 日韩一级网站| 国产精品久久久久久久一区探花 | 欧美性生交大片免费| 热久久精品国产| 久久91视频| 欧美一区二区三区爱爱| 四虎成人免费视频| 九九综合九九| 久久精品成人一区二区三区| 全程偷拍露脸中年夫妇| 狠狠色狠狠色综合日日tαg| 97色在线视频观看| 秋霞av一区二区三区| 蜜臀久久99精品久久久久宅男 | 国产九色porny| 一本大道色婷婷在线| 欧美亚洲一区二区在线| 国产精品久久久久久久99| 国产一区调教| 中文字幕国产日韩| 久久久精品99| 日韩精品一级二级| 亚洲专区中文字幕| 欧洲免费在线视频| 亚洲啪啪综合av一区二区三区| 欧美 亚洲 视频| xxxxxx欧美| 日韩一区二区三区观看| 黑人巨大精品欧美| 欧美成人亚洲| 国产精品高潮呻吟视频| 亚洲精品字幕在线| 亚洲国产精品国自产拍av| 男人草女人视频| 91tv亚洲精品香蕉国产一区| 日韩欧美中文一区二区| 久久久久亚洲av无码a片| 欧美激情偷拍| 国产精品免费福利| 亚洲 欧美 精品| 亚洲三级电影全部在线观看高清| 青青草精品视频在线| 成人免费观看49www在线观看| 亚洲精品国产精品国自产在线| 日本欧美一区二区三区不卡视频| 91精品电影| 国产成人aa精品一区在线播放 | 丝袜美腿诱惑一区二区三区| 日韩欧美国产一区在线观看| 亚洲无人区码一码二码三码的含义 | 美女日韩一区| 亚洲欧美制服综合另类| 久久网中文字幕| 极品销魂美女一区二区三区| 国产一区精品在线| 国产美女福利在线| 欧美日韩在线播| 欧美激情aaa| 国产精品女主播一区二区三区| 国产精品视频白浆免费视频| 四虎国产精品永远| 亚洲午夜在线电影| 国产又粗又猛又爽又黄| 国产精品久久久久久| 国产精品久久久久久久久免费| 日韩一级免费毛片| 夜夜揉揉日日人人青青一国产精品| www.日日操| 亚洲美女久久| 日韩美女主播视频| 久久久久久久久亚洲精品| 精品成人乱色一区二区| 手机免费看av片| 亚洲人体大胆视频| 国产一区二区黄色| 激情视频网站在线播放色| 51精品秘密在线观看| 天天色影综合网| 精品写真视频在线观看 | 看电影就来5566av视频在线播放| 亚洲线精品一区二区三区八戒| 亚洲综合伊人久久| 一本到12不卡视频在线dvd| 91精品视频在线免费观看| 精品自拍一区| 欧美一区午夜视频在线观看| 国产高清在线免费观看| 国产91在线看| 成人在线免费观看av| 日韩最新在线| 国产精品欧美久久久| 欧美激情二区| 日韩欧美成人激情| 可以免费看的av毛片| 91蜜桃免费观看视频| 亚洲第一中文av| 91精品国产福利在线观看麻豆| 91精品在线观看视频| 在线你懂的视频| 精品成人在线观看| 欧美精品韩国精品| 国产精品久久夜| 亚洲丝袜在线观看| 亚洲国产综合在线看不卡| 秋霞在线观看一区二区三区| jizz亚洲女人高潮大叫| 久久精品国产亚洲7777| 亚洲男女视频在线观看| 欧美日韩中文在线观看| 一级片黄色录像| 国产成人高清视频| 少妇高清精品毛片在线视频 | 亚洲午夜精品国产| 美国十次综合久久| 57pao成人国产永久免费| chinese偷拍一区二区三区| 日韩午夜在线观看视频| 日韩一区二区视频在线| 中文字幕一区二区视频| 久久人妻少妇嫩草av无码专区| 久久精品综合| 免费在线精品视频| 亚洲都市激情| 91高跟黑色丝袜呻吟在线观看| 国产粉嫩在线观看| 日韩视频一区在线| 亚洲aⅴ在线观看| 欧美丰满美乳xxx高潮www| 日韩三级免费看| 一色屋精品亚洲香蕉网站| 国产精品久久AV无码| 精品综合免费视频观看| 久久久999视频| 天天久久综合| 日韩高清在线播放| 大伊香蕉精品在线品播放| 国产精品入口免费视频一| 成人在线高清免费| 久久久91精品国产| 毛片免费在线播放| 亚洲大胆人体视频| av av片在线看| 欧美日韩国产首页| 香蕉影院在线观看| 亚洲v精品v日韩v欧美v专区 | 亚洲综合成人网| 国产一区第一页| 2020国产精品| 中文在线观看免费视频| 国产一区二区三区在线观看免费 | 欧美日韩视频免费观看| 久久久亚洲精选| a级网站在线播放| 国产一区二区三区直播精品电影| 亚洲国产成人在线观看| 69堂精品视频| 亚洲一卡二卡在线| 91国内精品野花午夜精品| 国产成人无码一区二区三区在线| 国产精品灌醉下药二区| 日本一道本视频| 久久这里只有精品6| 精品久久久久一区二区| 国产一区二区精品在线观看| 九九九九九九九九| 美腿丝袜亚洲色图| 另类小说第一页| 天堂va蜜桃一区二区三区| 国产一区二区三区精彩视频| 亚洲激情精品| 欧美中文字幕在线观看视频 | 国产成人一二三区| 久久久9色精品国产一区二区三区| 麻豆精品蜜桃一区二区三区| 好吊妞视频这里有精品 | 性色av一区二区三区免费| 亚洲图区一区| 欧美成人网在线| 手机av在线播放| 欧美第一页在线| 美洲精品一卡2卡三卡4卡四卡| 精品国产一区av| 国产素人视频在线观看| 北条麻妃久久精品| 婷婷在线视频| 久久九九全国免费精品观看| caopeng在线| 久久久久久有精品国产| av色在线观看| 91精品国产电影| 久久精品女人天堂av免费观看| 欧美在线www| 秋霞国产精品| 国产在线日韩在线| 91精品啪在线观看国产手机 | 国产黄色免费大片| 日韩免费福利电影在线观看| 成人爽a毛片一区二区| 亚洲精品理论电影| 韩国三级av在线免费观看| 在线观看国产欧美| 国产精品刘玥久久一区| 久久久噜噜噜久久中文字免| 美女网站在线看| 国产成人拍精品视频午夜网站| 澳门av一区二区三区| 91网在线免费观看| 国产精品中文字幕制服诱惑| 久久福利电影| 日韩中文在线电影| 国内少妇毛片视频| 久久综合影音| 午夜一级免费视频| av一区二区三区| 国产又大又粗又爽的毛片| 亚洲欧洲99久久| 日韩精品视频免费播放| 欧美在线|欧美| 国产高清第一页| 亚洲天堂男人天堂女人天堂| 理论片午午伦夜理片在线播放| 色综合久久悠悠| 成人激情综合| 亚洲综合av影视| 国产一区二区三区网| 男人日女人的bb| 久久亚洲欧洲| 无码人妻丰满熟妇啪啪网站| 国产拍欧美日韩视频二区| 免费在线视频观看| 91国偷自产一区二区三区成为亚洲经典| 中文字幕有码无码人妻av蜜桃| 欧美一区二区三区成人| 九色视频在线观看免费播放| 超碰97人人做人人爱少妇| 成人私拍视频| 99久久自偷自偷国产精品不卡| 琪琪久久久久日韩精品| 在线观看一区欧美| 久久一区二区三区四区五区 | 真实国产乱子伦对白视频| 视频一区中文字幕国产| 中文在线观看免费视频| 亚洲欧洲国产日韩| 日本高清不卡码| 亚洲成人精品久久久| 看女生喷水的网站在线观看| 国产91热爆ts人妖在线| 精品福利一区| 男人添女人下部视频免费| 久久精品国产99| 欧美做受xxxxxⅹ性视频| 亚洲成人av电影在线| a级片在线免费看| 自拍偷拍亚洲欧美| 电影网一区二区| 久久综合九色欧美狠狠| 亚洲激情社区| 波多野结衣三级视频| 国产精品盗摄一区二区三区| 7799精品视频天天看| 亚洲黄色www| 福利影院在线看| 懂色中文一区二区三区在线视频| 精品国产91久久久久久浪潮蜜月| 欧美激情亚洲天堂| 丁香婷婷综合色啪| 少妇久久久久久被弄高潮| 欧美美女bb生活片| 91高清在线| 91精品国产综合久久久久久久久| 九九视频精品全部免费播放| 国产中文字幕视频在线观看| 成人av在线资源| 国产成人精品亚洲男人的天堂| 91精品国产麻豆国产自产在线| 色开心亚洲综合| 91精品久久久久久| 天天综合一区| 午夜免费福利网站| 一区二区在线观看视频 | 亚洲第一偷拍网| 97天天综合网| 久久久久久久久久久久久久一区 | 成人3d动漫一区二区三区| 91麻豆免费视频| 亚洲 欧美 成人| 在线成人激情黄色| 亚洲伊人精品酒店| 狠狠干视频网站| 91亚洲精品一区二区乱码| www.国产高清| 亚洲视频999| 亚洲狼人综合| 免费cad大片在线观看| 成人一区在线看| 好看的av在线| 中文字幕亚洲在线| 国产成人免费视频网站视频社区| 综合久久国产| 成人短视频下载| caoporn国产| 综合网中文字幕| 亚洲3区在线| 国产91在线视频观看| 日本一区二区免费在线| 国产成人精品无码高潮| 77777少妇光屁股久久一区| 国产真实有声精品录音| 亚洲网中文字幕| 性做久久久久久| 福利片在线观看| 懂色一区二区三区av片| 美女国产精品| 免费成人深夜夜行网站| 亚洲成人免费网站| 成人看片网页| 成人一区二区av| 国产色产综合产在线视频| 国产乱码久久久| 欧美一二三视频| 一二三区不卡| 9.1成人看片免费版| 91精品国产色综合久久ai换脸| 9lporm自拍视频区在线| 亚洲精品自在在线观看| av亚洲产国偷v产偷v自拍| 中文字幕 国产| 久久久欧美一区二区|