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

圖文講透Golang標準庫 net/http實現原理 - 客戶端

開發 前端
Client.Get() 根據用戶的入參,請求參數 NewRequest使用上下文包裝NewRequestWithContext ,接著通過 Client.Do 方法,處理這個請求。

客戶端的內容將是如何發送請求和接收響應,走完客戶端就把整個流程就完整的串聯起來了!

這次我把調用的核心方法和流程走讀的函數也貼出來,這樣看應該更有邏輯感,重要部分用紅色標記了一下,可以著重看下。

圖片圖片

先了解下核心數據結構Client和Request。

Client結構體

type Client struct { 
    Transport RoundTripper 
    CheckRedirect func(req *Request, via []*Request) error 
    Jar CookieJar 
    Timeout time.Duration
}

四個字段分別是:

  • ? Transport:表示 HTTP 事務,用于處理客戶端的請求連接并等待服務端的響應;
  • ? CheckRedirect:處理重定向的策略
  • ? Jar:管理和存儲請求中的 cookie
  • ? Timeout:超時設置

Request結構體

Request字段較多,這里就列舉一下常見的一些字段

type Request struct {
    Method string
    URL *url.URL
    Header Header
    Body io.ReadCloser
    Host string
    Response *Response
    ...
}
  • ? Method:指定的HTTP方法(GET、POST、PUT等)
  • ? URL:請求路徑
  • ? Header:請求頭
  • ? Body:請求體
  • ? Host:服務器主機
  • ? Response:響應參數

構造請求

var DefaultClient = &Client{}

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

示例HTTP 的 Get方法會調用到 DefaultClient 的 Get 方法,,然后調用到 Client 的 Get 方法。

DefaultClient 是 Client 的一個空實例(跟DefaultServeMux有點子相似)

圖片圖片

Client.Get

func (c *Client) Get(url string) (resp *Response, err error) {
    req, err := NewRequest("GET", url, nil)
    if err != nil {
        return nil, err
    }
    return c.Do(req)
}

func NewRequest(method, url string, body io.Reader) (*Request, error) {
    return NewRequestWithContext(context.Background(), method, url, body)
}

Client.Get() 根據用戶的入參,請求參數 NewRequest使用上下文包裝NewRequestWithContext ,接著通過 Client.Do 方法,處理這個請求。

NewRequestWithContext

func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
    ...
    // 解析url
    u, err := urlpkg.Parse(url)
    ...
    rc, ok := body.(io.ReadCloser)
    if !ok && body != nil {
        rc = ioutil.NopCloser(body)
    } 
    u.Host = removeEmptyPort(u.Host)
    req := &Request{
        ctx:        ctx,
        Method:     method,
        URL:        u,
        Proto:      "HTTP/1.1",
        ProtoMajor: 1,
        ProtoMinor: 1,
        Header:     make(Header),
        Body:       rc,
        Host:       u.Host,
    } 
    ...
    return req, nil
}

NewRequestWithContext 函數主要是功能是將請求封裝成一個 Request 結構體并返回,這個結構體的名稱是req。

準備發送請求

構造好的Request結構req,會傳入c.Do()方法。

我們看下發送請求過程調用了哪些方法,用下圖表示下

圖片圖片

?? 其實不管是Get還是Post請求的調用流程都是一樣的,只是對外封裝了Post和Get請求

func (c *Client) do(req *Request) (retres *Response, reterr error) {
    ...
    for {
        ...
        resp, didTimeout, err = send(req, deadline)
        if err != nil {
            return nil, didTimeout, err
        }
    }
    ...
}
//Client 調用 Do 方法處理發送請求最后會調用到 send 函數中
func (c *Client) send(req *Request, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
    resp, didTimeout, err = send(req, c.transport(), deadline)
    if err != nil {
        return nil, didTimeout, err
    }
    ...
    return resp, nil, nil
}

c.transport()方法是為了回去Transport的默認實例 DefaultTransport ,我們看下DefaultTransport長什么樣。

DefaultTransport

var DefaultTransport RoundTripper = &Transport{
    Proxy: ProxyFromEnvironment,
    DialContext: defaultTransportDialContext(&net.Dialer{
        Timeout:   30 * time.Second,
        KeepAlive: 30 * time.Second,
    }),
    ForceAttemptHTTP2:     true,
    MaxIdleConns:          100,
    IdleConnTimeout:       90 * time.Second,
    TLSHandshakeTimeout:   10 * time.Second,
    ExpectContinueTimeout: 1 * time.Second,
}

可以根據需要建立網絡連接,并緩存它們以供后續調用重用,部分參數如下:

  • ? MaxIdleConns:最大空閑連接數
  • ? IdleConnTimeout:空閑連接超時時間
  • ? ExpectContinueTimeout:預計繼續超時

注意這里的RoundTripper是個接口,也就是說 Transport 實現 RoundTripper 接口,該接口方法接收Request,返回Response。

RoundTripper

type RoundTripper interface { 
    RoundTrip(*Request) (*Response, error)
}

圖片圖片

雖然還沒看完后面邏輯,不過我們猜測RoundTrip方法可能是實際處理客戶端請求的實現。

我們繼續追下后面邏輯,看下是否能驗證這個猜想。

func send(ireq *Request, rt RoundTripper, deadline time.Time) (resp *Response, didTimeout func() bool, err error) {
    ...
    resp, err = rt.RoundTrip(req)
    if err != nil {
        ...
    }
    ..
}

?? 你看send函數的第二個參數就是接口類型,調用層傳遞的Transport的實例DefaultTransport。

而rt.RoundTrip()方法的調用具體在net/http/roundtrip.go文件中,這也是RoundTrip接口的實現,代碼如下:

func (t *Transport) RoundTrip(req *Request) (*Response, error) {
    return t.roundTrip(req)
}

Transport.roundTrip 方法概況來說干了這些事:

  • ? 封裝請求transportRequest
  • ? 調用 Transport 的 getConn 方法獲取連接
  • ? 在獲取到連接后,調用 persistConn 的 roundTrip 方法等待請求響應結果
func (t *Transport) roundTrip(req *Request) (*Response, error) {
    ...  
    for {
        ...
        // 請求封裝
        treq := &transportRequest{Request: req, trace: trace, cancelKey: cancelKey} 
        cm, err := t.connectMethodForRequest(treq)
        if err != nil {
            ...
        } 
        // 獲取連接
        pconn, err := t.getConn(treq, cm)
        if err != nil {
            ...
        }
        
        // 等待響應結果
        var resp *Response
        if pconn.alt != nil {
            t.setReqCanceler(cancelKey, nil) 
            resp, err = pconn.alt.RoundTrip(req)
        } else {
            resp, err = pconn.roundTrip(treq)
        }
        ...
    }
}

封裝請求transportRequeste沒啥好說的,因為treq被roundTrip修改,所以這里需要為每次重試重新創建。

獲取連接

獲取連接的方法是 getConn,這里代碼還是比較長的,會有不同的兩種方式去獲取連接:

  1. 1. 調用 queueForIdleConn 排隊等待獲取空閑連接
  2. 2. 如果獲取空閑連接失敗,那么調用 queueForDial 異步創建一個新的連接,并通過channel來接收readdy信號,來確認連接是否構造完成

圖片圖片

getConn

func (t *Transport) getConn(treq *transportRequest, cm connectMethod) (pc *persistConn, err error) {
    ...
    //  初始化wantConn結構體
    w := &wantConn{
        cm:         cm,
        key:        cm.key(),
        ctx:        ctx,
        ready:      make(chan struct{}, 1),
        beforeDial: testHookPrePendingDial,
        afterDial:  testHookPostPendingDial,
    }
    ...
    // 獲取空閑連接
    if delivered := t.queueForIdleConn(w); delivered {
        ...
    }
 
    // 異步創建新連接
    t.queueForDial(w)
 
    select {
    // 阻塞等待獲取到連接完成
    case <-w.ready:
        ...
        return w.pc, w.err
    ...
}

queueForIdleConn獲取空閑連接

獲取成功

成功空閑獲取連接Conn流程如下圖

圖片圖片

  1. 1. 根據wantConn的key從 transport.idleConn 這個map中查找,看是否存不存在空閑的 connection 列表
  2. 2. 獲取到空閑的 connection 列表后,從列表中拿最后一個 connection
  3. 3. 獲取到連接后會調用 wantConn.tryDeliver 方法將連接綁定到 wantConn 請求參數上

獲取失敗

圖片圖片

當不存在該請求的 connection 列表,會將當前 wantConn 加入到名稱為 idleConnWait 的等待空閑map中。

不過此時的idleConnWait這個map的值是個隊列

queueForIdleConn方法

從上面的兩張圖解中差不多能看出是如何獲取空閑連接和如何獲取失敗時如何做的了,這里也貼下代碼體驗下,讓大家更清楚里面的實現邏輯。

//idleConn是map類型,指定key返回切片列表
idleConn     map[connectMethodKey][]*persistConn 
//idleConnWait,指定key返回隊列
idleConnWait map[connectMethodKey]wantConnQueue

這里將獲取空閑連接的代碼實現多進行注釋,更好理解一些!

func (t *Transport) queueForIdleConn(w *wantConn) (delivered bool) {
    //參數判斷
    if t.DisableKeepAlives {
        return false
    }

    if w == nil { 
        return false
    }
 
    // 計算空閑連接超時時間
    var oldTime time.Time
    if t.IdleConnTimeout > 0 {
        oldTime = time.Now().Add(-t.IdleConnTimeout)
    }
    //從idleConn根據w.key找對應的persistConn 列表
    if list, ok := t.idleConn[w.key]; ok {
        stop := false
        delivered := false
        for len(list) > 0 && !stop {
            // 找到persistConn列表最后一個
            pconn := list[len(list)-1] 
            // 檢查這個 persistConn 是不是過期
            tooOld := !oldTime.IsZero() && pconn.idleAt.Round(0).Before(oldTime)
            if tooOld {
                //如果過期進行異步清理
                go pconn.closeConnIfStillIdle()
            }
            // 該 persistConn 被標記為 broken 或 閑置太久 continue
            if pconn.isBroken() || tooOld { 
                list = list[:len(list)-1]
                continue
            }
            // 嘗試將該 persistConn 寫入到 wantConn(w)中
            delivered = w.tryDeliver(pconn, nil)
            if delivered {
                // 寫入成功,將persistConn從空閑列表中移除
                if pconn.alt != nil { 
                } else { 
                    t.idleLRU.remove(pconn)
                    //缺省了最后一個conn
                    list = list[:len(list)-1]
                }
            }
            stop = true
        }
        //對被獲取連接后的列表進行判斷
        if len(list) > 0 {
            t.idleConn[w.key] = list
        } else {
            // 如果該 key 對應的空閑列表不存在,那么將該key從字典中移除
            delete(t.idleConn, w.key)
        }
        if stop {
            return delivered
        }
    } 
    // 如果找不到空閑的 persistConn
    if t.idleConnWait == nil {
        t.idleConnWait = make(map[connectMethodKey]wantConnQueue)
    }
    // 將該 wantConn添加到等待空閑idleConnWait中
    q := t.idleConnWait[w.key] 
    q.cleanFront()
    q.pushBack(w)
    t.idleConnWait[w.key] = q
    return false
}

我們知道了為找到的空閑連接會被放到空閑 idleConnWait 這個等待map中,最后會被Transport.tryPutIdleConn方法將pconne添加到等待新請求的空閑持久連接列表中。

queueForDial創建新連接

queueForDial意思是排隊等待撥號,為什么說是等帶呢,因為最終的結果是在ready這個channel上進行通知的。

流程如下圖:

圖片圖片

我們先看下Transport結構體的這兩個map,名稱不一樣map的屬性和解釋都是一樣的,其中idleConnWait是在沒查找空閑連接的時候存放當前連接的map。

而connsPerHostWait用在了創建新連接的地方,可以猜測一下創建新鏈接的地方就是將當前的請求放入到 connsPerHostWait 等待map中。

// waiting getConns
idleConnWait map[connectMethodKey]wantConnQueue 
// waiting getConns
connsPerHostWait map[connectMethodKey]wantConnQueue

Transport.queueForDial

func (t *Transport) queueForDial(w *wantConn) {
    w.beforeDial()
    // 小于等于零,意思是限制,直接異步建立連接
    if t.MaxConnsPerHost <= 0 {
        go t.dialConnFor(w)
        return
    }
    ...
    //host建立的連接數沒達到上限,執行異步建立連接
    if n := t.connsPerHost[w.key]; n < t.MaxConnsPerHost {
        if t.connsPerHost == nil {
            t.connsPerHost = make(map[connectMethodKey]int)
        }
        t.connsPerHost[w.key] = n + 1
        go t.dialConnFor(w)
        return
    }
    //進入等待隊列
    if t.connsPerHostWait == nil {
        t.connsPerHostWait = make(map[connectMethodKey]wantConnQueue)
    }
    q := t.connsPerHostWait[w.key]
    q.cleanFront()
    q.pushBack(w)
    t.connsPerHostWait[w.key] = q
}

在獲取不到空閑連接之后,會嘗試去建立連接:

  1. 1. queueForDial 方法的內部會先校驗 MaxConnsPerHost 是否未設置和是否已達上限
  2. 1.
  1. 1. 檢驗不通過則將當前的請求放入到 connsPerHostWait 這個等待map中
  2. 2. 校驗通過那么會異步的調用 dialConnFor 方法創建連接

??那會不會queueForDial方法中將idleConnWait和connsPerHostWait打包到等待空閑連接idleConn這個map中呢?

我們繼續看dialConnFor的實現,它會給我們這個問題的答案!

dialConnFor

func (t *Transport) dialConnFor(w *wantConn) {
    defer w.afterDial()
    //創建 persistConn
    pc, err := t.dialConn(w.ctx, w.cm)
    //綁定到 wantConn
    delivered := w.tryDeliver(pc, err)
    if err == nil && (!delivered || pc.alt != nil) {
        //綁定wantConn失敗
        //放到存放空閑連接idleConn的map中
        t.putOrCloseIdleConn(pc)
    }
    if err != nil {
        t.decConnsPerHost(w.key)
    }
}
  • ? dialConnFor 先調用 dialConn 方法創建 TCP 連接
  • ? 調用 tryDeliver 將連接綁定到 wantConn 上,綁定成功的話,就將該鏈接放到空閑連接的idleConn這個map中
  • ? 綁定失敗的話會調用decConnsPerHost方法,用遞減密鑰的每主機連接計數方式,繼續異步調用Transport.dialConnFor

我們可以追蹤下代碼會發現Transport.tryPutIdleConn() 方法就是將persistConn添加到等待的空閑持久連接列表中的實現。

Transport.dialConn創建連接

func (t *Transport) dialConn(ctx context.Context, cm connectMethod) (pconn *persistConn, err error) {
    pconn = &persistConn{
        t:             t,
        cacheKey:      cm.key(),
        reqch:         make(chan requestAndChan, 1),
        writech:       make(chan writeRequest, 1),
        closech:       make(chan struct{}),
        writeErrCh:    make(chan error, 1),
        writeLoopDone: make(chan struct{}),
    }
    ...
    // 創建 tcp 連接,給pconn.conn
    conn, err := t.dial(ctx, "tcp", cm.addr())
    if err != nil {
        return nil, wrapErr(err)
    }
    pconn.conn = conn
    ...
    //開啟兩個goroutine處理讀寫
    go pconn.readLoop()
    go pconn.writeLoop()
    return pconn, nil
}

?? 看完這個創建persistConn的代碼是不是心里仿佛懂了什么?

上述代碼中HTTP 連接的創建過程是建立 tcp 連接,然后為連接異步處理讀寫數據,最后將創建好的連接返回。

我們可以看到創建的每個連接會分別創建兩個goroutine循環地進行進行讀寫的處理,這就是為什么我們連接能接受請求參數和處理請求的響應的關鍵。

?? 這兩個協程功能是這樣的!

  1. 1. persisConn.writeLoop(),通過 persistConn.writech 通道讀取到客戶端提交的請求,將其發送到服務端
  2. 2. persisConn.readLoop(),讀取來自服務端的響應,并添加到 persistConn.reqCh 通道中,給persistConn.roundTrip 方法接收

想看這兩個協程

等待響應

persistConn 連接本身創建了兩個讀寫goroutine,而這兩個goroutine就是通過兩個channel進行通信的。

這個通信就是在persistConn.roundTrip()方法中的進行傳遞交互的,其中writech 是用來寫入請求數據,reqch是用來讀取響應數據。

圖片圖片

func (pc *persistConn) roundTrip(req *transportRequest) (resp *Response, err error) {
    ...
    // 請求數據寫入到 writech channel中
    pc.writech <- writeRequest{req, writeErrCh, continueCh}

    // 接收響應的channel
    resc := make(chan responseAndError)
    // 接收響應的結構體 requestAndChan 寫到 reqch channel中
    pc.reqch <- requestAndChan{
        req:        req.Request,
        cancelKey:  req.cancelKey,
        ch:         resc,
        ...
    }
    ...
    for {
        ...
        select { 
            // 接收到響應數據
        case re := <-resc:
            ...
            // return響應數據
            return re.res, nil
        ...
    }
}

1. 連接獲取到之后,會調用連接的 roundTrip 方法,將請求數據寫入到 persisConn.writech channel中,而連接 persisConn 中的協程 writeLoop() 接收到請求后就會處理請求

2. 響應結構體 requestAndChan 寫入到 persisConn.reqch 中

3. 通過readLoop 接受響應數據,然后讀取 resc channel 的響應結果

4. 接受到響應數據之后循環結束,連接處理完成

好了,net/http標準庫的客戶端構造請求、發送請求、接受服務端的請求數據流程就講完了,看完之后是否意欲未盡呢?

還別說,小許也是第一次看是如何實現的,確實還是了解到了點東西呢!

責任編輯:武曉燕 來源: 小許code
相關推薦

2024-01-29 08:04:48

Golang標準庫服務端

2021-10-18 05:00:38

語言GoRequestHTTP

2021-05-07 15:28:03

Kafka客戶端Sarama

2022-02-20 23:15:46

gRPCGolang語言

2023-10-12 07:54:02

.NETXamarin框架

2009-08-18 15:43:56

ASP.NET生成客戶端腳本

2009-07-24 17:31:56

ASP.NET AJA

2020-03-24 15:15:29

HttpClientOkHttpJava

2021-08-01 23:18:21

Redis Golang命令

2024-05-09 08:30:57

OkHttpHTTP客戶端

2025-03-14 09:20:46

2024-05-29 07:30:41

2023-10-11 07:00:44

高可用程序客戶端

2024-10-10 15:54:44

.NET開源Redis

2022-04-20 08:32:09

RabbitMQ流控制

2009-02-04 17:39:14

ibmdwWebSphereDataPower

2009-10-15 10:46:03

PPC客戶端程序VB.NET創建

2021-09-22 15:46:29

虛擬桌面瘦客戶端胖客戶端

2011-08-17 10:10:59

2024-09-14 08:16:24

Redis客戶端性能
點贊
收藏

51CTO技術棧公眾號

精品无码人妻一区| 又粗又黑又大的吊av| 国产黄色av片| 国产日本精品| 中文字幕9999| 亚洲国产日韩在线一区| 日本蜜桃在线观看视频| 国产精品情趣视频| 国产在线观看一区| 怡红院男人天堂| 一区视频在线| 在线亚洲欧美视频| 国产激情视频网站| 成人综合日日夜夜| 欧美日韩亚洲一区二区| 日本黄xxxxxxxxx100| 无码精品人妻一区二区| 精品在线观看视频| 97avcom| 色偷偷www8888| 美女精品一区最新中文字幕一区二区三区 | 5g国产欧美日韩视频| 91玉足脚交嫩脚丫在线播放| 欧美好骚综合网| 精品一区二区三区四区| 亚洲一区二区三区三州| 成人h在线观看| 欧美日韩精品在线观看| 亚洲国产精品女人| a黄色在线观看| av男人天堂一区| 999视频在线免费观看| 中文字幕1区2区3区| 亚洲一区国产| 久久久视频在线| 少妇aaaaa| 国产欧美日韩影院| 日韩精品视频免费在线观看| 香蕉在线观看视频| www久久久| 欧美在线观看18| 日韩毛片在线免费看| 无码小电影在线观看网站免费| 一区二区三区四区高清精品免费观看| 亚洲精品不卡| h视频在线免费| 久久精品日产第一区二区三区高清版 | 欧美日韩国产成人| 久久久久麻豆v国产| 欧美色婷婷久久99精品红桃| 亚洲人成电影网站色| 国产美女喷水视频| 日韩成人一级| 国产偷国产偷亚洲清高网站| 一级特黄a大片免费| 国产精品玖玖玖在线资源| 欧美大肚乱孕交hd孕妇| 亚洲少妇一区二区三区| jizz性欧美23| 日韩成人在线视频观看| 91精品小视频| 男男gay无套免费视频欧美| 国产视频丨精品|在线观看| 成人免费av片| 欧美在线观看视频一区| 色偷偷888欧美精品久久久| 亚洲欧美va天堂人熟伦| 日韩欧美国产精品综合嫩v| 色妞一区二区三区| 暗呦丨小u女国产精品| 欧美另类女人| 国产91成人在在线播放| 国产成人无码一区二区在线播放| 久久久久久久尹人综合网亚洲 | 黄色成人小视频| 91 com成人网| 扒开伸进免费视频| 思热99re视热频这里只精品| 亚洲午夜未满十八勿入免费观看全集 | 在线播放成人| 精品国产乱码久久久久久久久| 亚洲自拍偷拍精品| 天天躁日日躁狠狠躁欧美| 中文字幕日韩综合av| 四虎永久免费在线| 99视频精品免费观看| 国产精品露脸自拍| 亚洲h视频在线观看| 久久久精品国产免大香伊| 先锋影音男人资源| gogo高清在线播放免费| 欧美亚洲自拍偷拍| 色哟哟在线观看视频| 亚洲区小说区图片区qvod| 精品国产自在精品国产浪潮| 国产在线观看免费av| 日韩激情一二三区| av蓝导航精品导航| 国产综合在线观看| 一区二区欧美精品| 丰满少妇在线观看| 操欧美女人视频| 在线视频精品一| xxxx 国产| 精品一区二区三区久久| 欧美日韩国产精品一卡| 影院在线观看全集免费观看| 91久久一区二区| 免费黄色a级片| 欧美xxav| 国产99久久久欧美黑人| 性少妇videosexfreexxx片| 久久久无码精品亚洲日韩按摩| 黄色一级片av| 激情中国色综合| 亚洲欧美国产精品久久久久久久| 国产精品视频一区二区三 | 国产精品久久久久久| 欧美亚洲国产成人精品| 国产强被迫伦姧在线观看无码| 久久久久综合网| 3d动漫一区二区三区| 久久av网站| 日韩在线国产精品| 伊人成年综合网| 99re这里只有精品6| 国产又粗又猛又爽又黄的网站| 欧美成人app| 亚洲丝袜在线视频| 精品欧美一区二区三区免费观看 | 香蕉久久aⅴ一区二区三区| 在线看日韩精品电影| 三叶草欧洲码在线| 一区免费视频| 国产精品国产精品国产专区蜜臀ah | 精品无人区一区二区| 美女视频久久黄| 91麻豆视频在线观看| 中文字幕精品在线不卡| 狠狠热免费视频| 午夜精品福利影院| 欧美在线免费看| 午夜视频免费看| 午夜精品在线视频一区| 佐佐木明希电影| 国内久久精品| 成人综合电影| 超碰97免费在线| 欧美va亚洲va在线观看蝴蝶网| caoporn91| 欧美日韩国产一区二区三区地区| 久久久久久久久久久久久久一区 | 国产偷v国产偷v亚洲高清| 一本大道熟女人妻中文字幕在线 | 亚洲福利影片在线| 久久免费在线观看视频| 国产精品一区二区在线观看网站| 强伦女教师2:伦理在线观看| 欧美一区=区三区| 色爱av美腿丝袜综合粉嫩av| 一二三四区视频| 亚洲丝袜自拍清纯另类| 欧美国产日韩另类 | 久久久久亚洲综合| 青青在线免费观看视频| 欧美电影一区| 亚洲影院在线看| 搞黄网站在线看| 亚洲老板91色精品久久| 波多野结衣午夜| 亚洲色图丝袜美腿| 亚洲免费观看在线| 免费在线日韩av| 视频一区二区三区在线观看| 久久久久伊人| 欧美第一淫aaasss性| 天堂在线观看免费视频| 在线观看国产91| 日本黄色免费片| 成人的网站免费观看| 久久精品99国产| 午夜片欧美伦| 国产日韩精品推荐| 成人精品国产| 欧美日韩成人网| 九色视频在线观看免费播放| 4438成人网| 亚洲黄色小说图片| 亚洲欧美一区二区视频| 免费黄色三级网站| 美女视频一区二区| 无码av天堂一区二区三区| 欧美色图在线播放| 国产成人精品福利一区二区三区 | 一区二区高清不卡| 日韩欧美电影一二三| 国产一级一级国产| 一区二区三区在线高清| 成人免费网站黄| 国产精品2024| 少妇一级淫免费放| 亚洲九九精品| 国产欧美综合一区| 国产亚洲电影| 国产欧美日韩综合一区在线观看| 国产精品久久久久久久久久齐齐| 久久久久久久久久久网站| 91看片在线观看| 日韩精品免费在线观看| www.香蕉视频| 欧美视频在线一区二区三区| 日韩精品久久久久久久酒店| 亚洲日本在线观看| 无码一区二区三区在线| 成人免费视频视频| 青青草原播放器| 日产国产高清一区二区三区| 中文字幕无码精品亚洲资源网久久| 久久裸体网站| 日本在线视频一区| 欧美天堂社区| 成人动漫视频在线观看完整版| 久久免费影院| 国产精品日韩在线播放| 五月天av在线| 91高清视频在线免费观看| 青春草视频在线| 久久视频精品在线| 亚洲s色大片| 一区二区三区视频观看| 男女视频在线观看| 亚洲精品美女免费| 黄色av中文字幕| 精品国产乱码久久久久久久| 99热这里只有精品1| 欧美精品三级日韩久久| 中文无码精品一区二区三区| 色8久久精品久久久久久蜜| av大片免费观看| 午夜精品久久久久影视| 国产一级av毛片| 亚洲一区二区三区四区五区中文| 麻豆精品一区二区三区视频| 亚洲日本一区二区| a在线视频播放观看免费观看| 国产精品久久久久一区| 欧洲性xxxx| 一区二区中文字幕在线| 战狼4完整免费观看在线播放版| 亚洲国产精品成人综合色在线婷婷 | 亚洲日本va午夜在线电影| 亚洲影视九九影院在线观看| 日韩一区二区三区色| av资源站久久亚洲| 波多野结衣欧美| 精品无人区一区二区三区竹菊| 老牛影视av一区二区在线观看| 国产精品三区在线| 老司机aⅴ在线精品导航| 明星裸体视频一区二区| 国内精品久久久久久久久电影网 | 巨大荫蒂视频欧美另类大| 久久精品中文字幕一区| 2021国产在线| 国模精品系列视频| a欧美人片人妖| 国产精品午夜视频| 欧美激情精品| 久久精品aaaaaa毛片| 欧美日韩高清| 91精品国产吴梦梦| 亚洲美女啪啪| 亚洲免费av一区二区三区| 国产毛片精品国产一区二区三区| 91精品国产高清91久久久久久| av在线免费不卡| 天天舔天天操天天干| 亚洲精品欧美综合四区| 国产成人在线播放视频| 欧美在线啊v一区| 国产精品一级视频| 精品国产1区二区| 国产九色在线| 欧美成在线视频| 蜜臀国产一区| 91手机在线播放| 亚洲三级精品| 99热这里只有精品7| 国产精品久久久久久久久久妞妞| 亚洲色图久久久| 丰满岳乱妇一区二区三区| 免费看污片网站| 一区二区成人在线视频| 久久国产乱子伦精品| 91精品一区二区三区在线观看| 少妇一区二区三区四区| 日韩中文字幕在线精品| 精品三级久久| 成人午夜小视频| 亚洲三级精品| 国产精品免费看久久久无码| 日韩精品三区四区| 特黄特色免费视频| 国产精品美女一区二区三区| 久久高清免费视频| 8x8x8国产精品| 国产永久免费高清在线观看| 久久久人成影片一区二区三区观看| 国产一区二区三区影视| 狠狠干一区二区| 香蕉视频国产精品 | 国模一区二区| 国产尤物99| 欧美福利专区| 久久婷婷综合色| 久久在线观看免费| 日本中文字幕免费| 欧美一级久久久久久久大片| av资源种子在线观看| 日本韩国欧美精品大片卡二| 在线综合色站| 国产四区在线观看| 美女视频一区二区| 夫妇交换中文字幕| 色婷婷精品久久二区二区蜜臂av | 在线电影欧美日韩一区二区私密| 密臀av在线播放| 国产一区二区黄色| 欧美精品一区二区三区久久久竹菊| 小明看看成人免费视频| 国产精品三级在线观看| 日本成人一级片| 亚洲人成在线一二| 在线天堂资源www在线污| 国产精品视频免费一区| 激情综合中文娱乐网| 色悠悠在线视频| 亚洲最大色网站| 亚洲av无码乱码在线观看性色| 久热国产精品视频| 久久中文字幕一区二区| 亚洲黄色网址在线观看| 韩国av一区二区三区四区| 亚洲精品久久久久久国| 欧美一区二区三区婷婷月色| 黄色网址在线免费播放| 成人做爰www免费看视频网站| 91视频综合| 亚洲天堂网站在线| 一区二区在线看| 国产综合视频在线| 97色在线播放视频| 亚洲传媒在线| 国产福利影院在线观看| 中文字幕电影一区| 国产精品天天操| 欧美日韩不卡合集视频| 国产 日韩 欧美 综合 一区| a级黄色一级片| 久久久精品欧美丰满| 亚洲天天综合网| 成人97在线观看视频| 伊人久久亚洲| 97成人在线观看视频| 国产亚洲va综合人人澡精品| 艳妇乳肉豪妇荡乳av无码福利| 色悠悠国产精品| 美女久久精品| 国产深夜男女无套内射| 国产亚洲精品久| 国产精品羞羞答答在线| 午夜精品99久久免费| 蜜桃一区二区三区| 日韩爱爱小视频| 亚洲综合999| 免费在线视频你懂得| 成人av番号网| 日韩午夜av| 九九九视频在线观看| 日韩精品资源二区在线| 亚洲天堂手机| 在线观看国产一区| www.66久久| 亚洲一区二区色| 97热在线精品视频在线观看| 不卡在线一区| 精品人妻一区二区免费| 欧美午夜在线一二页| 色呦呦呦在线观看| 欧美下载看逼逼| 国产福利一区二区| 亚洲综合成人av| 97精品免费视频| 久久综合国产| theav精尽人亡av| 欧美一区二区播放| 高清成人在线| 日本丰满少妇xxxx| 亚洲视频在线观看三级| 男女av在线|