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

Fork me on GitHub
隨筆 - 215  文章 - 13  trackbacks - 0
<2017年1月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
2930311234


專注即時通訊及網(wǎng)游服務端編程
------------------------------------
Openresty 官方模塊
Openresty 標準模塊(Opm)
Openresty 三方模塊
------------------------------------
本博收藏大部分文章為轉(zhuǎn)載,并在文章開頭給出了原文出處,如有再轉(zhuǎn),敬請保留相關信息,這是大家對原創(chuàng)作者勞動成果的自覺尊重!!如為您帶來不便,請于本博下留言,謝謝配合。

常用鏈接

留言簿(1)

隨筆分類

隨筆檔案

相冊

Awesome

Blog

Book

GitHub

Link

搜索

  •  

積分與排名

  • 積分 - 220944
  • 排名 - 117

最新評論

閱讀排行榜

KCP-GO源碼解析

概念

ARQ:自動重傳請求(Automatic Repeat-reQuest,ARQ)是OSI模型中數(shù)據(jù)鏈路層的錯誤糾正協(xié)議之一.
RTO:Retransmission TimeOut
FEC:Forward Error Correction

kcp簡介

kcp是一個基于udp實現(xiàn)快速、可靠、向前糾錯的的協(xié)議,能以比TCP浪費10%-20%的帶寬的代價,換取平均延遲降低30%-40%,且最大延遲降低三倍的傳輸效果。純算法實現(xiàn),并不負責底層協(xié)議(如UDP)的收發(fā)。查看官方文檔kcp

kcp-go是用go實現(xiàn)了kcp協(xié)議的一個庫,其實kcp類似tcp,協(xié)議的實現(xiàn)也很多參考tcp協(xié)議的實現(xiàn),滑動窗口,快速重傳,選擇性重傳,慢啟動等。
kcp和tcp一樣,也分客戶端和監(jiān)聽端。

    +-+-+-+-+-+            +-+-+-+-+-+
    
|  Client |            |  Server |
    
+-+-+-+-+-+            +-+-+-+-+-+
        
|------ kcp data ------>|     
        
|<----- kcp data -------|    

kcp協(xié)議
layer model
+----------------------+
|      Session         |
+----------------------+
|      KCP(ARQ)        |
+----------------------+
|      FEC(OPTIONAL)   |
+----------------------+
|      CRYPTO(OPTIONAL)|
+----------------------+
|      UDP(Packet)     |
+----------------------+

KCP header

KCP Header Format

      4           1   1     2 (Byte)
+---+---+---+---+---+---+---+---+
|     conv      |cmd|frg|  wnd  |
+---+---+---+---+---+---+---+---+
|     ts        |     sn        |
+---+---+---+---+---+---+---+---+
|     una       |     len       |
+---+---+---+---+---+---+---+---+
|                               |
+             DATA              +
|                               |
+---+---+---+---+---+---+---+---+

代碼結(jié)構(gòu)
src/vendor/github.com/xtaci/kcp-go/
├── LICENSE
├── README.md
├── crypt.go    加解密實現(xiàn)
├── crypt_test.go
├── donate.png
├── fec.go      向前糾錯實現(xiàn)
├── frame.png
├── kcp
-go.png
├── kcp.go      kcp協(xié)議實現(xiàn)
├── kcp_test.go
├── sess.go     會話管理實現(xiàn)
├── sess_test.go
├── snmp.go     數(shù)據(jù)統(tǒng)計實現(xiàn)
├── updater.go  任務調(diào)度實現(xiàn)
├── xor.go      xor封裝
└── xor_test.go

著重研究兩個文件kcp.gosess.go

kcp淺析

kcp是基于udp實現(xiàn)的,所有udp的實現(xiàn)這里不做介紹,kcp做的事情就是怎么封裝udp的數(shù)據(jù)和怎么解析udp的數(shù)據(jù),再加各種處理機制,為了重傳,擁塞控制,糾錯等。下面介紹kcp客戶端和服務端整體實現(xiàn)的流程,只是大概介紹一下函數(shù)流,不做詳細解析,詳細解析看后面數(shù)據(jù)流的解析。

kcp client整體函數(shù)流

和tcp一樣,kcp要連接服務端需要先撥號,但是和tcp有個很大的不同是,即使服務端沒有啟動,客戶端一樣可以撥號成功,因為實際上這里的撥號沒有發(fā)送任何信息,而tcp在這里需要三次握手。

DialWithOptions(raddr string, block BlockCrypt, dataShards, parityShards int)
    V
net.DialUDP(
"udp", nil, udpaddr)
    V
NewConn()
    V
newUDPSession() {初始化UDPSession}
    V
NewKCP() {初始化kcp}
    V
updater.addSession(sess) {管理session會話,任務管理,根據(jù)用戶設置的internal參數(shù)間隔來輪流喚醒任務}
    V
go sess.readLoop()
    V
go s.receiver(chPacket)
    V
s.kcpInput(data)
    V
s.fecDecoder.decodeBytes(data)
    V
s.kcp.Input(data, 
true, s.ackNoDelay)
    V
kcp.parse_data(seg) {將分段好的數(shù)據(jù)插入kcp.rcv_buf緩沖}
    V
notifyReadEvent()

客戶端大體的流程如上面所示,先Dial,建立udp連接,將這個連接封裝成一個會話,然后啟動一個go程,接收udp的消息。

kcp server整體函數(shù)流

ListenWithOptions() 

    V
net.ListenUDP()
    V
ServerConn()
    V
newFECDecoder()
    V
go l.monitor() {從chPacket接收udp數(shù)據(jù),寫入kcp}
    V
go l.receiver(chPacket) {從upd接收數(shù)據(jù),并入隊列}
    V
newUDPSession()
    V
updater.addSession(sess) {管理session會話,任務管理,根據(jù)用戶設置的internal參數(shù)間隔來輪流喚醒任務}
    V
s.kcpInput(data)`
    V
s.fecDecoder.decodeBytes(data)
    V
s.kcp.Input(data, 
true, s.ackNoDelay)
    V
kcp.parse_data(seg) {將分段好的數(shù)據(jù)插入kcp.rcv_buf緩沖}
    V
notifyReadEvent()


服務端的大體流程如上圖所示,先Listen,啟動udp監(jiān)聽,接著用一個go程監(jiān)控udp的數(shù)據(jù)包,負責將不同session的數(shù)據(jù)寫入不同的udp連接,然后解析封裝將數(shù)據(jù)交給上層。

kcp 數(shù)據(jù)流詳細解析

不管是kcp的客戶端還是服務端,他們都有io行為,就是讀與寫,我們只分析一個就好了,因為它們讀寫的實現(xiàn)是一樣的,這里分析客戶端的讀與寫。
kcp client 發(fā)送消息

s.Write(b []byte
    V
s.kcp.WaitSnd() {}
    V
s.kcp.Send(b) {將數(shù)據(jù)根據(jù)mss分段,并存在kcp.snd_queue}
     V
s.kcp.flush(
false) [flush data to output] {
    
if writeDelay==true {
        flush
    }
else{
        每隔`interval`時間flush一次
    }
}
     V
kcp.output(buffer, size) 
     V
s.output(buf)
     V
s.conn.WriteTo(ext, s.remote)
     V
s.conn..Conn.WriteTo(buf)

讀寫都是在sess.go文件中實現(xiàn)的,Write方法:

// Write implements net.Conn
func (s *UDPSession) Write(b []byte) (n int, err error) {
    
for {
        

        
// api flow control
        if s.kcp.WaitSnd() < int(s.kcp.snd_wnd) {
            n 
= len(b)
            
for {
                
if len(b) <= int(s.kcp.mss) {
                    s.kcp.Send(b)
                    
break
                } 
else {
                    s.kcp.Send(b[:s.kcp.mss])
                    b 
= b[s.kcp.mss:]
                }
            }

            
if !s.writeDelay {
                s.kcp.flush(
false)
            }
            s.mu.Unlock()
            atomic.AddUint64(
&DefaultSnmp.BytesSent, uint64(n))
            
return n, nil
        }

        
        
// wait for write event or timeout
        select {
        
case <-s.chWriteEvent:
        
case <-c:
        
case <-s.die:
        }

        
if timeout != nil {
            timeout.Stop()
        }
    }
}

假設發(fā)送一個hello消息,Write方法會先判斷發(fā)送窗口是否已滿,滿的話該函數(shù)阻塞,不滿則kcp.Send(“hello”),而Send函數(shù)實現(xiàn)根據(jù)mss的值對數(shù)據(jù)分段,當然這里的發(fā)送的hello,長度太短,只分了一個段,并把它們插入發(fā)送的隊列里。

func (kcp *KCP) Send(buffer []byteint {
    
    
for i := 0; i < count; i++ {
        var size 
int
        
if len(buffer) > int(kcp.mss) {
            size 
= int(kcp.mss)
        } 
else {
            size 
= len(buffer)
        }
        seg :
= kcp.newSegment(size)
        copy(seg.data, buffer[:size])
        
if kcp.stream == 0 { // message mode
            seg.frg = uint8(count - i - 1)
        } 
else { // stream mode
            seg.frg = 0
        }
        kcp.snd_queue 
= append(kcp.snd_queue, seg)
        buffer 
= buffer[size:]
    }
    
return 0
}

接著判斷參數(shù)writeDelay,如果參數(shù)設置為false,則立馬發(fā)送消息,否則需要任務調(diào)度后才會觸發(fā)發(fā)送,發(fā)送消息是由flush函數(shù)實現(xiàn)的。

// flush pending data
func (kcp *KCP) flush(ackOnly bool) {
    var seg Segment
    seg.conv 
= kcp.conv
    seg.cmd 
= IKCP_CMD_ACK
    seg.wnd 
= kcp.wnd_unused()
    seg.una 
= kcp.rcv_nxt

    buffer :
= kcp.buffer
    
// flush acknowledges
    ptr := buffer
    
for i, ack := range kcp.acklist {
        size :
= len(buffer) - len(ptr)
        
if size+IKCP_OVERHEAD > int(kcp.mtu) {
            kcp.output(buffer, size)
            ptr 
= buffer
        }
        
// filter jitters caused by bufferbloat
        if ack.sn >= kcp.rcv_nxt || len(kcp.acklist)-1 == i {
            seg.sn, seg.ts 
= ack.sn, ack.ts
            ptr 
= seg.encode(ptr)

        }
    }
    kcp.acklist 
= kcp.acklist[0:0]

    
if ackOnly { // flash remain ack segments
        size := len(buffer) - len(ptr)
        
if size > 0 {
            kcp.output(buffer, size)
        }
        
return
    }

    
// probe window size (if remote window size equals zero)
    if kcp.rmt_wnd == 0 {
        current :
= currentMs()
        
if kcp.probe_wait == 0 {
            kcp.probe_wait 
= IKCP_PROBE_INIT
            kcp.ts_probe 
= current + kcp.probe_wait
        } 
else {
            
if _itimediff(current, kcp.ts_probe) >= 0 {
                
if kcp.probe_wait < IKCP_PROBE_INIT {
                    kcp.probe_wait 
= IKCP_PROBE_INIT
                }
                kcp.probe_wait 
+= kcp.probe_wait / 2
                
if kcp.probe_wait > IKCP_PROBE_LIMIT {
                    kcp.probe_wait 
= IKCP_PROBE_LIMIT
                }
                kcp.ts_probe 
= current + kcp.probe_wait
                kcp.probe 
|= IKCP_ASK_SEND
            }
        }
    } 
else {
        kcp.ts_probe 
= 0
        kcp.probe_wait 
= 0
    }

    
// flush window probing commands
    if (kcp.probe & IKCP_ASK_SEND) != 0 {
        seg.cmd 
= IKCP_CMD_WASK
        size :
= len(buffer) - len(ptr)
        
if size+IKCP_OVERHEAD > int(kcp.mtu) {
            kcp.output(buffer, size)
            ptr 
= buffer
        }
        ptr 
= seg.encode(ptr)
    }

    
// flush window probing commands
    if (kcp.probe & IKCP_ASK_TELL) != 0 {
        seg.cmd 
= IKCP_CMD_WINS
        size :
= len(buffer) - len(ptr)
        
if size+IKCP_OVERHEAD > int(kcp.mtu) {
            kcp.output(buffer, size)
            ptr 
= buffer
        }
        ptr 
= seg.encode(ptr)
    }

    kcp.probe 
= 0

    
// calculate window size
    cwnd := _imin_(kcp.snd_wnd, kcp.rmt_wnd)
    
if kcp.nocwnd == 0 {
        cwnd 
= _imin_(kcp.cwnd, cwnd)
    }

    
// sliding window, controlled by snd_nxt && sna_una+cwnd
    newSegsCount := 0
    
for k := range kcp.snd_queue {
        
if _itimediff(kcp.snd_nxt, kcp.snd_una+cwnd) >= 0 {
            
break
        }
        newseg :
= kcp.snd_queue[k]
        newseg.conv 
= kcp.conv
        newseg.cmd 
= IKCP_CMD_PUSH
        newseg.sn 
= kcp.snd_nxt
        kcp.snd_buf 
= append(kcp.snd_buf, newseg)
        kcp.snd_nxt
++
        newSegsCount
++
        kcp.snd_queue[k].data 
= nil
    }
    
if newSegsCount > 0 {
        kcp.snd_queue 
= kcp.remove_front(kcp.snd_queue, newSegsCount)
    }

    
// calculate resent
    resent := uint32(kcp.fastresend)
    
if kcp.fastresend <= 0 {
        resent 
= 0xffffffff
    }

    
// check for retransmissions
    current := currentMs()
    var change, lost, lostSegs, fastRetransSegs, earlyRetransSegs uint64
    
for k := range kcp.snd_buf {
        segment :
= &kcp.snd_buf[k]
        needsend :
= false
        
if segment.xmit == 0 { // initial transmit
            needsend = true
            segment.rto 
= kcp.rx_rto
            segment.resendts 
= current + segment.rto
        } 
else if _itimediff(current, segment.resendts) >= 0 { // RTO
            needsend = true
            
if kcp.nodelay == 0 {
                segment.rto 
+= kcp.rx_rto
            } 
else {
                segment.rto 
+= kcp.rx_rto / 2
            }
            segment.resendts 
= current + segment.rto
            lost
++
            lostSegs
++
        } 
else if segment.fastack >= resent { // fast retransmit
            needsend = true
            segment.fastack 
= 0
            segment.rto 
= kcp.rx_rto
            segment.resendts 
= current + segment.rto
            change
++
            fastRetransSegs
++
        } 
else if segment.fastack > 0 && newSegsCount == 0 { // early retransmit
            needsend = true
            segment.fastack 
= 0
            segment.rto 
= kcp.rx_rto
            segment.resendts 
= current + segment.rto
            change
++
            earlyRetransSegs
++
        }

        
if needsend {
            segment.xmit
++
            segment.ts 
= current
            segment.wnd 
= seg.wnd
            segment.una 
= seg.una

            size :
= len(buffer) - len(ptr)
            need :
= IKCP_OVERHEAD + len(segment.data)

            
if size+need > int(kcp.mtu) {
                kcp.output(buffer, size)
                current 
= currentMs() // time update for a blocking call
                ptr = buffer
            }

            ptr 
= segment.encode(ptr)
            copy(ptr, segment.data)
            ptr 
= ptr[len(segment.data):]

            
if segment.xmit >= kcp.dead_link {
                kcp.state 
= 0xFFFFFFFF
            }
        }
    }

    
// flash remain segments
    size := len(buffer) - len(ptr)
    
if size > 0 {
        kcp.output(buffer, size)
    }

    
// counter updates
    sum := lostSegs
    
if lostSegs > 0 {
        atomic.AddUint64(
&DefaultSnmp.LostSegs, lostSegs)
    }
    
if fastRetransSegs > 0 {
        atomic.AddUint64(
&DefaultSnmp.FastRetransSegs, fastRetransSegs)
        sum 
+= fastRetransSegs
    }
    
if earlyRetransSegs > 0 {
        atomic.AddUint64(
&DefaultSnmp.EarlyRetransSegs, earlyRetransSegs)
        sum 
+= earlyRetransSegs
    }
    
if sum > 0 {
        atomic.AddUint64(
&DefaultSnmp.RetransSegs, sum)
    }

    
// update ssthresh
    
// rate halving, https://tools.ietf.org/html/rfc6937
    if change > 0 {
        inflight :
= kcp.snd_nxt - kcp.snd_una
        kcp.ssthresh 
= inflight / 2
        
if kcp.ssthresh < IKCP_THRESH_MIN {
            kcp.ssthresh 
= IKCP_THRESH_MIN
        }
        kcp.cwnd 
= kcp.ssthresh + resent
        kcp.incr 
= kcp.cwnd * kcp.mss
    }

    
// congestion control, https://tools.ietf.org/html/rfc5681
    if lost > 0 {
        kcp.ssthresh 
= cwnd / 2
        
if kcp.ssthresh < IKCP_THRESH_MIN {
            kcp.ssthresh 
= IKCP_THRESH_MIN
        }
        kcp.cwnd 
= 1
        kcp.incr 
= kcp.mss
    }

    
if kcp.cwnd < 1 {
        kcp.cwnd 
= 1
        kcp.incr 
= kcp.mss
    }
}

flush函數(shù)非常的重要,kcp的重要參數(shù)都是在調(diào)節(jié)這個函數(shù)的行為,這個函數(shù)只有一個參數(shù)ackOnly,意思就是只發(fā)送ack,如果ackOnly為true的話,該函數(shù)只遍歷ack列表,然后發(fā)送,就完事了。 如果不是,也會發(fā)送真實數(shù)據(jù)。 在發(fā)送數(shù)據(jù)前先進行windSize探測,如果開啟了擁塞控制nc=0,則每次發(fā)送前檢測服務端的winsize,如果服務端的winsize變小了,自身的winsize也要更著變小,來避免擁塞。如果沒有開啟擁塞控制,就按設置的winsize進行數(shù)據(jù)發(fā)送。
接著循環(huán)每個段數(shù)據(jù),并判斷每個段數(shù)據(jù)的是否該重發(fā),還有什么時候重發(fā):
1. 如果這個段數(shù)據(jù)首次發(fā)送,則直接發(fā)送數(shù)據(jù)。 2. 如果這個段數(shù)據(jù)的當前時間大于它自身重發(fā)的時間,也就是RTO,則重傳消息。 3. 如果這個段數(shù)據(jù)的ack丟失累計超過resent次數(shù),則重傳,也就是快速重傳機制。這個resent參數(shù)由resend參數(shù)決定。 4. 如果這個段數(shù)據(jù)的ack有丟失且沒有新的數(shù)據(jù)段,則觸發(fā)ER,ER相關信息ER

最后通過kcp.output發(fā)送消息hello,output是個回調(diào)函數(shù),函數(shù)的實體是sess.go的:

func (s *UDPSession) output(buf []byte) {
    var ecc [][]
byte

    
// extend buf's header space
    ext := buf
    
if s.headerSize > 0 {
        ext 
= s.ext[:s.headerSize+len(buf)]
        copy(ext[s.headerSize:], buf)
    }

    
// FEC stage
    if s.fecEncoder != nil {
        ecc 
= s.fecEncoder.Encode(ext)
    }

    
// encryption stage
    if s.block != nil {
        io.ReadFull(rand.Reader, ext[:nonceSize])
        checksum :
= crc32.ChecksumIEEE(ext[cryptHeaderSize:])
        binary.LittleEndian.PutUint32(ext[nonceSize:], checksum)
        s.block.Encrypt(ext, ext)

        
if ecc != nil {
            
for k := range ecc {
                io.ReadFull(rand.Reader, ecc[k][:nonceSize])
                checksum :
= crc32.ChecksumIEEE(ecc[k][cryptHeaderSize:])
                binary.LittleEndian.PutUint32(ecc[k][nonceSize:], checksum)
                s.block.Encrypt(ecc[k], ecc[k])
            }
        }
    }

    
// WriteTo kernel
    nbytes := 0
    npkts :
= 0
    
// if mrand.Intn(100) < 50 {
    for i := 0; i < s.dup+1; i++ {
        
if n, err := s.conn.WriteTo(ext, s.remote); err == nil {
            nbytes 
+= n
            npkts
++
        }
    }
    
// }

    
if ecc != nil {
        
for k := range ecc {
            
if n, err := s.conn.WriteTo(ecc[k], s.remote); err == nil {
                nbytes 
+= n
                npkts
++
            }
        }
    }
    atomic.AddUint64(
&DefaultSnmp.OutPkts, uint64(npkts))
    atomic.AddUint64(
&DefaultSnmp.OutBytes, uint64(nbytes))
}

output函數(shù)才是真正的將數(shù)據(jù)寫入內(nèi)核中,在寫入之前先進行了fec編碼,fec編碼器的實現(xiàn)是用了一個開源庫github.com/klauspost/reedsolomon,編碼以后的hello就不是和原來的hello一樣了,至少多了幾個字節(jié)。 fec編碼器有兩個重要的參數(shù)reedsolomon.New(dataShards, parityShards, reedsolomon.WithMaxGoroutines(1)),dataShardsparityShards,這兩個參數(shù)決定了fec的冗余度,冗余度越大抗丟包性就越強。

kcp的任務調(diào)度器

其實這里任務調(diào)度器是一個很簡單的實現(xiàn),用一個全局變量updater來管理session,代碼文件為updater.go。其中最主要的函數(shù)

func (h *updateHeap) updateTask() {
    var timer 
<-chan time.Time
    
for {
        select {
        
case <-timer:
        
case <-h.chWakeUp:
        }

        h.mu.Lock()
        hlen :
= h.Len()
        now :
= time.Now()
        
if hlen > 0 && now.After(h.entries[0].ts) {
            
for i := 0; i < hlen; i++ {
                entry :
= heap.Pop(h).(entry)
                
if now.After(entry.ts) {
                    entry.ts 
= now.Add(entry.s.update())
                    heap.Push(h, entry)
                } 
else {
                    heap.Push(h, entry)
                    
break
                }
            }
        }
        
if hlen > 0 {
            timer 
= time.After(h.entries[0].ts.Sub(now))
        }
        h.mu.Unlock()
    }
}

任務調(diào)度器實現(xiàn)了一個堆結(jié)構(gòu),每當有新的連接,session都會插入到這個堆里,接著for循環(huán)每隔interval時間,遍歷這個堆,得到entry然后執(zhí)行entry.s.update()。而entry.s.update()會執(zhí)行s.kcp.flush(false)來發(fā)送數(shù)據(jù)。

總結(jié)

這里簡單介紹了kcp的整體流程,詳細介紹了發(fā)送數(shù)據(jù)的流程,但未介紹kcp接收數(shù)據(jù)的流程,其實在客戶端發(fā)送數(shù)據(jù)后,服務端是需要返回ack的,而客戶端也需要根據(jù)返回的ack來判斷數(shù)據(jù)段是否需要重傳還是在隊列里清除該數(shù)據(jù)段。處理返回來的ack是在函數(shù)kcp.Input()函數(shù)實現(xiàn)的。具體詳細流程下次再介紹。

posted on 2017-12-09 15:20 思月行云 閱讀(1195) 評論(0)  編輯 收藏 引用 所屬分類: Golang
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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成人免费观看| 亚洲精选大片| 亚洲视频观看| 久久在线91| 欧美色123| 伊甸园精品99久久久久久| 亚洲激情综合| 亚洲国内在线| 性欧美大战久久久久久久免费观看| 亚洲欧美中文日韩在线| 久久国产一区二区三区| 日韩亚洲欧美精品| 蜜臀av在线播放一区二区三区| 欧美日韩免费高清| 怡红院精品视频| 欧美国产精品va在线观看| 国产精品成人aaaaa网站| 黄色成人精品网站| 亚洲免费婷婷| 亚洲精品少妇网址| 狼人社综合社区| 国产一区二区高清| 亚洲欧美国产日韩中文字幕| 欧美成人69av| 欧美有码视频| 国产在线精品一区二区夜色| 午夜精品网站| 亚洲综合色婷婷| 国产日韩欧美| 久久视频在线视频| 久久精品国产亚洲aⅴ| 国产精品一级| 久久精品二区亚洲w码| 亚洲一区在线看| 国产午夜精品久久久久久久| 欧美中文在线免费| 久热成人在线视频| 亚洲精品在线观看视频| 999亚洲国产精| 国产欧美韩日| 欧美激情第三页| 欧美视频在线观看| 久久精品国产亚洲一区二区三区| 午夜精品久久久久| 亚洲国产精品美女| 亚洲视频在线一区观看| 狠狠色狠狠色综合| 亚洲乱码视频| 伊人成人在线| 亚洲午夜精品福利| 老司机成人网| 亚洲一级黄色av| 久久精品国语| 午夜宅男欧美| 国产精品第2页| 亚洲国产精品ⅴa在线观看| 欧美欧美天天天天操| 久久午夜视频| 国产亚洲人成网站在线观看| 亚洲激情成人| 亚洲国产欧美日韩另类综合| 久久精品国产77777蜜臀| 午夜亚洲性色视频| 久久影院午夜片一区| 久久久精品999| 国产一区二区精品久久99| 亚洲欧美电影院| 欧美专区日韩专区| 国产一区二区中文| 亚洲欧美激情在线视频| 午夜欧美电影在线观看| 国产精品高潮呻吟久久av无限 | 老色鬼久久亚洲一区二区| 欧美大片在线观看一区| 国产精品亚洲成人| 亚洲欧美日韩精品久久亚洲区| 亚洲欧美日韩精品久久| 国产日韩精品电影| 久久久欧美精品sm网站| 欧美视频一区在线| 久久精品国产在热久久| 亚洲国产99| 久久av资源网站| 在线观看一区二区精品视频| 蜜桃久久av| 欧美一乱一性一交一视频| 国产午夜亚洲精品理论片色戒| 麻豆精品在线播放| 在线亚洲欧美| 亚洲国产综合在线看不卡| 欧美在线播放| 中文网丁香综合网| 亚洲国产日韩欧美在线图片| 国产精品免费小视频| 欧美激情一区二区三区在线| 久久精品久久综合| 欧美亚洲三区| 99re这里只有精品6| 欧美国产日韩一区| 噜噜爱69成人精品| 久久精品夜夜夜夜久久| 欧美一级播放| 香蕉久久a毛片| 欧美一级视频一区二区| 亚洲在线黄色| 亚洲综合电影| 亚欧美中日韩视频| 欧美一区深夜视频| 欧美一级淫片aaaaaaa视频| 在线亚洲高清视频| 亚洲视频精选在线| 午夜在线一区| 老司机精品久久| 亚洲激情网址| 亚洲天堂网站在线观看视频| 亚洲天堂激情| 久久免费视频网| 欧美久久久久久久久久| 欧美性理论片在线观看片免费| 欧美性猛交一区二区三区精品| 国产精品私房写真福利视频| 国内偷自视频区视频综合| 亚洲欧洲精品一区二区三区波多野1战4 | 久久资源在线| 亚洲高清久久网| 亚洲欧美高清| 欧美v国产在线一区二区三区| 欧美三级视频在线观看| 国产最新精品精品你懂的| 日韩视频免费观看高清在线视频 | 欧美一区二区三区免费看| 久久综合色婷婷| 亚洲性视频h| 欧美日韩国产一级| 最新日韩在线| 蜜桃av噜噜一区| 久久精品国产2020观看福利| 欧美日韩激情小视频| 亚洲国产成人精品久久| 雨宫琴音一区二区在线| 欧美日韩国产一区二区| 欧美日韩中文在线| 亚洲人被黑人高潮完整版| 久久久久久久久久久久久9999| 99精品免费网| 欧美日韩一区成人| 在线亚洲免费| 亚洲免费在线视频一区 二区| 欧美日韩在线不卡一区| 亚洲天堂男人| 在线综合亚洲欧美在线视频| 国产精品美女诱惑| 久久色在线观看| 欧美成人在线免费观看| 日韩视频二区| 夜夜爽www精品| 国产视频不卡| 欧美成人免费va影院高清| 香蕉久久夜色精品国产使用方法| 国产日韩一区二区| 欧美激情在线有限公司| 欧美三级韩国三级日本三斤| 亚洲欧美日韩另类精品一区二区三区 | 在线不卡视频| 亚洲美女av电影| 国产主播精品| 亚洲视频在线视频| 亚洲激情不卡| 亚洲欧美综合| 中国女人久久久| 麻豆成人av| 久久午夜色播影院免费高清| 欧美日韩免费看| 欧美大胆人体视频| 国产一区视频在线看| 中文亚洲字幕| 一区二区三区久久| 欧美成人免费大片| 蜜臀av在线播放一区二区三区| 国产精品久久久久免费a∨大胸| 亚洲第一精品电影| 亚洲高清在线观看一区| 欧美在线观看一区| 久久免费高清| 极品裸体白嫩激情啪啪国产精品| 欧美一区二区在线观看| 久久精品午夜| 亚洲国产精品福利| 看欧美日韩国产| 亚洲免费电影在线| 亚洲一区二区免费视频| 国产精品青草综合久久久久99| 国产精品99久久不卡二区| 欧美一区二视频在线免费观看| 国产精品推荐精品| 久久青草久久|