a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > C > 正文

Windows系統下使用C語言編寫單線程的文件備份程序

2020-01-26 14:43:10
字體:
來源:轉載
供稿:網友

寫在最前方

源路徑:即 From-Path,你準備要備份的資料
目的路徑: 即 To-Path,你準備要存儲備份的資料的地方
稍微回想一下,上一次寫的代碼,本次的任務是遍歷目錄及其子目錄,那么這回要干的就是將上次遍歷過的數據,挪一下窩,到我們想要他們去的位置。
這涉及到兩個操作,遍歷 和 拷貝,前一個動作我們在上一回已經實現了,只需做小小的改動,就能夠使用。后一個動作也是需要靠 Windows API來完成,至于哪些,稍后再提。
現在先讓我們完成一個魔法,3, 2, 1!:

 do{   puts("-------------------------------------------------");   fprintf(stdout, "The Default Path is : %s /n", DEFAULT_TO_PATH);   fprintf(stdout, "Now The Path is   : %s /n", get_backup_topath());   puts("-------------------------------------------------");   puts("That is a System Back Up Software for Windows! ");   puts("List of the software function : ");   puts("1. Back Up ");   puts("2. Set Back Up TO-PATH ");   puts("3. Show TO-PATH History");   puts("4. Read Me ");   puts("5. Exit ");   puts("-------------------------------------------------");

對界面稍微有了一些改動。

新增了第三行和第四行的 系統默認目的路徑和當前使用的目的路徑。

新增了倒數第四行的查看目的路徑歷史紀錄的功能。

在main函數外頭需要 extern DEFAULT_TO_PATH;因為引用了setPath.c里的一個全局變量。

寫在中間

我們曾經提到要讓函數的功能更加清晰,為了達到這個目的,應該把可能用到的一些原生庫函數包裹一下,讓可能發生的錯誤盡量掌握在我們自己的手里

安全函數

新建 safeFunc.h safeFunc.c
考慮一下我們需要包裹的函數: malloc, free, fopen 三個庫函數。

為了不讓后方的多線程實現產生更多的以后,不單獨使用全局錯誤輸出。
讓我來將他們實現一下
我不會省略一些看似不必要的東西,例如注釋,而是完整的呈現出來,如果覺得篇幅過長,可以選擇跳躍的閱讀。
魔法來了,3, 2, 1!

  

  #include <stdio.h> /* size_t */   #include <stdlib.h>   #include <setjmp.h>   #define TRY_TIMES 3   typedef struct _input_para{     char * file; /* 待打開或創建的文件名 */     char * mode; /* 打開的模式 */   }params;   jmp_buf malc_jmp; /*Malloc_s*/   jmp_buf fopn_jmp; /*Fopen*/   /**    * @version 1.0 2015/10/01    * @author wushengixin    * @param  ... 參看結構體說明         可傳入任意的個數的,形式為 .file = "xxx", .mode = "x" 的參數    * function 用于使用默認參數,并調用函數 Fopen 進行打開操作    */   #define Fopen_s(...) Fopen((params){.file = NULL, .mode = "r", __VA_ARGS__})   FILE* Fopen(const params file_open);   /**    * @version 1.0 2015/10/01    * @author wushengxin    * param  sizes 輸入需要分配的大小    * function 用于隱藏一些對錯誤的處理,并調用malloc庫函數分配空間    */   void * Malloc_s(size_t sizes);   /**    * @version 1.0 2015/10/01    * @author wushengxin    * @param  input 外部傳入的等待釋放的指針    * function 用于隱藏一些對錯誤的處理,并調用free庫函數進行釋放指針    */   void Free_s(void * input);

里面用到了一些新的特性,如果使用 GCC/Clang作為編譯器的,記得要開啟-std=c11 支持。

  這幾個函數就不再詳細解釋,而是簡略說幾個,接下來放上實現代碼:

   FILE* Fopen(const params file_open)   {     int times = 0;     FILE* ret_p = NULL;     if (file_open.file == NULL)     {       fputs("The File Name is EMPTY! Comfirm it and Try Again", stderr);       return ret_p;     }     setjmp(fopn_jmp); /* fopn_jmp To there */     ret_p = fopen(file_open.file, file_open.mode);     if (ret_p == NULL)     {       if (times++ < TRY_TIMES)        longjmp(fopn_jmp, 0); /* fopn_jmp From here */       fprintf(stderr, "The File : %s Open with Mode (%s) Fail!/n", file_open.file, file_open.mode);     }     return ret_p;   }   void * Malloc_s(size_t sizes)   {     int times = 0;     void * ret_p = NULL;     if (sizes == 0)       return NULL;     setjmp(malc_jmp); /* malc_jmp To There */     ret_p = malloc(sizes);     if (ret_p == NULL)     {       if (times++ < TRY_TIMES) /* malc_jmp From Here */         longjmp(malc_jmp, 0);       fputs("Allocate Memory Fail!", stderr);     }     return ret_p;   }   void Free_s(void * input)   {     if (input == NULL)     {   #if !defined(NOT_DEBUG_AT_ALL)       fputs("Sent A NULL pointer to the Free_s Function!", stderr);   #endif       return;     }     free(input);     input = NULL;   }

  第一個函數是用外部定義的宏 `Fopen_s`啟動它,這里沒有實現隱藏它。

  最后一個函數中使用了預處理的機制,如果在頭文件中定義了 `#define NOT_DEBUG_AT_ALL`,這個輸出將不在出現
安全函數已經撰寫完成,接下來就是干正事了

setPath.h

我們首先要將程序里保存上默認的目的路徑,首先想到用常量#define ...
其次應該要確保當前目的路徑不被其他非法的渠道訪問,那就應該用一個static 字符數組存儲。
接下來就是要提供一個函數當作接口(這里用了接口這個術語不知道合不合適),來獲取當前實際在使用的目的路徑 get_backup_topath。
這里還需要將之前實現過的 repl_str ,再次實現一次,因為之前的顯示功能只是測試,并不會實際應用到程序當中。
完成這兩個功能函數以后,再去考慮實現怎么樣設置路徑,存儲路徑,以及使用文件流操作來緩存歷史目的路徑

   #include "safeFunc.h"   #define SELF_LOAD_DEFAULT_PATH "C:/"   #define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */   #define LARGEST_PATH_NAME 32767 /* 路徑的最大限制 */   /*    * @version 1.0 2015/10/02    * @author  wushengxin    * @function 用于返回當前使用的目的路徑    */   const char * get_backup_topath();   /**   * @version 1.0 2015/09/28   * @author wushengxin   * @param  src 外部傳入的,用于調整   * @function 用于替換路徑中的 / 為 / 的   */   void repl_str(char * src);

  對應的實現中,會定義一個靜態的字符數組,且在頭文件中能夠看見,很多是在`showFiles`里定義過的。

  定義過的函數,例如 `repl_str`需要把`showFiles.c`中的**實現**,使用`#if 0 ... #endif` 進行注釋掉,不然會發生重定義的錯誤。
setPath.c

   #include "setPath.h"   static char to_path_buf[LARGEST_PATH_NAME] = SELF_LOAD_DEFAULT_PATH;   const char * DEFAULT_TO_PATH = SELF_LOAD_DEFAULT_PATH;   const int LARGEST_PATH = LARGEST_PATH_NAME;   const char * get_backup_topath()   {     return to_path_buf;   }   void repl_str(char * src)   {     size_t length = strlen(src);     for (size_t i = 0; i <= length; ++i)     {       if (src[i] == '/')         src[i] = '//';     }     return;   }

有了上面的代碼,主界面就再次能夠無誤運行了,那么剩下的就是實現,設置目的路徑,存儲目的路徑到本地,顯示目的路徑,分別對應主界面的2, 3。
怎么實現比較好,再開始之前,分析一下會遇到的情況:
我們在得到目的路徑之后,會將其拷貝給默認路徑 to_path_buf,并且將其存儲到本地緩存文件中,以便下次程序開始時可以直接使用上一次的路徑
還可以使用另一個文件存儲所有用過的歷史路徑,包含時間信息。
那么這就要求我們首先實現存儲目的路徑的功能,其次再實現設置目的路徑的功能,最后實現顯示目的路徑的功能
注:兩個看似無用的全局變量(const)是為了其他文件的可見性而設立的,且相對于#define能夠省一些無足輕重的空間。

存儲目的路徑 store_hist_path

setPath.h

    #include <time.h>    /**     * @version 1.0 2015/10/02     * @version wushengxin     * @param  path 需要存儲的路徑     * @function 用于存儲路徑到本地文件 "show_hist" 和 "use_hist"      */    void store_hist_path(const char * path);setPath.c    void store_hist_path(const char * path)    {      time_t ctimes;       time(&ctimes); /* 獲取時間 */      FILE* input_use = Fopen_s(.file = "LastPath.conf", .mode = "w"); /* 每次寫入覆蓋 */      FILE* input_show = Fopen_s(.file = "PathHistory.txt", .mode = "a");      if (!input_show || !input_use)      {    #if !defined(NOT_DEBUG_AT_ALL)        fputs("Open/Create the File Fail!", stderr);    #endif        return;      }      fprintf(input_use, "%s/n", path); /* 寫入 */      fprintf(input_show, "%s %s", path, ctime(&ctimes));      fclose(input_show);      fclose(input_use);      return;    }

    `time`和`ctime` 函數的使用網路上的介紹更加全面,這里不做解釋。

    完成了存儲的函數之后,便是實現從鍵盤讀取并且設置默認路徑
設置目的路徑 set_enter_path

在此處需要停下來在此思考一下,如果用戶輸入了錯誤的路徑(無效路徑或者惡意路徑),也應該被讀取嗎?所以應該增加一個檢查,用于確認路徑的有效性。
setPath.h

    #include <string.h>    #include <io.h> /* _access */    enum {NOT_EXIST = 0, EXIST = 1};    /**     * @version 1.0 2015/10/02     * @author  wushengxin     * @function 用于讀取從鍵盤輸入的路徑并將之設置為默認路徑,并存儲。     */    void set_enter_path();    /**     * @version 1.0 2015/10/02     * @author  wushengxin     * @param  path 用于檢查的路徑     * @function 用于檢查用戶輸入的路徑是否是有效的     */    int is_valid_path(const char * path);

setPath.c

    int is_valid_path(const char * path)    {/* _access 后方有解釋 */      if (_access(path, 0) == 0) /* 是否存在 */        return EXIST;      else        return NOT_EXIST;    }    void set_enter_path()    {      int intJudge = 0; /* 用來判斷是否決定完成輸入 */      char tmpBuf[LARGEST_PATH_NAME]; /** 臨時緩沖區 **/      while (1)      {        printf("Enter The Path You want!/n");        fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin); /* 獲取輸入的路徑 */        sscanf(tmpBuf, "%s", to_path_buf);        if (is_valid_path(to_path_buf) == NOT_EXIST)        {          fprintf(stderr, "Your Enter is Empty, So Load the Default Path/n");          fprintf(stderr, "%s /n", SELF_LOAD_DEFAULT_PATH);          strcpy(to_path_buf, SELF_LOAD_DEFAULT_PATH);        }        fprintf(stdout, "Your Enter is /" %s /" ?(1 for yes, 0 for no) /n", to_path_buf);        fgets(tmpBuf, LARGEST_PATH_NAME*sizeof(char), stdin);        sscanf(tmpBuf, "%d", &intJudge); /* 獲取判斷數的輸入 */        if (intJudge != 0)        {          if (to_path_buf[strlen(to_path_buf) - 1] != '/')            strcat(to_path_buf, "/");/* 如果最后一個字符不是'/',則添加,這里沒考慮是否越界 */          store_hist_path(to_path_buf);          break;        } /* if(intJudge) */      }/* while (1) */      return;    }/* set_enter_path */

    這一組函數的功能稍微復雜,大體來說便是 `讀取路徑輸入->檢查路徑有效性->讀取判斷數->是否結束循環`

    其中`_access` 函數有些淵源,因為這個函數被大家所熟知的是這個形式 `access`,但由于這個形式是 **POSIX** 標準,故 **Windows** 將其實現為`_access`,用法上還是一樣的,就是名字不同而已。
顯示歷史路徑 show_hist_path

setPath.h

    /**     * @version 1.0 2015/10/02     * author  wushengxin     * function 用于在窗口顯示所有的歷史路徑     */    void show_hist_path();

setPath.c

    void show_hist_path()    {      system("cls");      char outBufName[LARGEST_PATH_NAME] = {'/0'};      FILE* reading = Fopen_s(.file = "PathHistory.txt", .mode = "r");      if (!reading)      return;      for (int i = 1; i <= 10 && (!feof(reading)); ++i)        {        fgets(outBufName, LARGEST_PATH_NAME*sizeof(char), reading);        fprintf(stdout, "%2d. %s", i, outBufName);      }      fclose(reading);      system("pause");      return;    }

剩下最后一個收尾工作

初始化路徑
每次程序啟動的時候,我們都會讀取本地文件,獲取上一次程序使用的最后一個路徑,作為當前使用的目的路徑
初始化目的路徑 init_path

setPath.h

    /**     * @versions 1.0 2015/10/02     * @author  wushengxin     * @function 用于每次程序啟動時初始化目的路徑     */    void init_path();

setPath.c

    void init_path()    {      int len = 0;      char last_path[LARGEST_PATH_NAME] = { '/0' };      FILE* hist_file = Fopen_s(.file = "LastPath.conf", .mode = "r");      if (!hist_file) /* 打開失敗則不初始化 */        return;      fgets(last_path, LARGEST_PATH_NAME, hist_file);      len = strlen(last_path);      if (len > 1)      {        last_path[len - 1] = '/0'; /* 消除一個多余的 ‘/n' */        strcpy(to_path_buf, last_path);      }      return;    }

    這樣就大功告成了,對于這個函數中的后`8`行代碼,沒使用慣用的`fgets 配合 sscanf` 是因為如果這么干的話,需要搭配一個`memset`函數清零,后面會有解釋。

對于memset的解釋
這個函數對于大的內存塊的初始化實際上是很慢的,當然我們這個30KB左右大概的內存可能影響還沒有那么大,但是上兆以后,調用memset就是一種性能問題了,很多情況下,編譯器在開啟高優化等級之后會自動幫你取消memset的隱式調用
什么隱式調用,例如 init_path的第二行代碼,聲明并且用花括號初始化這個數組的時候,就會調用隱式memset。

寫在中間

上面完成了界面的大部分功能,剩下的便是備份這個主要功能。
在完成備份之前,首先想想要如何構造這個備份模型

既然是備份,如果不想擴展為多線程的形式,參考第一次寫的遍歷函數(show_structure)直接找到文件便調用Windows API(稍后介紹)進行復制即可,不需要講待備份的文件路徑保存下來。
如果要考慮多線程擴展,我們就需要從長計議。
對于一個備份模型,最好的莫過于使用一個隊列,依舊實行的是遍歷模式,但是將找到的文件路徑保存,并放入一個先進先出的隊列中,這樣我們就能夠保證在擴展成多線程的時候,可以有一個很清晰的模型參考。
那么現在的任務就是實現這個用于備份的隊列模型。
隊列模型

  • 應該有一個容器空間:用于存放路徑
  • 有隊首隊尾標志
  • O(1)復雜度的檢查隊列是否為空的接口或標志
  • O(1)復雜度的返回容器容量的接口或標志,容器容量應該固定不變

使用一些面向對象的黑魔法,保存一些操作函數防止代碼混亂。

  • 初始化函數
  • 釋放函數
  • 彈出操作函數
  • 壓入操作函數
  • 隊列實體

考慮到要存儲的是字符串,并且由于Windows API的參數需求,對于一個文件,我們需要存儲的路徑有兩個<源路徑,目的路徑>,對此應該再使用一個路徑模型結構體包裹他們,則空間的類型就相應改變一下
新建 Queue.h Queue.c

Queue.h

  typedef struct _vector_queue queue;  typedef struct _combine combine;      |  返回值  | | 函數類型名 ||  參數類型  |  typedef int       (*fpPushBack)(queue * __restrict, const char * __restrict, const char * __restrict);  typedef const combine * (*fpPopFront)(queue *);  typedef void      (*fpDelete)(queue *);

五個typedef不知道有沒有眼前一懵。,希望能夠很好的理解

前兩個是結構體的聲明,分別對應著 隊列模型 和 路徑模型。

后兩個是函數指針,作用是放在結構體里,使C語言的結構體也能夠擁有一些簡單的面向對象功能,例如成員函數功能,原理就是可以給這些函數指針類型的變量賦值。稍后例子更加明顯。試著解讀一下,很簡單的。

  struct _combine{  char * src_from_path; /* 源路徑 */  char * dst_to_path;  /* 目的路徑 */  };  struct _vector_queue{    combine **   path_contain; /* 存儲路徑的容器主體 */    unsigned int  rear;     /* 隊尾坐標 */    unsigned int  front;    /* 隊首坐標 */    int       empty;    /* 是否為空 */    unsigned int  capcity;   /* 容器的容量 */    fpPushBack   PushBack; /* 將元素壓入隊尾 */    fpPopFront   PopFront; /* 將隊首出隊 */    fpDelete    Delete;  /* 析構釋放整個隊列空間 */  };  /**   * @version 1.0 2015/10/03   * @author  wushengxin   * @param  object 外部傳入的對象指針,相當于 this   * @function 初始化隊列模型,建立隊列實體,分配空間,以及設置屬性。   */  int newQueue(queue* object);

可以看到,上方的函數指針類型,被用在了結構體內,此處少了一個初始化函數,是因為不打算把他當作成員函數(借用面向對象術語)

在使用的時候可以直接obj_name.PushBack(..., ..., ...);

更詳細的可以看后面的實現部分。成為成員函數的三個函數,將被實現為 static 函數,不被外界訪問。

queue.c

 int newQueue(queue * object) {   queue*   loc_que = object;   combine**  loc_arr = NULL;   loc_arr = (combine**)Malloc_s(CAPCITY * sizeof(combine*));   if (!loc_arr)     return 1;   loc_que->capcity = CAPCITY; /* 容量 */   loc_que->front = 0;    /* 隊首 */   loc_que->rear = 0;    /* 隊尾 */   loc_que->path_contain = loc_arr; /* 將分配好的空間,放進對象中 */   loc_que->PushBack = push_back;   loc_que->PopFront = pop_front;   loc_que->Delete  = del_queue;   return 0; }

在初始化函數中,可以看到,設置了隊首隊尾以及容量,分配了容器空間,配置了成員函數。

最后三句配置函數的語句中,push_back, pop_front, del_queue在后方以static 函數實現。

但是由于沒有聲明,所以切記要將三個static函數的實現放在newQueue的前方

 /**  * @version 1.0 2015/10/03  * @author  wushengxin   * @param  object 外部傳入的對象指針 相當于 this  * @function 釋放整個隊列實體的空間  */ static void del_queue(queue * object) {   Free_s(object->path_contain);   return; } /**  * @version 1.0 2015/10/03  * @author  wushengxin  * @param  object 外部傳入的對象指針 相當于 this        src  源路徑        dst  目的路徑  * @function 將外部傳入的<源路徑,目的路徑> 存入隊列中  */ static int push_back(queue * __restrict object, const char * __restrict src, const char * __restrict dst) {   int times = 0;   char*  loc_src = NULL; /* 本地變量,盡量利用寄存器以及緩存 */   char*  loc_dst = NULL;   combine* loc_com = NULL;   queue*  loc_que = object;   size_t  len_src = strlen(src); /* 獲取路徑長度 */   size_t  len_dst = strlen(dst);   size_t  rear = loc_que->rear;  /*獲取隊尾*/   size_t  front = loc_que->front; /*獲取隊首*/   loc_src = Malloc_s(len_src + 1); /* 分配空間 */   if (!loc_src)     return 1;   loc_dst = Malloc_s(len_dst + 1);   if (!loc_dst)     return 2;   strcpy(loc_src, src);   strcpy(loc_dst, dst);   loc_com = Malloc_s(sizeof(combine));   if (!loc_com)     return 3;   loc_com->dst_to_path = loc_dst;    loc_com->src_from_path = loc_src;   loc_que->path_contain[rear++] = loc_com; /* 將本地路徑加入實體 */   loc_que->rear = (rear % CAPCITY);   /* 用數組實現循環隊列的步驟 */   if (loc_que->rear == loc_que->front)      loc_que->empty = 0;   return 0; } /**  * @version 1.0 2015/10/03  * @author  wushengxin  * @param  object 外部傳入的對象指針  */ static const combine * pop_front(queue* object) {   size_t  loc_front = object->front;          /*獲取當前隊首*/   combine* loc_com  = object->path_contain[loc_front]; /*獲取當前文件名*/   object->path_contain[loc_front] = NULL;   /*出隊操作*/   object->front = ((object->front) + 1) % 20; /*完成出隊*/   if (object->front == object->rear)     object->empty = 1;   else     object->empty = 0;   return loc_com; }

一個一個的說這些函數

del_queue:釋放函數,直接調用Free_s

push_back:壓入函數,將外部傳入的兩個原始的沒有組成的路徑字符串,組合成一個combine,并壓入路徑,每次都判斷并置是否為空標志位,實際上這個函數中有累贅代碼的嫌疑,應該再分出一個函數,專門用來分配三個空間,防止這個函數過長(接近40行)

pop_front:彈出函數,將隊列的隊首combine彈出,用于復制,但是這里有一個隱患,就是要將釋放的工作交給外者,如果疏忽大意的話,隱患就是內存泄漏。

沒有特地的提供一個接口,用來判斷是否為空,因為當編譯器一優化,也會將這種接口給優化成直接使用成員的形式,某種形式上的內聯。

隊列模型設計完畢,可以開始設計備份模型
備份模型可以回想一下之前的遍歷函數,大體的結構一樣,只是此處為了擴展成多線程,需要添加一些多線程的調用函數,以及為了規格化,需要添加一個二級界面
先設計一下二級界面

二級界面

思考一下,這個界面要做什么
選擇是否開始備份
并且源路徑需要在此處輸入
返回上一級
新建 backup.h backup.c 文件
在主界面選擇 1 以后就會調用二級界面的函數
列出二級界面的選項
1 Start Back up
2 Back To last level
backup.h

  /**   * @version 1.0 2015/10/03   * @author  wushengxin   * function 顯示二級界面   */  void sec_main_windows();backup.c  void sec_main_windows()  {    char tmpBuf[256];    int selects;    do{      setjmp(select_jmp);      system("cls");      puts("-------------------1. Back Up------------------ ");      puts(" For This Select, You can choose Two Options: ");      puts(" 1. Start Back up (The Directory Path That You Enter LATER) ");      puts(" 2. Back To last level ");      puts("----------------------------------------------- ");      fprintf(stdout, "Enter Your Selection: ");      fgets(tmpBuf, 256, stdin);      sscanf(tmpBuf, "%d", &selects);      if (selects != 1 && selects != 2 )      {        fprintf(stdout, "/n Your Select /" %s /" is Invalid!/n Try Again /n", tmpBuf);        longjmp(select_jmp, 1);      }      switch (selects)      {        jmp_buf enter_path_jmp;       case 1:      {        char tmpBuf[LARGEST_PATH], tmpPath[LARGEST_PATH]; /* 使用棧分配空間,因為只用分配一次 */        setjmp(enter_path_jmp);     /* enter jump to there */        puts(" Enter the Full Path You want to BackUp(e.g: C:/Programing/)");        fprintf(stdout, " Or Enter q to back to select/nYour Enter : ");        fgets(tmpBuf, LARGEST_PATH, stdin);        sscanf(tmpBuf, "%s", tmpPath);        if (_access(tmpPath, 0) != 0)  /*檢查路徑是否存在,有效*/        {          if (tmpPath[0] == 'q' || tmpPath[0] == 'Q')             longjmp(select_jmp, 0); /* 回到可以選擇返回的界面 */          fprintf(stderr, "The Path You Enter is Not Exit! /n Try Again : ");          longjmp(enter_path_jmp, 0); /* enter jump from here */        }      }        break;      case 2:        return;      default:        break;      }/* switch */    } while (1);    return;  }

這個函數只說幾點,首先是`switch`的`case 1`,之所以用**花括號**包裹起來的原因是,這樣才能在里面定義**本地變量**,直接在冒號后面定義是**編譯錯誤**,這個特性可能比較少用,這里提一下,前面也有說過。
寫在最后方

剩下的就是編寫主要的功能函數和線程調用函數了。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表

圖片精選

主站蜘蛛池模板: 亚洲精品成人久久久 | 成人激情视频在线免费观看 | 国产在线观看91一区二区三区 | 久久久久综合 | 亚洲精品久久久一区二区三区 | 天天插天天操天天干 | 欧美a级成人淫片免费看 | 日韩视频区 | 97成人在线 | 日韩在线精品强乱中文字幕 | 黄色污污视频在线观看 | 在线视频一区二区三区 | 亚洲88| 亚洲国产一区二区在线 | 特级理论片| 日本中文字幕在线播放 | 一区二区三区精品视频 | 亚洲欧洲一区二区 | 在线视频这里只有精品 | 久久国产精品免费一区二区三区 | 欧美视频在线播放 | 国产精品精品视频一区二区三区 | 国产成人精品一区二区三区四区 | 日韩一区在线播放 | 在线激情视频 | 99久久婷婷国产精品综合 | 国产精品久久久久久吹潮 | 黄色在线免费 | 亚洲欧美在线一区 | 天天摸夜夜操 | 操操网站 | 久久久精品欧美一区二区免费 | 看亚洲a级一级毛片 | 国产一区 日韩 | 国产精品一区在线观看 | 久久久久久久久久久免费 | 成人不卡 | 国产999精品久久久久久 | 日韩色在线 | 国产精品成人一区二区 | 露娜同人18av黄漫网站 |