亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

MIDP終端模擬之二:高級終端模擬

2019-11-18 16:11:13
字體:
來源:轉載
供稿:網友

下載 項目源代碼

在本系列的第一篇文章中,我們構建了一個簡單的可在任何支持 TCP/ip 插槽的 MIDP 設備上運行的終端模擬器。它包含一個實現 Telnet 協議的 Connection 和一個經過定制來顯示終端內容的 Canvas。在第二篇文章中,我假定您已經閱讀了第一篇文章并熟悉了這些組件。

MIDP終端模擬之二:高級終端模擬(圖一)現在我們將進一步加強這個應用程序。首先,我們將通過添加一個更高級的終端類型來增加一些復雜性。然后我們將添加對用戶輸入的支持——注意大多數移動設備的各種限制。完成后,我們就能夠使用這個應用程序來通過 Telnet 連接到遠程服務器并運行許多種類的程序,而這一切都是從您的 MIDP 設備來進行的。

關于 ANSI 終端

在上一篇文章中我們實現了一個“啞巴”終端。它顯示的僅僅是一個字符流,當進入的字符到達屏幕的邊緣時就換行到下一行,在遇到一個換行符時就跳到下一行。雖然這種程度的交互性對于整代的命令行應用程序是足夠的,但較復雜的軟件則將屏幕作為一個整體來處理,這就需要編寫和清除特定位置的字符以提供更好的用戶體驗。

這里有一段有趣的歷史。在 20 世紀 70 年代,眾多生產視頻終端的廠商提供的屏幕操縱功能專有且各不相同,并以不兼容的方式來實現。要編寫可利用多種這樣的設備的面向屏幕的軟件就很困難。

American National Standards Institute (ANSI) 參與進來。堅持其通過可互操作性來支持商業的要求,ANSI  介入并發布了標準 X3.64:Additional Controls for Use with American National Standard Code for Information Interchange。這個文件定義了現在所知的 ANSI 終端類型。它對將光標移動到屏幕上的特定位置、在光標位置插入和刪除字符都規定了標準的命令序列。

最為重要的是命令序列定義自身,因為即使是未實現所有命令的終端也至少能夠將不支持的命令識別為命令,并安全地忽視它們。這一進步使得軟件開發商可以按照通用標準來編寫,并確信他們的應用程序能夠至少占用較小的容量來在很多廠商的設備上運行。ANSI 的故事是軟件行業一個重復出現的主題的一個很好的例子:采用一種標準會極大地擴展可互操作性。

ANSI 終端標準是一種很像 Telnet 協議的協議。它定義了多個特殊的字符序列,使一個應用程序可區分要解釋的命令和要顯示到屏幕的數據。

ANSI 終端是我們將要模擬的終端類型。TelnetConnection 已經在其到達屏幕之前過濾出 Telnet 握手和協商;現在我們將增加另一個過濾器來過濾出并解釋 ANSI 命令。因為這些命令是我們的終端的指令,實現該邏輯的最好的地方就是在 TelnetCanvas 類自身中。

終端轉義序列如何工作?

當 Telnet 使用字節值 255 來以信號方式表示一個命令序列時,ANSI 使用 ASCII 轉義序列,其值是 27。ANSI 將如下工作:

我們從輸入讀取一個字節。如果其值不是 27 (ESC),則它不是一個命令∶我們將它直接送到應用程序并繼續讀取。

我們讀取下一個字節。如果其值不是 133 ([),則它不是一個命令;我們將該字節后所跟的 27 直接送到應用程序并繼續讀取。

我們將繼續讀取,直到讀取到大于 63 的一個字節。這些字節形成一個字符串,其中含有命令的參數。最后一個字節(是 64 或更大)是命令代碼。我們處理(或忽略)該命令并繼續讀取。

許多命令從官方來講是標準的一部分。雖然完全模擬是一個值得的目標,我們仍將精力放在獲得足夠的功能來使眾多軟件可以接受地運行。我們將實現以下命令:

Cursor Control SequencesErase SequencesA Move cursor up n lines@ Insert n blank spacesB Move cursor down n linesJ Erase display: after cursor (n=0), before cursor (n=1), or entirely (n=2).C Move cursor forward n spacesD Move cursor backward n spacesK Erase line: after cursor (n=0), before cursor (n=1), or entirely (n=2). G Move cursor to column xH Move cursor to column x, row yL Insert n new blank linesd Move cursor to row yM Delete n lines from cursors Save current cursor positionP Delete n characters from cursoru Return to saved cursor position 


大多數這些命令期望參數采用分號分割字符串的形式。參數出現在序列 ESC [ 后和命令字節前。例如,一條用于將光標移動到第 10 行和第 10 列的命令將如下所示:ESC [ 1 0 ; 1 0 H。許多命令只帶一個參數;例如,ESC [ 2 J 將清除屏幕顯示。漏掉的參數將默認為 1,所以 ESC [ H 將把光標返回到坐標 (1,1),而 ESC [ B 則將光標移動到下一行。

雖讓有很多的東西要處理,但協議卻相當簡單。

增強 Telnet Canvas 類

我們需要在兩個領域中更新 TelnetCanvas。它需要解釋從遠程主機接收的 ANSI 命令并發送 ANSI 命令來響應用戶輸入。對于輸入,我們將允許用戶使用設備上的鍵盤來移動光標。

實現 ANSI

雖然我們必須修改 TelnetCanvas 類的內部,但卻沒有必要更改公共接口。所有數據將仍由 receive() 方法來接收。我們只更改其實現來監視轉義序列:

/*** Appends the specified ascii byte to the output.*/public void receive( byte b ){    // ignore nulls    if ( b == 0 ) return;     if ( state == PARAMS_STATE )    {        // if still receiving parameters        if ( b < 64 )        {            argbuf[0]++;                        // grow if needed            if ( argbuf[0] == argbuf.length )            {                char[] tmp = new char[ argbuf.length * 2 ];                System.arraycopy(                     argbuf, 0, tmp, 0, argbuf.length );                argbuf = tmp;             }                        argbuf[ argbuf[0] ] = (char) b;        }        else // final byte: PRocess the command        {            processCommand( b );            // reset for next command            argbuf[0] = 0;            state = NORMAL_STATE;        }    }    else    if ( state == ESCAPE_STATE )    {        // if a valid escape sequence        if ( b == '[' )        {            state = PARAMS_STATE;        }        else // not an escape sequence        {             // allow escape to pass through            state = NORMAL_STATE;            processData( (byte) 27 );            processData( b );        }    }    else // NORMAL_STATE    {        if ( b == 27 )        {            state = ESCAPE_STATE;        }        else        {            processData( b );        }    }}

該方法實現了一個具有三個狀態的簡單狀態機。NORMAL_STATE 監視任何 ESC 字節并將其他任何東西發送到 processData()。當一個 ESC 字節來到時,ESCAPE_STATE 會接管并檢查下一個字節是否是 133 ([)。如果是,我們就轉到 PARAMS_STATE,同時累加參數字符串直到我們碰到此命令字符。我們在執行該操作時,調用 processCommand(),然后轉回 NORMAL_STATE。

讀取命令參數的代碼值得進行檢查。為了避免創建和保存 StringBuffer 所帶來的系統開銷,我們使用一個稱為 argbuf 的字符數組。為了避免不斷的內存重新分配,我們使其比所需的大一些并保持富余,同時根據需要擴大它。最后,為了跟蹤下一個字符到達何處,我們借用 Pascal 的一個技巧,將參數字符串的長度存儲在數組的第一個元素中。getArgument()getArgumentCount() 參數處理來自該數組的單個參數的分析和提取。

我們現在將曾經位于 receive() 方法中的代碼轉到 processData() 方法。邏輯是一樣的,將進入的字節放在當前的光標位置,除了接近方法主體末尾的這兩行外,代碼沒有變化:

/*** Appends the specified byte to the display buffer.*/protected void processData( byte b ){    ...    // increment bound if necessary    while ( cursor > bound ) bound += columns;        ...}


采用較早版本的 MIDTerm 的簡單的面向流的方法時,光標不僅標出了進入數據的插入點,同時標出了應該在屏幕上顯示的數據緩沖區的外界。由于光標現在可以向上和向前移動到數據緩沖區,就需要一個額外的變量來跟蹤數據的外界,以便我們可以確定屏幕的底部在什么位置。這個變量稱為 bound,必須跟蹤光標并在光標移動時位于它前面。

雖然 processData() 處理了大部分字節,但在接收到合法的終端命令時還是要調用 processCommand()。這個方法是我們的 ANSI 實現的核心。

/*** Executes the specified ANSI command, oBTaining arguments* as needed from the getArgument() and getArgumentCount() * methods.*/protected void processCommand( byte command ){    try     {        switch ( command )        {            ... // other commands go here            case 'd': // cursor to row x                 if ( argbuf[0] > 0 )                 {                    cursor = bound                      - ((rows-getArgument( 0 )+1)*columns)                      + ( cursor % columns );                }                break;                        case 'G': // cursor to column x                 if ( argbuf[0] > 0 )                 {                    cursor = cursor                      - ( cursor % columns )                      + getArgument( 0 );                }                break;                        ... // other commands go here                            default:                System.err.println( "unsupported command: "                     + (char) command                     + " : "                     + new String( argbuf, 1, argbuf[0] ) );                        }    }    catch ( Throwable t )     {        // probably parse exception or wrong number of args        System.err.println( "Error in processCommand: " );        t.printStackTrace();    }}

processCommand() 其實是一個大的 switch 語句,為所支持的每條命令都有一個 case。不支持的命令進入默認 case 并被忽略。這里所列出的兩個 case 示范了處理命令的剩余部分的邏輯。

這里您可以了解操縱鼠標所需的數組算法的種類了。記住,我們的二維屏幕是由一維字節數組來表示的,而且我們在內存用盡之前將不會丟棄滾出屏幕頂端的數據。我們盡可能多地保留,以便用戶可以滾動回來來查看他們可能漏看的任何內容。由于這個原因,屏幕的原點必須相對于數組的末尾而不是開頭來計算,所以原點的位置在第 1 行、第 1 列。

如果我們不支持 scrollback 特性,要計算一對坐標的數組下標就很簡單:y * columns + x。要支持這一特性,我們就需要多做一些工作,并必須相對于我們的顯示緩沖區的外界來計算坐標。如果這會使我們的用戶滿意,做這些額外的工作是值得的。

使其具備交互特性

為了使用戶更滿意而又不需要太多工作,可使他們能夠使用他們設備上的鍵盤來與遠程主機交互。

我們較早的應用程序與遠程主機交互的方式,只能是通過執行一組腳本形式的命令。用戶只能等待操作完成,然后使用箭頭鍵來滾動數據緩沖區來查看返回的內容。

在下一版本的 MIDTerm 中,我們將用戶的鍵擊信號直接發送到遠程主機。要發送任何東西,我們都需要一個輸出流,所以 TelnetCanvas 現在有一個 setOutputStream() 方法用于此目的。用戶輸入通過如下修改 keyPressed() 方法來處理:

...private byte[] move = new byte[] { 27, (byte) '[', 0 };...public void keyPressed( int keyCode ){    switch ( getGameAction( keyCode ) )    {        case LEFT:            // move cursor left one column            move[2] = 'D';            send( move );            break;        case RIGHT:            // move cursor right one column            move[2] = 'C';            send( move );            break;        case DOWN:            if ( isScrolling() )            {                // scroll down one row                scrollY++;                if ( scrollY > calcLastVisibleScreen() )                {                    scrollY = calcLastVisibleScreen();                }                repaint();            }            else             {                // move cursor down one row                move[2] = 'B';                send( move );            }            break;        case UP:            if ( isScrolling() )            {                // scroll up one row                scrollY--;                if ( scrollY < 0 ) scrollY = 0;                repaint();            }            else             {                // move cursor down one row                move[2] = 'A';                send( move );            }            break;        case FIRE:            // send a line feed:            send( (byte) '/n' );            break;        default:            // send code directly            send( (byte) keyCode );    }}


要注意的第一件事情是,在決定按哪個鍵之前我們要使用 getGameAction() 來將按鍵代碼轉換為游戲代碼。MIDP 設備的鍵盤布局不同:有些有箭頭按鍵和數字小鍵盤,有些只有數字小鍵盤可用作箭頭按鍵。getGameAction() 方法隱藏了這些復雜性。

如果所按的鍵是 UP、DOWN、LEFT 或 RIGHT,我們生成相應的 ANSI 命令并將其發送到遠程主機。注意 UPDOWN 有兩個模式:一個用于移動光標,一個用于滾動顯示內容。當前的滾動模式由兩種新的方法 isScrolling()setScrolling() 來發現和控制。如果打開滾動,UPDOWN 將滾動輸出而不是移動光標。注意,通過直接測試 scrolling 變量而不是調用 isScrolling() 來測試,我們可以節省一些系統開銷。

MIDP終端模擬之二:高級終端模擬(圖二)FIRE 鍵發送一個換行符,它類似于一般鍵盤上的 Enter 或 Return 鍵。這種特性對于基于菜單的應用程序很有用,由箭頭鍵突出顯示一個選項并用 Enter 鍵選擇突出顯示的選項;Lynx web 瀏覽器就是一個很好的例子。這些應用程序僅使用用戶手持設備上的鍵盤就可以完全發揮作用。

如果按鍵代碼沒有映射到任何游戲動作,就會將它直接發送到遠程主機。MIDP 規范中規定,具有比標準手機的按鍵多的設備應發送等同的 ASCII 字符作為其按鍵代碼。直接發送這些鍵代碼使得應用程序可以完全利用具有完整鍵盤的設備的優勢。具有這些設備的用戶只需開始輸入即可;他們的鍵擊會通過要建立的連接而發送。

最后,因為 TelnetCanvas 組件隱藏了來自應用程序的其他部分的所有終端模擬邏輯,我們需要新的方法 getRows()getColumns()getTerminalType() 來宣布屏幕尺寸和實現所支持的終端模擬的種類。

更新 MIDlet

與以前一樣,MIDlet 類自身 MIDTermTelnetConnectionTelnetCanvas 捆綁在一起。只需要首位改變一下連接的設置:

...connection = new TelnetConnection(     (StreamConnection) Connector.open(         connectString, Connector.READ_WRITE, true ),    canvas.getColumns(),     canvas.getRows(),     canvas.getTerminalType() );input = connection.openInputStream();output = connection.openOutputStream();canvas.setOutputStream( output );...

但 MIDTerm 需要做更多。雖然 TelnetCanvas 處理基本用戶輸入,僅有數字鍵的鍵盤則需要特殊的方式來輸入文字,如重復按某個鍵來選擇某個字母,或預測式文字輸入法。利用這些內在功能的唯一途徑就是使用 MIDP 的 TextFieldTextBox 組件。因為這些組件不能與 Canvas 位于相同的屏幕,文字輸入需要另外一個屏幕。雖然我們在屏幕上,但應用程序還應提供另一個屏幕來建立連接,這要用到主機名稱和要在其上連接的端口的字段。MIDTerm 將用作中心代理來將這些屏幕捆綁在一起。

Form 類非常靈活,所以我們不需要每個類都有一個單獨的子類,而這一點很讓人高興。大多數 MIDP 設備用于應用程序存儲的空間都有限,并限制應用程序的大小,通常是最大 32 KB 或 64 KB,所以大小問題很重要。應用程序中的每個類都會至少將 JAR  文件的大小增加半個千字節,即使在模糊處理后仍是如此,所以應盡可能地避免創建子類。

輸入窗體會經常用到,所以將它創建在 MIDTerm 的構造函數中,并在應用程序運行期間保持待用狀態。這種方法消除了創建輸入窗體帶來的任何可感知的延遲,并避免為窗體進行重復內存分配和垃圾收集而引起的內存擾動(memory churn)。輸入窗體的設置非常簡單:

...MIDP終端模擬之二:高級終端模擬(圖三)inputForm = new Form( "Input" );inputField = new TextField( null, "", 255, TextField.ANY );inputForm.append( inputField );inputOptions = new ChoiceGroup( null, Choice.MULTIPLE );inputOptions.append( INPUT_ENTER, null );inputOptions.append( INPUT_CTRL, null );inputOptions.append( INPUT_ESC, null );inputOptions.setSelectedIndex( 0, true ); // default trueinputForm.append( inputOptions );scrollOptions = new ChoiceGroup( null, Choice.MULTIPLE );scrollOptions.append( INPUT_SCROLL, null );inputForm.append( scrollOptions ); inputForm.addCommand( okCommand );inputForm.addCommand( cancelCommand );inputForm.setCommandListener( this );...


TelnetCanvas 會在用戶希望發送一些文字時激活該窗體。第一個字段是 TextField,而且在大部分平臺上它都應獲得默認焦點。稍微費點功夫,用戶就可以快速調用該窗體,輸入一些文字,然后選擇 OK 命令來發送數據。

因為許多基于終端的應用程序假定用戶應采用終端類型的鍵盤,用戶就需要某種方法來發送特殊的鍵擊,如 Return、Escape 或 Control 加按鍵組合。我們為此而使用 ChoiceGroup,將其設置為 MULTIPLE 模式,以便我們得到復選框而不是互斥的單選按鈕。根據兩種復選框設置,用戶的文本將在 Escape 字符后發送,或在換行字符后發送。如果選擇了 Send as CTRL 選項,發送每個字符時就像是按下了 Control 鍵一樣(Control 加按鍵代碼的計算方法,是將一個字符轉換為其大寫形式,然后從其值減 64)。Append ENTER 選項默認為 true,因為可用性測試表明大多數文字輸入后跟的是一個換行符。

最后,該窗體是允許客戶設置 TelnetCanvas 的滾動模式的一個很好的地方。這一特性在功能上等同于終端鍵盤上的 Scroll Lock 鍵,而復選框也同樣地進行標記。為了進行可視區分,這個選項加入一個單獨的 ChoiceGroup。

登錄窗體在一般的應用程序生命周期內很少會用到兩次以上,所以我們只根據需要來創建它,當不用時就將其清除。其創建和布局在 onShowLogin() 中進行:

MIDP終端模擬之二:高級終端模擬(圖四)public void onShowLogin(){    // create and populate login form    loginForm = new Form( "Connect to" );    hostField =         new TextField( "Host", host, 50, TextField.URL );    loginForm.append( hostField );    portField =         new TextField( "Port", port, 6, TextField.NUMERIC );    loginForm.append( portField );    loginForm.addCommand( exitCommand );    loginForm.addCommand( openCommand );    loginForm.setCommandListener( this );        // show form    display.setCurrent( loginForm );}

雖然這是一個簡單的窗體,可用性仍是我們最重要的考慮。一些實現可以利用一個 TextFieldURLNUMERIC)上的“線索”并適當地定制用戶界面。因為用戶通常將在 Host 中輸入服務器的域名,指出所期望的用戶輸入像一個 URL 就很有意義。某些設備可能有特殊的屏幕布局,它們針對字母和符號而不是數字進行優化。同樣地,Port 字段應僅限于數字輸入;一些設備可能允許用戶在它們的鍵盤上快速輸入數字,而繞過任何重復按鍵式文字輸入系統。

MIDlet 是登錄窗體、輸入窗以及 Telnet canvas 的命令監聽器??蓪⑵湟曌鲬贸绦蚬ぷ髁鞯南驅?。    MIDP終端模擬之二:高級終端模擬(圖五)MIDP終端模擬之二:高級終端模擬(圖六)MIDP終端模擬之二:高級終端模擬(圖七)MIDP終端模擬之二:高級終端模擬(圖八)MIDP終端模擬之二:高級終端模擬(圖九)

在啟動時,將出現登錄窗體,選項為 OpenExit。 一旦打開,就會出現 Telnet canvas,選項為 CloseInput。Input 選項顯示輸入窗體,其中有一個 OK 選項用于隱藏窗體并發送文字,另有一個 Cancel 選項僅僅隱藏窗體。Close 選項隱藏 Telnet canvas 并顯示一個登錄窗體。最后,Exit 選項調用 notifyDestroyed() 并退出應用程序。所有這些命令由 MIDTermcommandAction() 方法來處理。

讓我們使它工作起來

現在所有組件已經就緒,我們就可以使我們的終端模擬器工作起來了。終端應用程序在企業和教育計算環境中得到廣泛應用,用于企業應用程序、軟件開發、系統管理甚至對策模擬。MIDTerm 使得所有這些種類的應用程序和資源都可以從您的移動設備來訪問。


軟件開發: emacs
MIDP終端模擬之二:高級終端模擬(圖十)
系統管理: top
MIDP終端模擬之二:高級終端模擬(圖十)
Gaming: starcross
MIDP終端模擬之二:高級終端模擬(圖十二)

更進一步,任何這些應用程序都可以被“刮擦”:您可以編寫一個移動應用程序來與遠程服務器上的資源密集型程序進行交互、提取輸出并將它呈現在一個用戶友好的圖形界面上。企業開發者通常使用這種技術來在遺留系統上構造新式的用戶界面。

MIDP 平臺上的有效 Telnet 和 ANSI 終端實現清除了這些種類的軟件項目的主要障礙。

結束語

通過實現對 ANSI 的終端轉義序列的支持,我們已經更新了第一篇文章的未完善的終端顯示。這個應用程序還更好地利用了 MIDP 的用戶界面功能,并具有對用于用戶輸入的鍵盤和定制窗體的支持。您可以組合和匹配這些軟件組建來為新的種類的網絡意識(network-aware)移動應用程序提供基礎。

有關詳細信息

(出處:http://www.49028c.com)



發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美专区在线视频| 欧美激情综合色| 亚洲国产日韩欧美综合久久| 亚洲第一中文字幕在线观看| 一本色道久久88精品综合| 亚洲白虎美女被爆操| 在线日韩中文字幕| 欧美影院成年免费版| 欧美日韩第一页| 国产精品欧美日韩| 国产日韩在线看| 亚洲a一级视频| 久99久在线视频| 91天堂在线观看| 欧美成人精品在线播放| 久久久噜久噜久久综合| 2020国产精品视频| 国产精品91在线| 欧美亚洲国产精品| 久久免费在线观看| 精品国产电影一区| 2021久久精品国产99国产精品| 欧美午夜性色大片在线观看| 欧美在线视频导航| 91精品国产综合久久香蕉922| 国产精品精品视频| 美女精品视频一区| 在线观看欧美日韩| 欧美日韩在线第一页| 日韩www在线| 亚洲精品国产美女| 久久久久久久一区二区三区| 亚洲美女自拍视频| 国产精品第一第二| 最近2019免费中文字幕视频三| 亚洲精品中文字幕有码专区| 欧美一级视频一区二区| 国产一区二区香蕉| 亚洲天堂成人在线| 日韩美女免费线视频| 日本韩国欧美精品大片卡二| 亚洲精品国产欧美| 亚洲色图第三页| 国产成人aa精品一区在线播放| 日韩欧美视频一区二区三区| 欧美激情一区二区三区高清视频| 91美女高潮出水| 欧美亚洲国产日韩2020| 亚洲区在线播放| 色综合伊人色综合网站| 久久久久成人网| 在线观看欧美www| 不卡伊人av在线播放| 97**国产露脸精品国产| 韩国美女主播一区| 欧美精品免费在线| 77777少妇光屁股久久一区| 久久精品免费电影| 日本一区二区三区四区视频| 日韩大片在线观看视频| 欧美日韩成人在线视频| 亚洲国产精品中文| 久久久久久久久网站| 欧美激情综合亚洲一二区| 中文字幕国产精品| 精品高清一区二区三区| 日韩精品久久久久| 久久久天堂国产精品女人| 国产偷亚洲偷欧美偷精品| 国产xxx69麻豆国语对白| www.99久久热国产日韩欧美.com| 97**国产露脸精品国产| 日韩av在线免费观看| 91成人福利在线| 亚洲欧美制服另类日韩| 亚洲国产精品va在线看黑人动漫| 97碰碰碰免费色视频| 国产精品一区二区在线| 日韩美女在线观看| 欧美多人乱p欧美4p久久| 欧美日本中文字幕| 国产精品99久久久久久久久久久久| 久久躁狠狠躁夜夜爽| 中文字幕精品影院| 这里只有精品在线观看| 国产啪精品视频| 日韩美女免费视频| 欧美一级电影免费在线观看| 色综合久久88色综合天天看泰| 精品综合久久久久久97| 91精品国产网站| 国产精品国产三级国产aⅴ9色| 欧美亚洲激情视频| 91免费国产网站| 午夜精品视频网站| 亚洲欧洲国产精品| 欧美一区二区大胆人体摄影专业网站| 亚洲午夜久久久影院| 一区二区成人精品| 亚洲欧洲偷拍精品| 久久久久久成人精品| 欧美性猛交xxxx乱大交蜜桃| 国产亚洲a∨片在线观看| 欧美精品videosex牲欧美| 国产精品久久99久久| 精品成人国产在线观看男人呻吟| 色偷偷91综合久久噜噜| 欧美一级淫片videoshd| 亚洲福利视频久久| 亚洲精品小视频在线观看| 久久综合久久88| 日韩**中文字幕毛片| 日韩一级裸体免费视频| 日本一区二区三区在线播放| 91精品视频一区| 91色p视频在线| 夜夜嗨av色一区二区不卡| 91在线直播亚洲| 91在线视频导航| 国产成人精品优优av| 日本一区二区在线免费播放| 欧美激情精品久久久久久变态| 欧美电影电视剧在线观看| 亚洲人成网站777色婷婷| 国产精品久久久久久久久久ktv| 国产精品揄拍500视频| 国产精品白丝av嫩草影院| 中文字幕欧美国内| 欧美一区深夜视频| 欧美精品一区二区免费| 夜夜嗨av一区二区三区四区| 国产日韩一区在线| 亚洲成av人影院在线观看| 亚洲欧美日韩精品久久奇米色影视| 亚洲精品欧美一区二区三区| 91精品久久久久久| 成人福利视频网| 精品少妇一区二区30p| 国产精品欧美日韩一区二区| 国产a∨精品一区二区三区不卡| 欧美极品xxxx| 成人免费福利视频| 日韩性xxxx爱| 97免费中文视频在线观看| 精品福利免费观看| 这里只有精品在线观看| 欧美在线视频在线播放完整版免费观看| 亚洲91精品在线观看| 欧美午夜性色大片在线观看| 韩曰欧美视频免费观看| 国产成人精品综合久久久| 欧美午夜视频一区二区| 久久久久久中文| 久久九九国产精品怡红院| 精品国产91乱高清在线观看| 久久综合久中文字幕青草| 久久精品精品电影网| 精品久久久精品| 国产成人自拍视频在线观看| 亚洲第一福利网| 欧美精品第一页在线播放| 久久久久久九九九| 成人黄色免费片| 欧美老女人性生活|