国产欧美日韩精品a在线观看-国产欧美日韩精品一区二区三区-国产欧美日韩精品综合-国产欧美中文字幕-一区二区三区精品国产-一区二区三区精品国产欧美

ACS880-07C
關注中國自動化產業發展的先行者!
CAIAC 2025
2024
工業智能邊緣計算2024年會
2023年工業安全大會
OICT公益講堂
當前位置:首頁 >> 案例 >> 案例首頁

案例頻道

Delphi環境下使用定制接口開發OPC數據訪問客戶程序
  • 企業:控制網     領域:電源    
  • 點擊數:5897     發布時間:2005-07-13 17:23:40
  • 分享到:
簡要的介紹了OPC以及使用定制接口開發OPC數據訪問客戶程序的背景知識。文中較為詳細的講述了如何Delphi環境下使用定制接口開發OPC數據訪問客戶程序。



1  引言


圖1  OPC應用架構



    OPC(用于過程控制的OLE)是一個工業標準。它由一些世界上占領先地位的自動化系統和硬件、軟件公司與微軟公司緊密合作而建立的。這個標準定義了應用Microsoft操作系統在基于PC 的客戶機之間交換自動化實時數據的方法。管理這個標準的國際組織是OPC基金會。因為應用程序要和不同的設備,比如PLC、變頻器、現場總線的儀表等通訊,如果不同的設備廠家都遵守一個相同的程序接口標準的話,那么程序和不同設備的溝通將變得非常容易。OPC 就是這樣一種工業標準,它是OLE for Process Control 的英文縮寫。OPC 是基于微軟的COM(Component Object Model,組件對象模型)和OLE(Object Linking and Embedding,對象鏈接與嵌入)技術之上的。和以前不同的是,現在設備廠家提供
不同的OPC Server。OPC Server負責從設備中取數據和寫數據。人們所要做的就是利用統一的COM 規范編寫OPC Client??蛻舫绦蚝蚈PC Server 打交道。OPC Server是一座在客戶和硬件設備之間的橋梁,通過它,人們可以很容易的取得現場的溫度、壓力、流量、位置等信號,以及控制現場的閥門開度、電機轉速等。注意客戶程序和服務器程序可以在同一臺計算機上,也可以在不同的計算機上,區別是使用COM 還是使用DCOM(Distributed Component Object Model,分布式組件對象模型)。如圖1所示。

2  OPC數據訪問服務器接口方式

    OPC數據訪問規范提供了兩套接口方案,即定制接口(Custom Interface)和自動化接口(Automation Interface)。其中自動化接口是對定制接口的進一步封裝。定制接口效率高,采用它能夠發揮OPC服務器的最佳性能。用C++編寫訪問OPC服務器的程序一般采用定制接口方案;對于VB、VBA、EXCEL等編程軟件或工具,不能直接訪問通用接口,而要通過自動化接口,因此使用VB或VBA等語言的客戶程序一般使用自動化接口。


圖2  OPC數據訪問方式

3  OPC數據訪問服務器的數據訪問方法

(1)  同步數據訪問處理
    OPC 服務器把按照OPC 客戶應用程序的要求得到的數據訪問結果,作為方法的參數返回給OPC 客戶程序。OPC 客戶應用程序在結果被返回之前必須處于等待狀態。
(2)  異步數據訪問處理
    OPC 服務器接到 OPC 應用程序的要求后,幾乎立即將方法返回。OPC 客戶應用程序隨后可以進行其他處理。當OPC 完成數據訪問時,觸發OPC 客戶應用程序的一部訪問事件,將數據訪問結果傳送給OPC 應用程序。OPC 應用程序在事件處理程序中接受OPC 服務器傳送來的數據。
(3)  訂閱方式數據采集
    此種方式是OPC 客戶應用程序不需要向OPC 服務器程序提出數據請求,而是自動接收OPC 服務器送來的變化通知信號的訂閱方式數據采集。服務器按照一定的更新周期更新OPC 服務器的數據緩沖器的數值,如果發現數值有變化時,就會以數據變化事件通知OPC 客戶應用程序。OPC 服務器支持不敏感帶寬 (Dead Band),當OPC標簽的數據類型是模擬量時,只有當前值與上一次值的偏差的絕對值超過一定的限度時,才更新緩沖器的數據并通知OPC 客戶應用程序。這樣就可以忽略模擬量的微小變化,從而減輕OPC 服務器和OPC 客戶的負擔。
(4)  刷新方式
    刷新是一種特殊形式的訂閱,它強制更新活動組中的所有活動項數據,而不管這些數據與以前相比是否有了變化。

4  使用Delphi進行COM程序開發的優點

    Delphi是功能強大的應用程序開發工具。它具有功能強大、運行速度快、易于學習、使用和開發效率高等特點。它是可視化應用編程環境,可重用性面向對象編程語言,快速編譯器和數據庫的完美組合。編寫OPC定制接口的客戶程序的本質就是編寫COM客戶程序,而使用Delphi進行COM開發時,人們會發現Object Pascal為COM提供了強大的語言支持。主要有以下幾點:
(1)  Variant 和 OleVariant支持
    直接使用C和C++處理變體需要調用VariantInit(),VariantCopy(),VariantClear()等函數。而Object Pascal對變體的支持,使得在使用Variant和OleVariant類型的情況下,編譯器能夠自動生成對API的變體支持程序的調用。
(2)  可變數組支持
    在Delphi中一旦一個Variant包含了一個可變數組,就可以用標準數組的下標來訪問數組元素。與C和C++中手工生成的安全數組相比,Object Pascal的封裝功能要簡潔的多,且不易出錯。
(3)  后期綁定Automation支持
    Object Pascal對于Variant和OleVariant的支持使得編寫后期綁定的自動化(Automation)客戶程序成為可能。因此使用Delphi來開發基于自動化接口的OPC客戶程序也是很方便的。
(4)  寬字符串支持
    Delphi提供的寬字符串(WideString)類型是一個和COM BSTR字符串兼容的字符串。而傳統方式下,使用標準的諸如SysStringLen()等API函數與BSTR一起工作是一件相當麻煩的事。
(5)  接口支持
    Object Pascal為合乎COM規范的接口提供了完全自包容的實現代碼,接口的實現不需要任何的COM API函數。程序員不需要考慮在傳統COM編程中的引用計數和接口查詢等底層細節。
(6)  Dispinterface接口支持
    Object Pascal對于Dispinterface的支持使得編寫支持雙接口的COM程序非常容易。

5  Delphi環境下使用定制接口開發OPC數據訪問客戶程序

    OPC規范中規定OPC服務器必須提供定制接口,而自動化接口則可以有選擇地提供。因此編寫使用定制接口的OPC客戶程序更具有一般意義,而且使用定制接口的效率要遠遠高于使用自動化接口的效率。本文限于篇幅只討論針對OPC數據訪問服務器的客戶應用程序。
5.1  OPC數據訪問服務器簡介
    OPCDataAccess服務器是最基本的OPC服務器,它包括OPCServer、OPCGroup和OPCItem三類典型的對象。OPC服務器對象維護有關服務器的信息并用作OPC組對象的容器,而OPC組對象維護組的信息,提供包容OPC項的機制,并管理OPC項。OPC組提供了客戶程序組織數據的手段。有兩種類型的組:公共(Public)組和局部(Local)組。公共組可以被多個客戶共享,而局部組只能被一個客戶使用。每個組中都可以定義一個或多個OPC項。OPC項代表了與服務器中的數據的連接。客戶程序對OPC項的操作都是通過包容此項的OPC組來進行的,而不是直接把OPC項作為一個對象來操作。每個OPC項都有值(Value)、品質(Quality)和時間戳(Time Stamp)三個屬性。



圖3  OPC數據訪問服務器的數據組織方式

    人們要的就是上面的item,這就是點,人們所謂的點,就是PLC的I/O點、儀表的數值等。編寫客戶端的程序的過程實際上就是對在OPC服務器中的數據項目(Item)進行操作。這就要求人們要了解OPC數據訪問服務器不同對象的接口的功能:
(1)  OPCServer對象接口
    OPCServer對象是OPC中的首要對象,它提供了如下接口:
    IUnknown接口是COM的標準接口;
   IOPCServer接口可對OPCGroup對象進行有關操作;
   IOPCServerPublicGroups接口為客戶和服務器提供了管理公共組的功能;
   IOPCBrowseServerAddressSpace接口提供了客戶瀏覽服務器數據項的功能;
   IOPCItemProperties接口讓客戶能夠瀏覽與ItemID相關的可訪問屬性;
   IOPCCommon接口提供了設置和詢問LocaleID的功能;
   IPersistFile接口允許客戶裝載和保存服務器的配置信息;
   IConnectionPointContainer接口允許用戶探查發現連接點。
(2)  OPCGroup對象接口
   OPCGroup對象是管理數據項集合的對象,它提供的接口如下:
   IUnknown接口是COM的標準接口;
   IOPCItemMgt接口為客戶提供了添加,刪除和控制組中數據項的功能;
   IOPCGroupStateMgt接口允許客戶管理組中的所有狀態信息;
   IOPCPublicGroupStateMgt接口用來將私有組轉換為公共組;
   IOPCSyncIO接口允許用戶對服務器執行同步讀寫操作;
   IOPCAsyncIO接口允許客戶對服務器執行異步讀寫操作;
   IOPCAsyncIO2接口用來替代IOPCAsyncIO接口;
   IConnectionPointContainer接口允許用戶探查發現連接點;
   IDataObject接口允許客戶和使用OPC數據流格式的組之間產生連接。
5.2  同步讀寫方式編程
   編程部分限于篇幅只列出核心代碼,關于一些類型定義,接口描述均省略了。
(1)  COM庫的初始化
   在調用任何COM或OLE API函數之前,必須要用CoIntialize()函數來對COM庫進行初始化,為了關閉COM庫在最后一次調用COM庫后,要調用CoUnitialize()函數。使用Delphi開發時,事情就簡化了,人們只需要在程序中包括ComObj單元即可,這樣應用程序在調用Application.Initialize()時會自動調用CoInitialize()函數,而ComObj單元的finalization部分會自動調用CoUnitialize()函數。只需要一句代碼:
Uses ComObj;
(2)  創建服務器對象
   Const ServerProgID = 'hua.da2.1';//OPC服務器的注冊名稱
   Var 
   ServerIf: IOPCServer;//聲明服務器對象接口
   HR: HResult;//用來保存函數返回值
   ServerIf := CreateComObject(ProgIDToClassID(ServerProgID)) as IOPCServer;
   / /本函數用來獲取服務器對象的IOPCServer接口,這是一個COM庫函數
(3)  添加組對象
   Var  
   GroupIf: IOPCItemMgt; 
   GroupHandle: OPCHANDLE;
   HR:=ServerAddGroup(ServerIf, 'MyGroup', True, 500, 0, GroupIf, GroupHandle);
   //本函數用來對IOPCServer.AddGroup方法進行包裝,在服務器對象中添加一個名為MyGroup的組對象,激活狀態為true,更新率為500毫秒。如果組對象添加成功,則組對象接口保存在GroupIf中,組對象句柄保存在GroupHandle中。其實現過程如下:
   function ServerAddGroup(ServerIf: IOPCServer; Name: string; Active: BOOL;
        UpdateRate: DWORD; ClientHandle: OPCHANDLE; var GroupIf: IOPCItemMgt;
        var ServerHandle: OPCHANDLE): HResult;
var
  PercentDeadBand: Single;
  RevisedUpdateRate: DWORD;
begin
  Result := E_FAIL;
  if ServerIf <> nil then
  begin
    PercentDeadBand := 0.0;
    Result := ServerIf.AddGroup(PWideChar(WideString(Name)), Active, UpdateRate,
                            ClientHandle, nil, @PercentDeadBand, 0,
                            ServerHandle, RevisedUpdateRate, IOPCItemMgt,
                            IUnknown(GroupIf));
  end;
  if Failed(Result) then
  begin
    GroupIf := nil;
  end;
end;
(4)  添加項目
const  Item0Name = 'item.hua.bstr';//要添加的項目名稱
Var 
ItemType: TVarType;
Item0Handle: OPCHANDLE;
HR := GroupAddItem(GroupIf, Item0Name, 0, VT_EMPTY, Item0Handle,ItemType);
//本函數用來對IOPCItemMgt.AddItems進行包裝,在組對象中添加一個類型為VT_EMPTY,名稱為item.hua.bstr的項目,如果添加成功,則項目句柄保存在Item0Handle中,實際項目類型保存在ItemType中。其實現過程如下:
function GroupAddItem(GroupIf: IOPCItemMgt; ItemID: string;
          ClientHandle: OPCHANDLE; DataType: TVarType;
          var ServerHandle: OPCHANDLE; var CanonicalType: TVarType): HResult;
var
  ItemDef: OPCITEMDEF;
  Results: POPCITEMRESULTARRAY;
  Errors: PResultList;
begin
  if GroupIf = nil then
  begin
    Result := E_FAIL;
    Exit;
  end;
  with ItemDef do
  begin
    szAccessPath := '';
    szItemID := PWideChar(WideString(ItemID));
    bActive := True;
    hClient := ClientHandle;
    dwBlobSize := 0;
    pBlob := nil;
    vtRequestedDataType := DataType;
  end;
  Result := GroupIf.AddItems(1, @ItemDef, Results, Errors);
  if Succeeded(Result) then
  begin
    Result := Errors[0];
    try
      if Succeeded(Result) then
      begin
        ServerHandle := Results[0].hServer;
        CanonicalType := Results[0].vtCanonicalDataType;
      end;
    finally
      CoTaskMemFree(Results[0].pBlob);
      CoTaskMemFree(Results);
      CoTaskMemFree(Errors);
    end;
  end;
end;
(5)  同步讀
Var
ItemValue: string;
ItemQuality: Word;
HR := ReadOPCGroupItemValue(GroupIf, Item0Handle, ItemValue, ItemQuality);
//本函數用來同步讀取組中的項目值,如果讀取成功,則項目值保存在ItemValue中,項目質量保存在ItemQuality中,其實現過程如下:
function ReadOPCGroupItemValue(GroupIf: IUnknown; ItemServerHandle: OPCHANDLE;
          var ItemValue: string; var ItemQuality: Word): HResult;
var
  SyncIOIf: IOPCSyncIO;
  Errors: PResultList;
  ItemValues: POPCITEMSTATEARRAY;
begin
  Result := E_FAIL;
  try
    SyncIOIf := GroupIf as IOPCSyncIO;
  except
    SyncIOIf := nil;
  end;
  if SyncIOIf <> nil then
  begin
    Result := SyncIOIf.Read(OPC_DS_CACHE, 1, @ItemServerHandle, ItemValues,
                            Errors);
    if Succeeded(Result) then
    begin
      Result := Errors[0];
      CoTaskMemFree(Errors);
      ItemValue := VarToStr(ItemValues[0].vDataValue);
      ItemQuality := ItemValues[0].wQuality;
      VariantClear(ItemValues[0].vDataValue);
      CoTaskMemFree(ItemValues);
    end;
  end;
end;
(6)  同步寫
ItemValue:='hello,the operation is sync-write';
HR := WriteOPCGroupItemValue(GroupIf, Item0Handle, ItemValue);
//本函數用來將ItemValue同步寫入Item0Handle所代表的項目中,其實現過程如下:
function WriteOPCGroupItemValue(GroupIf: IUnknown; ItemServerHandle: OPCHANDLE;
          ItemValue: OleVariant): HResult;
var
  SyncIOIf: IOPCSyncIO;
  Errors: PResultList;
begin
  Result := E_FAIL;
  try
    SyncIOIf := GroupIf as IOPCSyncIO;
  except
    SyncIOIf := nil;
  end;
  if SyncIOIf <> nil then
  begin
    Result := SyncIOIf.Write(1, @ItemServerHandle, @ItemValue, Errors);
    if Succeeded(Result) then
    begin
      Result := Errors[0];
      CoTaskMemFree(Errors);
    end;
  end;
end;
(7)  斷開服務器連接
   HR := ServerIf.RemoveGroup(GroupHandle, False);
   //本函數用來將在服務器中創建的組GroupHandle刪除,服務器對象在程序結束時會自動銷毀。
5.3  異步讀寫方式編程
    使用異步方式進行讀寫編程,需要客戶提供IOPCDataCallback接口,還需要使用到COM的連接點容器,連接點和接收器等相關知識。以下關于異步讀寫方式編程中和同步方式相同的部分均未給出其實現部分。下面給出異步讀寫方式編程的主要步驟:
   (1)  COM庫的初始化   //實現過程同上
   (2)  創建服務器對象 //實現過程同上
   (3)  添加組對象 //實現過程同上
   (4)  添加項目  //實現過程同上
   (5)  實現IOPCDataCallback接口
   為了使用連接點,客戶必須創建同時支持IUnknown和IOPCDataCallback接口的對象。下面的TOPCDataCallback從TInterfacedObject繼承所以支持IUnknown接口,它實現了IOPCDataCallback接口。
type
   // 本類用來接收 IConnectionPointContainer 的數據變化回調
  TOPCDataCallback = class(TInterfacedObject, IOPCDataCallback)
  public
    function OnDataChange(dwTransid: DWORD; hGroup: OPCHANDLE;
      hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
      phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
      pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
      pErrors: PResultList): HResult; stdcall;
    function OnReadComplete(dwTransid: DWORD; hGroup: OPCHANDLE;
      hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
      phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
      pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
      pErrors: PResultList): HResult; stdcall;
    function OnWriteComplete(dwTransid: DWORD; hGroup: OPCHANDLE;
      hrMastererr: HResult; dwCount: DWORD; pClienthandles: POPCHANDLEARRAY;
      pErrors: PResultList): HResult; stdcall;
    function OnCancelComplete(dwTransid: DWORD; hGroup: OPCHANDLE):
      HResult; stdcall;
  end;
(6)  連接IOPCDataCallback接口
   客戶程序除了實現接收器對象外,還必須建立接收器與連接點對象之間的連接關系
var 
  AsyncConnection: Longint;
  OPCDataCallback: IOPCDataCallback;
  OPCDataCallback := TOPCDataCallback.Create;//創建接收器對象
  HR := GroupAdvise2(GroupIf, OPCDataCallback, AsyncConnection);
//本函數實現了接收器對象與連接點對象之間建立關系的過程,如果連接成功則用AsyncConnection來標識這個連接,其具體實現如下:
function GroupAdvise2(GroupIf: IUnknown; OPCDataCallback: IOPCDataCallback;
          var AsyncConnection: Longint): HResult;
var
  ConnectionPointContainer: IConnectionPointContainer;
  ConnectionPoint: IConnectionPoint;
begin
  Result := E_FAIL;
  try
    ConnectionPointContainer := GroupIf as IConnectionPointContainer;
  except
    ConnectionPointContainer := nil;
  end;
  if ConnectionPointContainer <> nil then
  begin
    Result := ConnectionPointContainer.FindConnectionPoint(IID_IOPCDataCallback,
      ConnectionPoint);
    if Succeeded(Result) and (ConnectionPoint <> nil) then
    begin
      Result := ConnectionPoint.Advise(OPCDataCallback as IUnknown,
        AsyncConnection);
    end;
  end;
end;
(7)  異步讀寫
   客戶進行異步讀寫操作只需要簡單的調用IOPCAsyncIO2接口的Read,Write等方法,這個過程不需要客戶程序等待,函數的返回值中并不包括操作的結果,而操作的具體結果會由服務器通過調用客戶的IOPCDataCallback接口中的OnDataChange,OnReadComplete,OnWriteComplete,OnCancelComplete等方法來返回給客戶。下面給出IOPCAsyncIO2接口的Delphi描述:
IOPCAsyncIO2 = interface(IUnknown)
    ['{39C13A71-011E-11D0-9675-0020AFD8ADB3}']
    function Read(
            dwCount:                    DWORD;
            phServer:                   POPCHANDLEARRAY;
            dwTransactionID:            DWORD;
      out   pdwCancelID:                DWORD;
      out   ppErrors:                   PResultList): HResult; stdcall;
    function Write(
            dwCount:                    DWORD;
            phServer:                   POPCHANDLEARRAY;
            pItemValues:                POleVariantArray;
            dwTransactionID:            DWORD;
      out   pdwCancelID:                DWORD;
      out   ppErrors:                   PResultList): HResult; stdcall;
    function Refresh2(
            dwSource:                   OPCDATASOURCE;
            dwTransactionID:            DWORD;
      out   pdwCancelID:                DWORD): HResult; stdcall;
    function Cancel2(
            dwCancelID:                 DWORD): HResult; stdcall;
    function SetEnable(
            bEnable:                    BOOL): HResult; stdcall;
    function GetEnable(
      out   pbEnable:                   BOOL): HResult; stdcall;
  end;
   當服務器中的數據發生變化時,服務器會調用客戶的IOPCDataCallback接口中OnDataChange函數,當服務器異步讀完成后會調用客戶的IOPCDataCallback接口中OnReadComplete函數,當服務器異步寫完成后會調用客戶的IOPCDataCallback接口中OnWriteComplete函數,當服務器撤銷操作完成后會調用客戶的IOPCDataCallback接口中OnCancelComplete函數。限于篇幅,這里只給出客戶的IOPCDataCallback接口中OnDataChange函數實現,其他函數的實現過程類似。
function TOPCDataCallback.OnDataChange(dwTransid: DWORD; hGroup: OPCHANDLE;
  hrMasterquality: HResult; hrMastererror: HResult; dwCount: DWORD;
  phClientItems: POPCHANDLEARRAY; pvValues: POleVariantArray;
  pwQualities: PWordArray; pftTimeStamps: PFileTimeArray;
  pErrors: PResultList): HResult;
var
  ClientItems: POPCHANDLEARRAY;
  Values: POleVariantArray;
  Qualities: PWORDARRAY;
  I: Integer;
  NewValue: string;
begin
  Result := S_OK;
  ClientItems := POPCHANDLEARRAY(phClientItems);
  Values := POleVariantArray(pvValues);
  Qualities := PWORDARRAY(pwQualities);
  for I := 0 to dwCount - 1 do
  begin
    if Qualities[I] = OPC_QUALITY_GOOD then
    begin
      NewValue := VarToStr(Values[I]);
      Form1.LblValue.Caption :=NewValue;
    end
    else begin
      ShowMessage('Callback received for item , but quality not good');
    end;
  end;
end;
(8)  斷開IOPCDataCallback接口
GroupUnadvise2(GroupIf, AsyncConnection);
//斷開客戶與組對象GroupIf之間用AsyncConnection標識的IOPCDataCallback接口連接,其實現過程如下:
function GroupUnadvise2(GroupIf: IUnknown; var AsyncConnection: Longint): HResult;
var
  ConnectionPointContainer: IConnectionPointContainer;
  ConnectionPoint: IConnectionPoint;
begin
  Result := E_FAIL;
  try
    ConnectionPointContainer := GroupIf as IConnectionPointContainer;
  except
    ConnectionPointContainer := nil;
  end;
  if ConnectionPointContainer <> nil then
  begin
    Result := ConnectionPointContainer.FindConnectionPoint(IID_IOPCDataCallback,
      ConnectionPoint);
    if Succeeded(Result) and (ConnectionPoint <> nil) then
    begin
      Result := ConnectionPoint.Unadvise(AsyncConnection);
    end;
  end;
end;
(9)  斷開服務器連接 //實現過程同上

6  結語

    使用OPC的定制接口進行客戶程序的編程,需要一些COM知識的理解以及對于以下開發工具的熟練使用,因此相對于自動化接口的編程來說時比較困難的,但是使用定制接口的效率和靈活性卻是使用其他方式所無法比擬的。本文只是粗淺的介紹了在Delphi環境下使用定制接口進行OPC客戶程序的開發,希望能達到拋磚引玉的效果。

熱點新聞

推薦產品

x
  • 在線反饋
1.我有以下需求:



2.詳細的需求:
姓名:
單位:
電話:
郵件:
主站蜘蛛池模板: 在线观看一区二区三区视频 | 男人的天堂2018 | 日韩加勒比在线 | 一区二区日韩欧美 | 免费观看久久 | 国产一级特黄a大片99 | 久色成人| 9cao在线精品免费 | 久久亚洲精品中文字幕二区 | aaa色| 另类视频一区 | 二区三区在线观看 | www.91免费视频 | 国产小网站| 在线视频久| 久久久国产精品免费看 | 久久只有精品视频 | 国产三级做爰高清在线 | 七七国产福利在线二区 | 免费观看欧美精品成人毛片 | 欧美日韩精品国产一区在线 | 国产精品久久久天天影视香蕉 | 欧美性色高清生活片 | 欧美一级毛片黄 | 国产在线精品观看 | 精品久久久久久影院免费 | 免费老外的毛片清高 | 亚洲国产精品线在线观看 | 麻豆日韩 | 日本乱理伦片在线观看网址 | 日本三级香港三级人妇99视 | 怡红院免费va男人的天堂 | 亚洲国产系列 | 国产在线一二三区 | 国产a级一级久久毛片 | 午夜精品视频在线观看美女 | 欧美性视频xxxxxxxx | 噜噜噜狠狠夜夜躁精品 | 欧美成人亚洲欧美成人 | 国产欧美综合精品一区二区 | 泰国一级毛片aaa下面毛多 |