<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

    本文由得物技術(shù)團(tuán)隊(duì)Uni分享,本文有內(nèi)容修訂和大量排版優(yōu)化。

    1、引言

    關(guān)于Java網(wǎng)絡(luò)編程中的同步IO和異步IO的區(qū)別及原理的文章非常的多,具體來(lái)說(shuō)主要還是在討論Java BIO和Java NIO這兩者,而關(guān)于Java AIO的文章就少之又少了(即使用也只是介紹了一下概念和代碼示例)。

    在深入了解AIO之前,我注意到以下幾個(gè)現(xiàn)象:

    • 1)2011年Java 7發(fā)布,它增加了AIO(號(hào)稱異步IO網(wǎng)絡(luò)編程模型),但12年過(guò)去了,平時(shí)使用的開(kāi)發(fā)框架和中間件卻還是以NIO為主(例如網(wǎng)絡(luò)框架Netty、Mina,Web容器Tomcat、Undertow),這是為什么?
    • 2)Java AIO又稱為NIO 2.0,難道它也是基于NIO來(lái)實(shí)現(xiàn)的?
    • 3)Netty為什么會(huì)舍去了AIO的支持?(點(diǎn)此查看);
    • 4)AIO看起來(lái)貌似只是解決了有無(wú),實(shí)際是發(fā)布了個(gè)寂寞?

    Java AIO的這些不合常理的現(xiàn)象難免會(huì)令人心存疑惑。所以決定寫這篇文章時(shí),我不想只是簡(jiǎn)單的把AIO的概念再?gòu)?fù)述一遍,而是要透過(guò)現(xiàn)象,深入分析、思考和并理解Java AIO的本質(zhì)。

     

    技術(shù)交流:

    - 移動(dòng)端IM開(kāi)發(fā)入門文章:《新手入門一篇就夠:從零開(kāi)發(fā)移動(dòng)端IM

    - 開(kāi)源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK備用地址點(diǎn)此

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

    2、我們所理解的異步

    AIO的A是Asynchronous(即異步)的意思,在了解AIO的原理之前,我們先理清一下“異步”到底是怎樣的一個(gè)概念。

    說(shuō)起異步編程,在平時(shí)的開(kāi)發(fā)還是比較常見(jiàn)的。

    例如以下的代碼示例:

    @Async

    publicvoidcreate() {

        //TODO

    }

     

    publicvoidbuild() {

        executor.execute(() -> build());

    }

    不管是用@Async注解,還是往線程池里提交任務(wù),他們最終都是同一個(gè)結(jié)果,就是把要執(zhí)行的任務(wù),交給另外一個(gè)線程來(lái)執(zhí)行。

    這個(gè)時(shí)候,我們可以大致的認(rèn)為,所謂的“異步”,就是用多線程的方式去并行執(zhí)行任務(wù)。

    3、Java BIO和NIO到底是同步還是異步?

    Java BIO和NIO到底是同步還是異步,我們先按照異步這個(gè)思路,做異步編程。

    3.1BIO代碼示例

    byte[] data = newbyte[1024];

    InputStream in = socket.getInputStream();

    in.read(data);

    // 接收到數(shù)據(jù),異步處理

    executor.execute(() -> handle(data));

     

    publicvoidhandle(byte[] data) {

        // TODO

    }

    如上:BIO在read()時(shí),雖然線程阻塞了,但在收到數(shù)據(jù)時(shí),可以異步啟動(dòng)一個(gè)線程去處理。

    3.2NIO代碼示例

    selector.select();

    Set<SelectionKey> keys = selector.selectedKeys();

    Iterator<SelectionKey> iterator = keys.iterator();

    while(iterator.hasNext()) {

        SelectionKey key = iterator.next();

        if(key.isReadable()) {

            SocketChannel channel = (SocketChannel) key.channel();

            ByteBuffer byteBuffer = (ByteBuffer) key.attachment();

            executor.execute(() -> {

                try{

                    channel.read(byteBuffer);

                    handle(byteBuffer);

                } catch(Exception e) {

     

                }

            });

        }

    }

     

    publicstaticvoidhandle(ByteBuffer buffer) {

        // TODO

    }

    同理:NIO雖然read()是非阻塞的,通過(guò)select()可以阻塞等待數(shù)據(jù),在有數(shù)據(jù)可讀的時(shí)候,異步啟動(dòng)一個(gè)線程,去讀取數(shù)據(jù)和處理數(shù)據(jù)。

    3.3產(chǎn)生的理解偏差

    此時(shí)我們信誓旦旦地說(shuō),Java的BIO和NIO是異步還是同步,取決你的心情,你高興給它個(gè)多線程,它就是異步的。

    果真如此么?

    在翻閱了大量博客文章之后,基本一致的闡明了——BIO和NIO是同步的。

    那問(wèn)題點(diǎn)出在哪呢,是什么造成了我們理解上的偏差呢?

    那就是參考系的問(wèn)題,以前學(xué)物理時(shí),公交車上的乘客是運(yùn)動(dòng)還是靜止,需要有參考系前提,如果以地面為參考,他是運(yùn)動(dòng)的,以公交車為參考,他是靜止的。

    Java IO也是一樣,需要有個(gè)參考系,才能定義它是同步還是異步。

    既然我們討論的是關(guān)于Java IO是哪一種模式,那就是要針對(duì)IO讀寫操作這件事來(lái)理解,而其他的啟動(dòng)另外一個(gè)線程去處理數(shù)據(jù),已經(jīng)是脫離IO讀寫的范圍了,不應(yīng)該把他們扯進(jìn)來(lái)。

    3.4嘗試定義異步

    所以以IO讀寫操作這事件作為參照,我們先嘗試的這樣定義,就是:發(fā)起IO讀寫的線程(調(diào)用read和write的線程),和實(shí)際操作IO讀寫的線程,如果是同一個(gè)線程,就稱之為同步,否則是異步。

    按上述定義:

    • 1)顯然BIO只能是同步,調(diào)用in.read()當(dāng)前線程阻塞,有數(shù)據(jù)返回的時(shí)候,接收到數(shù)據(jù)的還是原來(lái)的線程;
    • 2)而NIO也稱之為同步,原因也是如此,調(diào)用channel.read()時(shí),線程雖然不會(huì)阻塞,但讀到數(shù)據(jù)的還是當(dāng)前線程。

    按照這個(gè)思路,AIO應(yīng)該是發(fā)起IO讀寫的線程,和實(shí)際收到數(shù)據(jù)的線程,可能不是同一個(gè)線程。

    是不是這樣呢?我們將在上一節(jié)直接上Java AIO的代碼,我們從 實(shí)際代碼中一窺究竟吧。

    4、一個(gè)Java AIO的網(wǎng)絡(luò)編程示例

    4.1AIO服務(wù)端程序代碼

    publicclassAioServer {

     

        publicstaticvoidmain(String[] args) throwsIOException {

            System.out.println(Thread.currentThread().getName() + " AioServer start");

            AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open()

                    .bind(newInetSocketAddress("127.0.0.1", 8080));

            serverChannel.accept(null, newCompletionHandler<AsynchronousSocketChannel, Void>() {

     

                @Override

                publicvoidcompleted(AsynchronousSocketChannel clientChannel, Void attachment) {

                    System.out.println(Thread.currentThread().getName() + " client is connected");

                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    clientChannel.read(buffer, buffer, newClientHandler());

                }

     

                @Override

                publicvoidfailed(Throwable exc, Void attachment) {

                    System.out.println("accept fail");

                }

            });

            System.in.read();

        }

    }

     

    publicclassClientHandler implementsCompletionHandler<Integer, ByteBuffer> {

        @Override

        publicvoidcompleted(Integer result, ByteBuffer buffer) {

            buffer.flip();

            byte[] data = newbyte[buffer.remaining()];

            buffer.get(data);

            System.out.println(Thread.currentThread().getName() + " received:"+ newString(data, StandardCharsets.UTF_8));

        }

     

        @Override

        publicvoidfailed(Throwable exc, ByteBuffer buffer) {

     

        }

    }

    4.2AIO客戶端程序

    publicclassAioClient {

        publicstaticvoidmain(String[] args) throwsException {

            AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();

            channel.connect(newInetSocketAddress("127.0.0.1", 8080));

            ByteBuffer buffer = ByteBuffer.allocate(1024);

            buffer.put("Java AIO".getBytes(StandardCharsets.UTF_8));

            buffer.flip();

            Thread.sleep(1000L);

            channel.write(buffer);

     }

    }

    4.3異步的定義猜想結(jié)論

    分別運(yùn)行服務(wù)端和客戶端程序:

    在服務(wù)端運(yùn)行結(jié)果里:

    1)main線程發(fā)起serverChannel.accept的調(diào)用,添加了一個(gè)CompletionHandler監(jiān)聽(tīng)回調(diào),當(dāng)有客戶端連接過(guò)來(lái)時(shí),Thread-5線程執(zhí)行了accep的completed回調(diào)方法。

    2)緊接著Thread-5又發(fā)起了clientChannel.read調(diào)用,也添加了個(gè)CompletionHandler監(jiān)聽(tīng)回調(diào),當(dāng)收到數(shù)據(jù)時(shí),是Thread-1的執(zhí)行了read的completed回調(diào)方法。

    這個(gè)結(jié)論和上面異步猜想一致:發(fā)起IO操作(例如accept、read、write)調(diào)用的線程,和最終完成這個(gè)操作的線程不是同一個(gè),我們把這種IO模式稱之AIO。

    當(dāng)然了,這樣定義AIO只是為了方便我們理解,實(shí)際中對(duì)異步IO的定義可能更抽象一點(diǎn)。

    5、 AIO示例引發(fā)思考1:“執(zhí)行completed()方法的線程是誰(shuí)創(chuàng)建、什么時(shí)候創(chuàng)建?”

    一般,這樣的問(wèn)題,需要從程序的入口的開(kāi)始了解,但跟線程相關(guān),其實(shí)是可以從線程棧的運(yùn)行情況來(lái)定位線程是怎么運(yùn)行。

    只運(yùn)行AIO服務(wù)端程序,客戶端不運(yùn)行,打印一下線程棧(備注:程序在Linux平臺(tái)上運(yùn)行,其他平臺(tái)略有差異)。如下圖所示。

    分析線程棧,發(fā)現(xiàn),程序啟動(dòng)了那么幾個(gè)線程:

    • 1)線程Thread-0阻塞在EPoll.wait()方法上;
    • 2)線程Thread-1、Thread-2~Thread-n(n和CPU核心數(shù)量一致)從阻塞隊(duì)列里take()任務(wù),阻塞等待有任務(wù)返回。

    此時(shí)可以暫定下一個(gè)結(jié)論:AIO服務(wù)端程序啟動(dòng)之后,就開(kāi)始創(chuàng)建了這些線程,且線程都處于阻塞等待狀態(tài)。

    另外:發(fā)現(xiàn)這些線程的運(yùn)行都跟epoll有關(guān)系!

    提到epoll,我們印象中,Java NIO在Linux平臺(tái)底層就是用epoll來(lái)實(shí)現(xiàn)的,難道Java AIO也是用epoll來(lái)實(shí)現(xiàn)么?

    為了證實(shí)這個(gè)結(jié)論,我們從下一個(gè)問(wèn)題來(lái)展開(kāi)討論。

    6、 AIO示例引發(fā)思考2:AIO注冊(cè)事件監(jiān)聽(tīng)和執(zhí)行回調(diào)是如何實(shí)現(xiàn)的?

    帶著這個(gè)問(wèn)題,去閱讀JDK分析源碼時(shí),發(fā)現(xiàn)源碼特別的長(zhǎng),而源碼解析是一項(xiàng)枯燥乏味的過(guò)程,很容易把閱讀者給逼走勸退掉。

    對(duì)于長(zhǎng)流程和邏輯復(fù)雜的代碼的理解,我們可以抓住它幾個(gè)脈絡(luò),找出哪幾個(gè)核心流程。

    以注冊(cè)監(jiān)聽(tīng)read為例clientChannel.read(...),它主要的核心流程是:注冊(cè)事件 -> 監(jiān)聽(tīng)事件 -> 處理事件。

    注冊(cè)事件:

    注:注冊(cè)事件調(diào)用EPoll.ctl(...)函數(shù),這個(gè)函數(shù)在最后的參數(shù)用于指定是一次性的,還是永久性。上面代碼events | EPOLLONSHOT字面意思看來(lái),是一次性的。

    監(jiān)聽(tīng)事件:

    處理事件:

     

    核心流程總結(jié):

    在分析完上面的代碼流程后會(huì)發(fā)現(xiàn):每一次IO讀寫都要經(jīng)歷的這三個(gè)事件是一次性的,也就是在處理事件完,本次流程就結(jié)束了,如果想繼續(xù)下一次的IO讀寫,就得從頭開(kāi)始再來(lái)一遍。這樣就會(huì)存在所謂的死亡回調(diào)(回調(diào)方法里再添加下一個(gè)回調(diào)方法),這對(duì)于編程的復(fù)雜度大大提高了。

    7、 AIO示例引發(fā)思考3:監(jiān)聽(tīng)回調(diào)的本質(zhì)是什么?

    7.1概述

    先說(shuō)一下結(jié)論:所謂監(jiān)聽(tīng)回調(diào)的本質(zhì),就是用戶態(tài)線程調(diào)用內(nèi)核態(tài)的函數(shù)(準(zhǔn)確的說(shuō)是API,例如read、write、epollWait),該函數(shù)還沒(méi)有返回時(shí),用戶線程被阻塞了。當(dāng)函數(shù)返回時(shí),會(huì)喚醒阻塞的線程,執(zhí)行所謂回調(diào)函數(shù)。

    對(duì)于這個(gè)結(jié)論的理解,要先引入幾個(gè)概念。

    7.2系統(tǒng)調(diào)用與函數(shù)調(diào)用

    函數(shù)調(diào)用:找到某個(gè)函數(shù),并執(zhí)行函數(shù)里的相關(guān)命令。

    系統(tǒng)調(diào)用:操作系統(tǒng)對(duì)用戶應(yīng)用程序提供了編程接口,所謂API。

    系統(tǒng)調(diào)用執(zhí)行過(guò)程:

    • 1)傳遞系統(tǒng)調(diào)用參數(shù);
    • 2)執(zhí)行陷入指令,用用戶態(tài)切換到核心態(tài)(這是因?yàn)橄到y(tǒng)調(diào)用一般都需要再核心態(tài)下執(zhí)行);
    • 3)執(zhí)行系統(tǒng)調(diào)用程序;
    • 4)返回用戶態(tài)。

    7.3用戶態(tài)和內(nèi)核態(tài)之間的通信

    用戶態(tài)->內(nèi)核態(tài):通過(guò)系統(tǒng)調(diào)用方式即可。

    內(nèi)核態(tài)->用戶態(tài):內(nèi)核態(tài)根本不知道用戶態(tài)程序有什么函數(shù),參數(shù)是啥,地址在哪里。所以內(nèi)核是不可能去調(diào)用用戶態(tài)的函數(shù),只能通過(guò)發(fā)送信號(hào),比如kill 命令關(guān)閉程序就是通過(guò)發(fā)信號(hào)讓用戶程序優(yōu)雅退出的。

    既然內(nèi)核態(tài)是不可能主動(dòng)去調(diào)用用戶態(tài)的函數(shù),為什么還會(huì)有回調(diào)呢,只能說(shuō)這個(gè)所謂回調(diào)其實(shí)就是用戶態(tài)的自導(dǎo)自演。它既做了監(jiān)聽(tīng),又做了執(zhí)行回調(diào)函數(shù)。

    7.4用實(shí)際例子驗(yàn)證結(jié)論

    為了驗(yàn)證這個(gè)結(jié)論是否有說(shuō)服力,舉個(gè)例子:平時(shí)開(kāi)發(fā)寫代碼用的IntelliJ IDEA,它是如何監(jiān)聽(tīng)鼠標(biāo)、鍵盤事件和處理事件的。

    按照慣例,先打印一下線程棧,會(huì)發(fā)現(xiàn)鼠標(biāo)、鍵盤等事件的監(jiān)聽(tīng)是由“AWT-XAWT”線程負(fù)責(zé)的,處理事件則是“AWT-EventQueue”線程負(fù)責(zé)。如下圖所示。

    定位到具體的代碼上:可以看到“AWT-XAWT”正在做while循環(huán),調(diào)用waitForEvents函數(shù)等待事件返回。如果沒(méi)有事件,線程就一直阻塞在那邊。如下圖所示。

    8、Java AIO的本質(zhì)是什么?

    8.1Java AIO的本質(zhì),就是只在用戶態(tài)實(shí)現(xiàn)了異步

    由于內(nèi)核態(tài)無(wú)法直接調(diào)用用戶態(tài)函數(shù),Java AIO的本質(zhì),就是只在用戶態(tài)實(shí)現(xiàn)異步,并沒(méi)有達(dá)到理想意義上的異步。

    1)理想中的異步:

    何謂理想意義上的異步?這里舉個(gè)網(wǎng)購(gòu)的例子。

    兩個(gè)角色,消費(fèi)者A、快遞員B:

    • 1)A在網(wǎng)上購(gòu)物時(shí),填好家庭地址付款提交訂單,這個(gè)相當(dāng)于注冊(cè)監(jiān)聽(tīng)事件;
    • 2)商家發(fā)貨,B把東西送到A家門口,這個(gè)相當(dāng)于回調(diào)。

    A在網(wǎng)上下完單,后續(xù)的發(fā)貨流程就不用他來(lái)操心了,可以繼續(xù)做其他事。B送貨也不關(guān)心A在不在家,反正就把貨扔到家門口就行了,兩個(gè)人互不依賴,互不相干擾。

    假設(shè)A購(gòu)物是用戶態(tài)來(lái)做,B送快遞是內(nèi)核態(tài)來(lái)做,這種程序運(yùn)行方式過(guò)于理想了,實(shí)際中實(shí)現(xiàn)不了。

    2)現(xiàn)實(shí)中的異步:

    A住的是高檔小區(qū),不能隨意進(jìn)去,快遞只能送到小區(qū)門口。

    A買了一件比較重的商品,比如一臺(tái)電視,因?yàn)锳要上班不在家里,所以找了一個(gè)好友C幫忙把電視搬到他家。

    A出門上班前,跟門口的保安D打聲招呼,說(shuō)今天有一臺(tái)電視送過(guò)來(lái),送到小區(qū)門口時(shí),請(qǐng)電話聯(lián)系C,讓他過(guò)來(lái)拿。

    具體就是:

    • 1)此時(shí),A下單并跟D打招呼,相當(dāng)于注冊(cè)事件。在AIO中就是EPoll.ctl(...)注冊(cè)事件;
    • 2)保安在門口蹲著相當(dāng)于監(jiān)聽(tīng)事件,在AIO中就是Thread-0線程,做EPoll.wait(..);
    • 3)快遞員把電視送到門口,相當(dāng)于有IO事件到達(dá);
    • 4)保安通知C電視到了,C過(guò)來(lái)搬電視,相當(dāng)于處理事件(在AIO中就是Thread-0往任務(wù)隊(duì)列提交任務(wù),Thread-1 ~n去取數(shù)據(jù),并執(zhí)行回調(diào)方法)。

    整個(gè)過(guò)程中,保安D必須一直蹲著,寸步不能離開(kāi),否則電視送到門口,就被人偷了。

    好友C也必須在A家待著,受人委托,東西到了,人卻不在現(xiàn)場(chǎng),這有點(diǎn)失信于人。

    所以實(shí)際的異步和理想中的異步,在互不依賴,互不干擾,這兩點(diǎn)相違背了。保安的作用最大,這是他人生的高光時(shí)刻。

    異步過(guò)程中的注冊(cè)事件、監(jiān)聽(tīng)事件、處理事件,還有開(kāi)啟多線程,這些過(guò)程的發(fā)起者全是用戶態(tài)一手操辦。所以說(shuō)Java AIO本質(zhì)只是在用戶態(tài)實(shí)現(xiàn)了異步,這個(gè)和BIO、NIO先阻塞,阻塞喚醒后開(kāi)啟異步線程處理的本質(zhì)一致。

    8.2Java AIO的其它真相

    Java AIO跟NIO一樣:在各個(gè)平臺(tái)的底層實(shí)現(xiàn)方式也不同,在Linux是用epoll、Windows是IOCP、Mac OS是KQueue。原理是大同小異,都是需要一個(gè)用戶線程阻塞等待IO事件,一個(gè)線程池從隊(duì)列里處理事件。

    Netty之所以移除掉AIO:很大的原因是在性能上AIO并沒(méi)有比NIO高。Linux雖然也有一套原生的AIO實(shí)現(xiàn)(類似Windows上的IOCP),但Java AIO在Linux并沒(méi)有采用,而是用epoll來(lái)實(shí)現(xiàn)。

    Java AIO不支持UDP。

    AIO編程方式略顯復(fù)雜,比如“死亡回調(diào)”。

    9、參考資料

    [1] 少啰嗦!一分鐘帶你讀懂Java的NIO和經(jīng)典IO的區(qū)別

    [2] 史上最強(qiáng)Java NIO入門:擔(dān)心從入門到放棄的,請(qǐng)讀這篇!

    [3] Java的BIO和NIO很難懂?用代碼實(shí)踐給你看,再不懂我轉(zhuǎn)行!

    [4] Java新一代網(wǎng)絡(luò)編程模型AIO原理及Linux系統(tǒng)AIO介紹

    [5] 從0到1的快速裂變:詳解快的打車架構(gòu)設(shè)計(jì)及技術(shù)實(shí)踐

    [6] 新手入門:目前為止最透徹的的Netty高性能原理和框架架構(gòu)解析

    [7] 史上最通俗Netty框架入門長(zhǎng)文:基本介紹、環(huán)境搭建、動(dòng)手實(shí)戰(zhàn)

    [8] 高性能網(wǎng)絡(luò)編程(五):一文讀懂高性能網(wǎng)絡(luò)編程中的I/O模型

    [9] 高性能網(wǎng)絡(luò)編程(六):一文讀懂高性能網(wǎng)絡(luò)編程中的線程模型

    [10] 高性能網(wǎng)絡(luò)編程(七):到底什么是高并發(fā)?一文即懂!

    [11] 從根上理解高性能、高并發(fā)(二):深入操作系統(tǒng),理解I/O與零拷貝技術(shù)

    [12] 從根上理解高性能、高并發(fā)(三):深入操作系統(tǒng),徹底理解I/O多路復(fù)用

    [13] 從根上理解高性能、高并發(fā)(四):深入操作系統(tǒng),徹底理解同步與異步

    [14] 從根上理解高性能、高并發(fā)(五):深入操作系統(tǒng),理解高并發(fā)中的協(xié)程

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



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


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


    網(wǎng)站導(dǎo)航:
     
    Jack Jiang的 Mail: jb2011@163.com, 聯(lián)系QQ: 413980957, 微信: hellojackjiang
    主站蜘蛛池模板: 亚洲 小说区 图片区 都市| 亚洲毛片免费观看| 丰满少妇作爱视频免费观看| 亚洲熟妇AV一区二区三区宅男| 亚洲Av无码一区二区二三区| 亚洲精品第一国产综合野| 亚洲国产美女精品久久久久| 亚洲精品第一国产综合精品| 亚洲毛片基地日韩毛片基地| 亚洲国产理论片在线播放| 亚洲国产成人在线视频| 亚洲1234区乱码| 亚洲啪AV永久无码精品放毛片| 亚洲色偷偷色噜噜狠狠99网| 亚洲AV无码AV日韩AV网站| 美女被爆羞羞网站在免费观看| 无遮挡免费一区二区三区 | 久久国产成人精品国产成人亚洲| 亚洲精品WWW久久久久久| 亚洲一本大道无码av天堂| 亚洲小说区图片区另类春色| 久久亚洲精品成人777大小说| 亚洲色图校园春色| 亚洲综合校园春色| 色欲aⅴ亚洲情无码AV| 一级毛片a免费播放王色| 精品四虎免费观看国产高清午夜| 99久热只有精品视频免费看| 成年人性生活免费视频| 免费一级国产生活片| 亚洲日韩精品一区二区三区无码| 在线电影你懂的亚洲| 亚洲区日韩精品中文字幕| 国产精品永久免费| 18禁男女爽爽爽午夜网站免费| 久久精品无码一区二区三区免费| 免费在线观看a级毛片| 亚洲第一福利网站| 亚洲AV日韩AV无码污污网站| a级毛片在线免费看| 在线观看免费人成视频|