<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Jack Jiang

    我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
    posts - 494, comments - 13, trackbacks - 0, articles - 1

    1、引言

    我相信大家剛開始學(xué)網(wǎng)絡(luò)編程中socket的時候,都跟我一樣對書上所講的socket概念云里霧里的、似懂非懂,很是困擾。

    這篇文章我打算從初學(xué)者的角度,用通俗易懂的文字,跟大家分享下我所理解的socket是什么,并由淺入深從操作系統(tǒng)內(nèi)核實現(xiàn)來透視socket的原理。

    * 推薦閱讀:跟本篇類似,《到底什么是Socket?一文即懂!》一文也非常適合初學(xué)者。另一篇《我們在讀寫Socket時,究竟在讀寫什么?》,相信可進(jìn)一步為你解惑。

    學(xué)習(xí)交流:

    (本文已同步發(fā)布于:http://www.52im.net/thread-4146-1-1.html

    2、系列文章

    本文是系列文章中的第 15 篇,本系列文章的大綱如下:

    3、初識socket

    故事要從一個插頭說起。

    ▲ 插頭與插座

    當(dāng)我將插頭插入插座,那看起來就像是將兩者連起來了。

    ▲風(fēng)扇與電力系統(tǒng)建立"連接"

    而插座的英文,又叫socket。巧了,我們程序員搞網(wǎng)絡(luò)編程時也會用到一個叫socket的東西。

    其實兩者非常相似。通過socket,我們可以與某臺機(jī)子建立"連接",建立"連接"的過程,就像是將插口插入插槽一樣。

    大概概念是了解了,但我相信各位對socket其實還是很模糊。接下來我們從大家最熟悉的使用場景開始說起。

    4、socket的典型使用場景

    我們想要將數(shù)據(jù)從A電腦的某個進(jìn)程發(fā)到B電腦的某個進(jìn)程。

    這時候我們需要選擇將數(shù)據(jù)發(fā)過去的方式,如果需要確保數(shù)據(jù)要能發(fā)給對方,那就選可靠的TCP協(xié)議(見《快速理解TCP協(xié)議一篇就夠》),如果數(shù)據(jù)丟了也沒關(guān)系,看天意,那就選擇不可靠的UDP協(xié)議(見《一泡尿的時間,快速搞懂TCP和UDP的區(qū)別》)。

    初學(xué)者毫無疑問,首選TCP。(見《快速理解TCP和UDP的差異》)

    ▲TCP是什么

    那這時候就需要用socket進(jìn)行編程。

    于是第一步就是創(chuàng)建個關(guān)于TCP的socket,就像下面這樣:

    1sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    上面這個方法會返回socket_fd,它是socket文件的句柄,是個數(shù)字,相當(dāng)于socket的身份證號。

    得到了socket_fd之后,對于服務(wù)端,就可以依次執(zhí)行bind(), listen(), accept()方法,然后坐等客戶端的連接請求。

    對于客戶端,得到socket_fd之后,你就可以執(zhí)行connect()方法向服務(wù)端發(fā)起建立連接的請求,此時就會發(fā)生TCP三次握手(如下圖所示)。

    ▲握手建立連接流程

    連接建立完成后,客戶端可以執(zhí)行send() 方法發(fā)送消息,服務(wù)端可以執(zhí)行recv()方法接收消息,反過來,服務(wù)器也可以執(zhí)行send(),客戶端執(zhí)行recv()方法。

    到這里為止,就是我們大部分程序員最熟悉的使用場景。

    PS:限于篇幅,本篇不展開TCP協(xié)議的3次握手原理,有興趣可以詳讀:《理論經(jīng)典:TCP協(xié)議的3次握手與4次揮手過程詳解》、《跟著動畫來學(xué)TCP三次握手和四次揮手》。

    5、socket該怎么設(shè)計?

    5.1基本認(rèn)知

    現(xiàn)在,socket我們見過,也用過,但對大部分程序員來說,它是個黑盒。

    那既然是黑盒,我們索性假設(shè)我們忘了socket。重新設(shè)計一個內(nèi)核網(wǎng)絡(luò)傳輸功能。

    網(wǎng)絡(luò)傳輸,從操作上來看,無非就是發(fā)數(shù)據(jù)和遠(yuǎn)端之間互相收發(fā)數(shù)據(jù)(也就是對應(yīng)著寫數(shù)據(jù)和讀數(shù)據(jù))。

    ▲ 讀寫收發(fā)

    但顯然,事情沒那么簡單。

    這里有兩個問題:

    • 1)接收端和發(fā)送端可能不止一個,因此我們需要一些信息做下區(qū)分,這個大家肯定很熟悉,可以用IP和端口。IP用來定位是哪臺電腦,端口用來定位是這臺電腦上的哪個進(jìn)程;
    • 2)發(fā)送端和接收端的傳輸方式有很多區(qū)別,可以是可靠的TCP協(xié)議,也可以是不可靠的UDP協(xié)議,甚至還需要支持基于icmp協(xié)議的ping命令。

    5.2sock的基本定義

    寫過代碼的都知道,為了支持這些功能,我們需要定義一個數(shù)據(jù)結(jié)構(gòu)去支持這些功能。這個數(shù)據(jù)結(jié)構(gòu),叫sock。

    為了解決上面的第一個問題,我們可以在sock里加入IP和端口字段:

    ▲ sock加入IP和端口字段

    而第二個問題:我們會發(fā)現(xiàn)這些協(xié)議雖然各不相同,但還是有一些功能相似的地方,比如收發(fā)數(shù)據(jù)時的一些邏輯完全可以復(fù)用。按面向?qū)ο缶幊痰乃枷耄覀兛梢詫⒉煌膮f(xié)議當(dāng)成是不同的對象類(或結(jié)構(gòu)體),將公共的部分提取出來,通過"繼承"的方式,復(fù)用功能。

    5.3基于各種sock實現(xiàn)網(wǎng)絡(luò)傳輸功能

    于是,我們將功能重新劃分下,定義了一些數(shù)據(jù)結(jié)構(gòu):

    ▲ 繼承sock的各類sock

    sock是最基礎(chǔ)的結(jié)構(gòu),維護(hù)一些任何協(xié)議都有可能會用到的收發(fā)數(shù)據(jù)緩沖區(qū)。

    inet_sock特指用了網(wǎng)絡(luò)傳輸功能的sock,在sock的基礎(chǔ)上還加入了TTL,端口,IP地址這些跟網(wǎng)絡(luò)傳輸相關(guān)的字段信息。說到這里大家就懵了,難道還有不是用網(wǎng)絡(luò)傳輸?shù)模坑校热鏤nix domain socket,用于本機(jī)進(jìn)程之間的通信,直接讀寫文件,不需要經(jīng)過網(wǎng)絡(luò)協(xié)議棧。這是個非常有用的東西,我以后一定講講(畫餅)。

    inet_connection_sock 是指面向連接的sock,在inet_sock的基礎(chǔ)上加入面向連接的協(xié)議里相關(guān)字段,比如accept隊列,數(shù)據(jù)包分片大小,握手失敗重試次數(shù)等。雖然我們現(xiàn)在提到面向連接的協(xié)議就是指TCP,但設(shè)計上linux需要支持?jǐn)U展其他面向連接的新協(xié)議,

    tcp_sock 就是正兒八經(jīng)的tcp協(xié)議專用的sock結(jié)構(gòu)了,在inet_connection_sock基礎(chǔ)上還加入了tcp特有的滑動窗口、擁塞避免等功能。同樣udp協(xié)議也會有一個專用的數(shù)據(jù)結(jié)構(gòu),叫udp_sock。

    好了,現(xiàn)在有了這套數(shù)據(jù)結(jié)構(gòu),我們將它們跟硬件網(wǎng)卡對接一下,就實現(xiàn)了網(wǎng)絡(luò)傳輸?shù)墓δ堋?/p>

    5.4提供socket層

    可以想象得到,這里面的代碼肯定非常復(fù)雜,同時還操作了網(wǎng)卡硬件,需要比較高的操作系統(tǒng)權(quán)限,再考慮到性能和安全,于是決定將它放在操作系統(tǒng)內(nèi)核里。

    既然網(wǎng)絡(luò)傳輸功能做在內(nèi)核里,那用戶空間的應(yīng)用程序想要用這部分功能的話,該怎么辦呢?

    這個好辦,本著不重復(fù)造輪子的原則,我們將這部分功能抽象成一個個簡單的接口。以后別人只需要調(diào)用這些接口,就可以驅(qū)動我們寫好的這一大堆復(fù)雜的數(shù)據(jù)結(jié)構(gòu)去發(fā)送數(shù)據(jù)。

    那么問題來了,怎么樣將這部分功能暴露出去呢?讓其他程序員更方便的使用呢?

    既然跟遠(yuǎn)端服務(wù)端進(jìn)程收發(fā)數(shù)據(jù)可以抽象為“讀和寫”,操作文件也可以抽象為"讀和寫",正好有句話叫,"linux里一切皆是文件",那我們索性,將內(nèi)核的sock封裝成文件就好了。創(chuàng)建sock的同時也創(chuàng)建一個文件,文件有個句柄fd,說白了就是個文件系統(tǒng)里的身份證號碼,通過它可以唯一確定是哪個sock。

    這個文件句柄fd其實就是 sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 里的sock_fd。

    將句柄暴露給用戶,之后用戶就可以像操作文件句柄那樣去操作這個sock句柄。在用戶空間里操作這個句柄,文件系統(tǒng)就會將操作指向內(nèi)核sock結(jié)構(gòu)。

    是的,操作這個特殊的文件就相當(dāng)于操作內(nèi)核里對應(yīng)的sock:

    ▲ 通過文件找到sock

    有了sock_fd句柄之后,我們就需要提供一些接口方法,讓用戶更方便的實現(xiàn)特定的網(wǎng)絡(luò)編程功能。這些接口,我們列了一下,發(fā)現(xiàn)需要有send(),recv(),bind(), listen(),connect()這些。

    到這里,我們的內(nèi)核網(wǎng)絡(luò)傳輸功能就算設(shè)計完成了。

    現(xiàn)在是不是眼熟了,上面這些接口方法其實就是socket提供出來的接口。

    所以說:socket其實就是個代碼庫 or 接口層,它介于內(nèi)核和應(yīng)用程序之間,提供了一些高度封裝過的接口,讓我們?nèi)ナ褂脙?nèi)核網(wǎng)絡(luò)傳輸功能。

    ▲ 基于sock實現(xiàn)網(wǎng)絡(luò)傳輸功能

    到這里,我們應(yīng)該明白了。我們平時寫的應(yīng)用程序里代碼里雖然用了socket實現(xiàn)了收發(fā)數(shù)據(jù)包的功能,但其實真正執(zhí)行網(wǎng)絡(luò)通信功能的,不是應(yīng)用程序,而是linux內(nèi)核。相當(dāng)于應(yīng)用程序通過socket提供的接口,將網(wǎng)絡(luò)傳輸?shù)倪@部分工作外包給了linux內(nèi)核。

    這聽起來像不像我們最熟悉的前后端分離的服務(wù)架構(gòu),雖然這么說不太嚴(yán)謹(jǐn),但看上去linux就像是被分成了應(yīng)用程序和內(nèi)核兩個服務(wù)。內(nèi)核就像是后端,暴露了好多個api接口,其中一類就是socket的send()和recv()這些方法。應(yīng)用程序就像是前端,負(fù)責(zé)調(diào)用內(nèi)核提供的接口來實現(xiàn)想要的功能。

    ▲ 進(jìn)程通過socket調(diào)用內(nèi)核功能

    看到這里,我擔(dān)心大家會有點混亂,下面來做個小的總結(jié)。

    5.5小結(jié)一下

    在操作系統(tǒng)內(nèi)核空間里,實現(xiàn)網(wǎng)絡(luò)傳輸功能的結(jié)構(gòu)是sock,基于不同的協(xié)議和應(yīng)用場景,會被泛化為各種類型的xx_sock,它們結(jié)合硬件,共同實現(xiàn)了網(wǎng)絡(luò)傳輸功能。

    為了將這部分功能暴露給用戶空間的應(yīng)用程序使用,于是引入了socket層,同時將sock嵌入到文件系統(tǒng)的框架里,sock就變成了一個特殊的文件,用戶就可以在用戶空間使用文件句柄,也就是socket_fd來操作內(nèi)核sock的網(wǎng)絡(luò)傳輸能力。

    這個socket_fd是一個int類型的數(shù)字。

    現(xiàn)在回去看socket的中文翻譯,套接字,我將它理解為一套用于連接的數(shù)字,是不是就覺得特別合理了。

    ▲網(wǎng)絡(luò)分層與基于sock實現(xiàn)網(wǎng)絡(luò)傳輸功能

    6、socket如何實現(xiàn)網(wǎng)絡(luò)通信

    6.1概述

    上面關(guān)于怎么實現(xiàn)網(wǎng)絡(luò)通信功能這一塊一筆帶過了。本節(jié)我們就來詳細(xì)聊聊。

    這套sock的結(jié)構(gòu)其實非常復(fù)雜。我們以最常用的TCP協(xié)議為例,簡單了解下它是怎么實現(xiàn)網(wǎng)絡(luò)傳輸功能的。

    我將它分為兩階段,分別是建立連接數(shù)據(jù)傳輸

    6.2建立連接

    對于TCP,要傳數(shù)據(jù),就得先在客戶端和服務(wù)端中間建立連接

    在客戶端,代碼執(zhí)行socket提供的connect(sockfd, "ip:port")方法時,會通過sockfd句柄找到對應(yīng)的文件,再根據(jù)文件里的信息指向內(nèi)核的sock結(jié)構(gòu)。

    通過這個sock結(jié)構(gòu)主動發(fā)起三次握手:

    ▲ TCP三次握手

    在服務(wù)端握手次數(shù)還沒達(dá)到"三次"的連接,叫半連接,完成好三次握手的連接,叫全連接。它們分別會用半連接隊列和全連接隊列來存放,這兩個隊列會在你執(zhí)行l(wèi)isten()方法的時候創(chuàng)建好。

    當(dāng)服務(wù)端執(zhí)行accept()方法時,就會從全連接隊列里拿出一條全連接:

    ▲半連接隊列和全連接隊列

    至此,連接就算準(zhǔn)備好了,之后,就可以開始傳輸數(shù)據(jù)。

    6.3數(shù)據(jù)傳輸

    為了實現(xiàn)發(fā)送和接收數(shù)據(jù)的功能,sock結(jié)構(gòu)體里帶了一個發(fā)送緩沖區(qū)和一個接收緩沖區(qū),說是緩沖區(qū),但其實就是個鏈表,上面掛著一個個準(zhǔn)備要發(fā)送或接收的數(shù)據(jù)。

    當(dāng)應(yīng)用執(zhí)行send()方法發(fā)送數(shù)據(jù)時,同樣也會通過sock_fd句柄找到對應(yīng)的文件,根據(jù)文件指向的sock結(jié)構(gòu),找到這個sock結(jié)構(gòu)里帶的發(fā)送緩沖區(qū),將數(shù)據(jù)會放到發(fā)送緩沖區(qū),然后結(jié)束流程,內(nèi)核看心情決定什么時候?qū)⑦@份數(shù)據(jù)發(fā)送出去。

    接收數(shù)據(jù)流程也類似,當(dāng)數(shù)據(jù)送到linux內(nèi)核后,數(shù)據(jù)不是立馬給到應(yīng)用程序的,而是先放在接收緩沖區(qū)中,數(shù)據(jù)靜靜躺著,卑微的等待應(yīng)用程序什么時候執(zhí)行recv()方法來拿一下。就像我的文章,躺在你的推文列表里,卑微的等一個點贊關(guān)注轉(zhuǎn)發(fā)三連。懂?

    ▲ sock的發(fā)送和接收緩沖區(qū)

    PS:IP和端口其實不在sock下,而在inet_sock下,上面這么畫只是為了簡化。

    那么問題來了,發(fā)送數(shù)據(jù)是應(yīng)用程序主動發(fā)起,這個大家都沒問題。那接收數(shù)據(jù)呢?數(shù)據(jù)從遠(yuǎn)端發(fā)過來了,怎么通知并給到應(yīng)用程序呢?

    這就需要用到等待隊列:

    ▲ sock內(nèi)的等待隊列

    當(dāng)你的應(yīng)用進(jìn)程執(zhí)行recv()方法嘗試獲取(阻塞場景下)接收緩沖區(qū)的數(shù)據(jù)時:

    • 1) 如果有數(shù)據(jù),那正好,取走就好了。這點沒啥疑問;
    • 2) 但如果沒數(shù)據(jù),就會將自己的進(jìn)程信息注冊到這個sock用的等待隊列里,然后進(jìn)程休眠。如果這時候有數(shù)據(jù)從遠(yuǎn)端發(fā)過來了,數(shù)據(jù)進(jìn)入到接收緩沖區(qū)時,內(nèi)核就會取出sock的等待隊列里的進(jìn)程,喚醒進(jìn)程來取據(jù)。

    ▲ recv時無數(shù)據(jù)進(jìn)程進(jìn)入等待隊列

    有時候,你會看到多個進(jìn)程通過fork的方式,listen了同一個socket_fd。在內(nèi)核,它們都是同一個sock,多個進(jìn)程執(zhí)行l(wèi)isten()之后,都嗷嗷等待連接進(jìn)來,所以都會將自身的進(jìn)程信息注冊到這個socket_fd對應(yīng)的內(nèi)核sock的等待隊列中。

    如果這時真來了一個連接,是該喚醒等待隊列里的哪個進(jìn)程來接收連接呢?

    這個問題的答案比較有趣:

    • 1) 在linux 2.6以前,會喚醒等待隊列里的所有進(jìn)程。但最后其實只有一個進(jìn)程會處理這個連接請求,其他進(jìn)程又重新進(jìn)入休眠,這些被喚醒了又無事可做最后只能重新回去休眠的進(jìn)程會消耗一定的資源。就好像你在廣東的街頭,想問路,叫一聲靚仔,幾十個人同時回頭,但你其實只需要其中一個靚仔告訴你路該怎么走。你這種一不小心驚動這群靚仔的場景,在計算機(jī)領(lǐng)域中,就叫驚群效應(yīng);
    • 2) 在linux 2.6之后,只會喚醒等待隊列里的其中一個進(jìn)程。是的,socket監(jiān)聽的驚群效應(yīng)問題被修復(fù)了。

    ▲ 驚群效應(yīng)

    看到這里,問題又來了。

    服務(wù)端 listen 的時候,那么多數(shù)據(jù)到一個 socket 怎么區(qū)分多個客戶端的?

    以TCP為例,服務(wù)端執(zhí)行l(wèi)isten方法后,會等待客戶端發(fā)送數(shù)據(jù)來。客戶端發(fā)來的數(shù)據(jù)包上會有源IP地址和端口,以及目的IP地址和端口,這四個元素構(gòu)成一個四元組,可以用于唯一標(biāo)記一個客戶端。

    PS:其實說四元組并不嚴(yán)謹(jǐn),因為過程中還有很多其他信息,也可以說是五元組。。。但大概理解就好,就這樣吧。

    ▲ 四元組

    服務(wù)端會創(chuàng)建一個新的內(nèi)核sock,并用四元組生成一個hash key,將它放入到一個hash表中。

    ▲ 四元組映射成hash鍵

    下次再有消息進(jìn)來的時候,通過消息自帶的四元組生成hash key再到這個hash表里重新取出對應(yīng)的sock就好了。所以說服務(wù)端是通過四元組來區(qū)分多個客戶端的。

    ▲ 多個hash_key對應(yīng)多個客戶端

    7、sock怎么實現(xiàn)"繼承"?

    大家都知道linux內(nèi)核是C語言實現(xiàn)的,而C語言沒有類也沒有繼承的特性,是怎么做到"繼承"的效果的呢?

    在C語言里,結(jié)構(gòu)體里的內(nèi)存是連續(xù)的,將要繼承的"父類",放到結(jié)構(gòu)體的第一位。

    就像下面這樣:

    structtcp_sock {

        /* inet_connection_sock has to be the first member of tcp_sock */

        structinet_connection_sock inet_conn;

            // 其他字段

    }

     

    structinet_connection_sock {

        /* inet_sock has to be the first member! */

        structinet_sock   icsk_inet;

            // 其他字段

    }

    然后我們就可以通過結(jié)構(gòu)體名的長度來強(qiáng)行截取內(nèi)存,這樣就能轉(zhuǎn)換結(jié)構(gòu)體,從而實現(xiàn)類似"繼承"的效果。

    如下代碼所示:

    // sock 轉(zhuǎn)為 tcp_sock

    staticinlinestructtcp_sock *tcp_sk(conststructsock *sk)

    {

        return(structtcp_sock *)sk;

    }

    ▲ 內(nèi)存布局

    8、本文小結(jié)

    寫到這里,文章就算是結(jié)束了,我們來總結(jié)一下。

    1)socket中文套接字,我理解為一套用于連接的數(shù)字。并不一定準(zhǔn)確,歡迎評論。

    2)sock在內(nèi)核,socket_fd在用戶空間,socket層介于內(nèi)核和用戶空間之間。

    3)在操作系統(tǒng)內(nèi)核空間里,實現(xiàn)網(wǎng)絡(luò)傳輸功能的結(jié)構(gòu)是sock,基于不同的協(xié)議和應(yīng)用場景,會被泛化為各種類型的xx_sock,它們結(jié)合硬件,共同實現(xiàn)了網(wǎng)絡(luò)傳輸功能。為了將這部分功能暴露給用戶空間的應(yīng)用程序使用,于是引入了socket層,同時將sock嵌入到文件系統(tǒng)的框架里,sock就變成了一個特殊的文件,用戶就可以在用戶空間使用文件句柄,也就是socket_fd來操作內(nèi)核sock的網(wǎng)絡(luò)傳輸能力。

    4)服務(wù)端可以通過四元組來區(qū)分多個客戶端。

    5)內(nèi)核通過c語言"結(jié)構(gòu)體里的內(nèi)存是連續(xù)的"這一特點實現(xiàn)了類似繼承的效果。

    推薦閱讀:跟本篇類似,《到底什么是Socket?一文即懂!》一文也非常適合初學(xué)者。另一篇《我們在讀寫Socket時,究竟在讀寫什么?》,相信可進(jìn)一步為你解惑。

    9、參考資料

    [1] 到底什么是Socket?一文即懂!

    [2] 我們在讀寫Socket時,究竟在讀寫什么?

    [3] 通俗易懂-深入理解TCP協(xié)議(上):理論基礎(chǔ)

    [4] 快速理解TCP協(xié)議一篇就夠

    [5] 假如你來設(shè)計TCP協(xié)議,會怎么做?

    [6] 一泡尿的時間,快速搞懂TCP和UDP的區(qū)別

    [7] 快速理解TCP和UDP的差異

    [8] 理論經(jīng)典:TCP協(xié)議的3次握手與4次揮手過程詳解

    [9] 跟著動畫來學(xué)TCP三次握手和四次揮手

    [10] 手把手教你寫基于TCP的Socket長連接

    [11] 為什么QQ用的是UDP協(xié)議而不是TCP協(xié)議?

    [12] 移動端即時通訊協(xié)議選擇:UDP還是TCP?

    (本文已同步發(fā)布于:http://www.52im.net/thread-4146-1-1.html



    作者:Jack Jiang (點擊作者姓名進(jìn)入Github)
    出處:http://www.52im.net/space-uid-1.html
    交流:歡迎加入即時通訊開發(fā)交流群 215891622
    討論:http://www.52im.net/
    Jack Jiang同時是【原創(chuàng)Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
    本博文 歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處(也可前往 我的52im.net 找到我)。


    只有注冊用戶登錄后才能發(fā)表評論。


    網(wǎng)站導(dǎo)航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 亚洲精品国产第1页| 在线精品一卡乱码免费| 亚洲色大成WWW亚洲女子| 亚洲VA成无码人在线观看天堂| 国产美女无遮挡免费视频 | 日本一区二区三区日本免费| 亚洲综合婷婷久久| 亚洲一区二区三区乱码A| 性无码免费一区二区三区在线| 高潮毛片无遮挡高清免费视频| 亚洲av日韩av激情亚洲| 毛片视频免费观看| 九九九国产精品成人免费视频| 亚洲男同gay片| 国产亚洲人成网站在线观看| 免费v片在线观看品善网| 嫩草视频在线免费观看| fc2免费人成为视频| 亚洲成年人免费网站| 亚洲国产精品成人| 1000部啪啪未满十八勿入免费| 成人久久免费网站| 97在线免费视频| 99re6在线精品免费观看| 色多多A级毛片免费看| 亚洲中文久久精品无码1| 亚洲天堂在线视频| 亚洲国产精品狼友中文久久久| 四虎影视永久免费观看地址| 国产大片91精品免费观看男同| 国产成人一区二区三区免费视频| 日韩视频免费一区二区三区| 午夜一级毛片免费视频| 91成人免费观看| 亚洲免费人成视频观看| 97性无码区免费| 成人毛片18女人毛片免费96| 麻豆国产VA免费精品高清在线| 国产大片91精品免费看3| 亚洲精品人成无码中文毛片| 久久精品夜色噜噜亚洲A∨|