在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 0x0C-開始行動
0x0E-單線程備份(下)
0x11-套接字編程-1
0x05-C語言指針:(Volume-1)
0x13-套接字編程-HTTP服務(wù)器(1)
0x0C-開始行動
C 語言進(jìn)階
第一部分
0x05-C語言指針(Volume-2)
0x08-C語言效率(下)
0x07-C語言效率(上)
0x04 C代碼規(guī)范
0x0F-多線程備份
0x05-C語言變量
第四部分
0x16-套接字編程-HTTP服務(wù)器(4)
0x0D-單線程備份(上)
總結(jié)
0x01-C語言序言
0x15-套接字編程-HTTP服務(wù)器(3)
0x14-套接字編程-HTTP服務(wù)器(2)
0x17-套接字編程-HTTP服務(wù)器(5)
第三部分
我的C語言
0x06-C語言預(yù)處理器
0x09-未曾領(lǐng)略的新風(fēng)景
0x0A-C線程和Glib的視角
第二部分
0x10-網(wǎng)絡(luò)的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語言錯誤處理

0x0C-開始行動

0x0C-開始行動

寫在最前方

  • 對于線程的概念以及意義,我說一些
    • 現(xiàn)在我只說,一個多線程的技術(shù)對于C語言程序而言就像,原本只有你一個人在干活,現(xiàn)在突然有許多人愿意追隨你,變成了以你為核心的多人合作模式,共同完成同一個任務(wù)。
    • 導(dǎo)致的結(jié)果就是,這個任務(wù)被切分成多個模塊,有可能很多人一起做同一個模塊,有可能某些模塊只有一個人在做,這就帶來了什么問題呢?
    • "我" 對這個任務(wù)的掌控力度變低了,試想本來就是我一個人在開發(fā)這個程序,程序的每部分都是我一個字符一個字符敲上去的,自然了解程序的每個部分。但是現(xiàn)在,突然多了一些人來和你一起完成這個任務(wù),必然導(dǎo)致有一部分程序代碼不是由你親自寫下去的,雖然你指示他們怎么做,做出什么樣的功能。
    • 但是畢竟不是你親自寫的,所以他們在協(xié)調(diào)工作的時候,很大可能性會出現(xiàn)問題,這個問題在多線程變成里面就是資源爭奪問題,也就是同一個內(nèi)存塊, 在同一時刻被多于一個的線程訪問,那么該如何處理呢?
    • C語言到現(xiàn)在已經(jīng)支持多線程(然而零零散散的,雖然C11標(biāo)準(zhǔn)支持多線程庫)了,并沒有辦法實際用在編程里,我一般使用 各平臺的 API 或者 Glib 進(jìn)行多線程編程,前者是不需要配置環(huán)境直接上手,缺點(diǎn)就是無法跨平臺(萬惡的Windows)。后者則是需要配置環(huán)境,且這個庫說實話的確很臃腫,對于我們即將寫的這個備份程序有些殺雞用牛刀。
  • 好吧,如果硬要說的官方一點(diǎn)那就是:

    • 應(yīng)用程序,進(jìn)程,線程的區(qū)別:
    • 應(yīng)用程序是一組數(shù)據(jù)和指令。
    • 多進(jìn)程就相當(dāng)于啟動了多個應(yīng)用程序,彼此之間獨(dú)立(虛擬內(nèi)存)
    • 進(jìn)程就是應(yīng)用程序運(yùn)行起來的名稱,兩者的區(qū)別在于進(jìn)程是有狀態(tài)的,即它會變。
    • 線程和進(jìn)程十分相似,都有狀態(tài),但是它的狀態(tài)比進(jìn)程少,并且線程之間可以十分輕易的共享數(shù)據(jù),而這點(diǎn)是多進(jìn)程無法比擬的(進(jìn)程間共享數(shù)據(jù)開銷極大)。
    • 狀態(tài)指的就是程序運(yùn)行起來產(chǎn)生的一些值,因為是運(yùn)行后產(chǎn)生的所以形象的叫他們狀態(tài),例如寄存器里的值,棧(而不是堆,線程并沒有自己的堆)的值
  • 看完上面的解釋,就準(zhǔn)備開始著手寫程序了

寫在中間

  • 首先我使用的是 Visual Studio 2013作為編譯器
  • 創(chuàng)建 Win32控制臺應(yīng)用程序
  • 記得創(chuàng)建C源文件
  • 如果不小心點(diǎn)成C++也沒事,改成.c就行。
  1. 創(chuàng)建一個入口源文件Entery.c,用于放置main函數(shù)

          #include <stdio.h>
          #include <stdlib.h>
    
          int main(int argc, char* argv[])
          {
              system("pause"); /* 因為Windows的命令行命令對大小寫不敏感,所以命令無所謂大小寫,從環(huán)境變量的配置就能知道這一點(diǎn) */
              return 0;
          }
      因為等一下需要構(gòu)造一個字符顯示界面模擬GUI的功能,故添加`#include <stdlib.h>`
    
      當(dāng)然并不是只因為這個原因。但是現(xiàn)在是為了使用其中的`system()`函數(shù)調(diào)用系統(tǒng)命令。
  • 好,那么接下來...
  • 等一下,要直接寫程序了嗎?程序功能呢?程序結(jié)構(gòu)呢?從哪里開始寫呢?

    • 這些都沒有考慮?。?!
  • 所以,停下來我們好好構(gòu)思構(gòu)思!

    • 問題:
      1. 我們要做什么? : 一個多線程的備份程序
      2. 我們要怎么做? : 這個問題有點(diǎn)大,應(yīng)該拆開
      3. 我們從哪里開始做? : 這個問題比較好回答。
    • 回答:

      1. 我們要實現(xiàn)把某個路徑備份到某個特定路徑
        • 首先要實現(xiàn)路徑的設(shè)置,也就是說由鍵盤輸入路徑
        • 得到了路徑之后,要得到該路徑下所有文件夾和文件信息(在*nix下這倆玩意兒都是一個東西)
        • 行了就這么簡單
      2. 先從輸出所有文件和文件夾的結(jié)構(gòu)開始
    • 問題:

      1. 怎么得到文件和文件夾的信息?
    • 回答:
      1. MSDN放在網(wǎng)上,雖然現(xiàn)在不提供離線版本了,但是也沒有被墻啊。所以不懂的時候就一個字,查API文檔!對于十分基本的API, MSDN甚至?xí)o出示例代碼。恰好,這就是一個。
  • 題外話

    • 其實會寫這個程序沒什么大不了,重要的是學(xué)習(xí)能力,一個自我學(xué)習(xí)的能力,就比如當(dāng)我不知道一個東西,也無從下手的情況下,我應(yīng)該進(jìn)行聯(lián)想,聯(lián)想可能與他相關(guān)的各種方面,并且親自去查,不厭其煩的查,遍地撒網(wǎng),重點(diǎn)撈魚。
  • 首先我們需要實現(xiàn)一個功能,就是遍歷輸出路徑下的所有文件夾和文件

  • 在這之前,我們先設(shè)計一下界面,稍后...3, 2, 1,出現(xiàn)!

      while(1)
      {
          system("cls"); /* 系統(tǒng)的清屏命令 */
          do{
              puts("-------------------------------------------------");
              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. Read Me ");
              puts("4. Exit ");
              puts("-------------------------------------------------");
    
              puts("Your Select: ");
              fscanf(stdin, "%d", &select);
              getchar(); /* 讀取上方 fscanf 留在流里面的換行符 '\n' */
              }while ((select < 1) || (select > 4)); /* 如果選擇無效 */
              system("cls");
    
              switch(select)
              {
              case 1 :
                  break;
              case 2 :
                  break;
              case 3 :
                  break;
              case 4 :
                  exit(0); /* 退出程序 */
              default :
                  break;
              }/** switch(select) **/
          }/** while(1) **/
          system("pause");
          return 0;

    突然出現(xiàn)的這一段代碼就是設(shè)計的界面,其實很簡單,看看就懂了,不再多說。

    英文莫怪。

  • 緊接著,我們來實現(xiàn)第一個功能,顯示結(jié)構(gòu),讓我們吧這個功能函數(shù)叫做show_structure

    • 新建頭文件showFiles.h

          /*頭文件包裹一定要切記*/
          #ifndef INCLUDE_SHOWFILES_H
          #define INCLUDE_SHOWFILES_H
          /* 代碼寫在里面,這樣就不會發(fā)生重定義,也能節(jié)省資源 */
          #endif
    • 新建源文件showFiles.c

      • 記得包含頭文件#include <showFiles.h>
    • showFiles.h 稍后進(jìn)行解釋

          #include <stdio.h>
          #include <stdlib.h>
          #include <Windows.h> /* Windows API */
          #include <string.h>  /* 字符串函數(shù) */
          #include <tchar.h>   /* _ftprintf */
          #include <setjmp.h>  /* setjmp, longjmp */
      
          #define TRY_TIMES 3  /* 重新嘗試獲取的最大次數(shù) */
          #define MIN_PATH_NAME _MAX_PATH /* 最小的限制 */
          #define LARGEST_PATH_NAME 32767 /* 路徑的最大限制 */
      
          /* 我們需要在這里面包含函數(shù)的聲明 */
          /** 加上文檔注釋,不太喜歡死板的硬套,選擇自己覺得重要的信息記錄吧
           * @version 1.0 2015/09/28
           * @author  wushengxin
           * @param   from_dir_name 源目錄,即用于掃描的目錄
                      depth         遞歸的深度,用于在屏幕上格式化輸出,每次增加 4
           * @function 用于輸出顯示目錄結(jié)構(gòu)的,可以改變輸出流
           */
          void show_structure(const char * from_dir_name, int depth);

      題外話,在Visual Studio中,會強(qiáng)制要求你使用他們編寫的安全函數(shù),例如fscanf_s,如果你不想用的話,那就將它關(guān)閉吧,具體怎么操作,就當(dāng)是一個小問題留給你自己。

    • showFiles.c 先不寫太多,這里比較重要的是寫法

          /* 首先是需要的一系列變量 */
          int times = 0; /* 用來配合 setjmp和longjmp重新獲取句柄HANDLE的 */
          /** 操作時獲取文件夾,文件信息的的必要變量 **/
          HANDLE file_handle;    
          WIN32_FIND_DATAA file_data;
          LARGE_INTEGER file_size;
      `file_handle` : 文件的句柄,后期操作的主要對象
      
      `file_data`  : 文件的信息,各種屬性
      
      `file_size`  : 文件的大小有可能非常大,需要使用特定的結(jié)構(gòu)體保存
  • 到這里我們停下來,因為下一步我們要去實現(xiàn)獲取路徑的操作。

    • 問題:
      1. 我們要怎么樣獲取路徑
      2. 我們獲取到的路徑要怎么存儲
      3. 存儲的路徑要符合什么格式
    • 回答:

      1. 有兩個路徑:備份的來源路徑,備份的目的路徑。前者用鍵盤輸入,后者在程序內(nèi)部首先指定一個。
      2. 這里有兩種方案:用系統(tǒng)棧存,用堆存
        • 前者是方便的內(nèi)存管理,完全不用程序員操心,但是棧的大小比較小,一般只有幾兆而已,這也是為什么遞歸容易爆棧的原因。速度較快
        • 后者是近似巨大的內(nèi)存上限,對于32位系統(tǒng)的Windows應(yīng)用程序而言,可以有2~3GB的分配空間(4GT機(jī)制),64位就更為可怕了(Windows8 最大有512GB, Windows7 最大有 192GB,服務(wù)器系列大概是1~2TB)。速度較慢
        • 不熟練的程序員應(yīng)該盡可能選擇前者。
        • 這里采用后者,前者的代碼也會一并附上。
      3. 對于微軟API處理自己的Windows路徑,一般要求末尾以/或者\結(jié)尾,前者在C語言中不是轉(zhuǎn)義,所以比較好存儲,如果需要使用后者,可以選擇如此\\就行了。

              char dir_path_1[PATH_MAX] = "../";
              char dir_path_2[PATH_MAX] = "..\\";
              /* 兩種效果一致,且占的空間也相同 */
    • 注意
      • 這里有涉及到一個問題,那就是Windows下的路徑限制,在windef.h中定義了一個常量PATH_MAX的值為260,也就是說最大的路徑長路為260字節(jié),但是如果我們的路徑名超過了這個長度怎么辦呢?
      • 這里直接給出了解決辦法,就是添加前綴\\?\,這樣長度限制就增加到了32767
      • 此處不予以實現(xiàn),日后遇見情況的話可以當(dāng)作一個解決的辦法。
  • 解決了上一個關(guān)于路徑的問題,我們就需要考慮一下如何設(shè)計實現(xiàn)這個功能,首先要達(dá)到模塊化的目的,即盡量減少每個函數(shù)的功能。

    • 問題
      • 都需要什么功能?
    • 回答
      • 一個主要的函數(shù)用來遞歸(也可以用循環(huán),循環(huán)的好處就也在于不容易爆棧)
      • 一個用來專門給路徑分配空間的函數(shù)
      • 一個用來釋放分配空間的函數(shù)
      • 一個對輸入的路徑進(jìn)行處理的函數(shù),讓路徑變得規(guī)范
  • showFiles.h 變個魔術(shù) 3, 2, 1,添加了如下代碼

          /** 
           * @version 1.0 2015/09/28
           * @author  wushengxin
           * @param   src 外部傳進(jìn)來的,用于向所分配空間中填充的路徑
           * @function 用于在堆上為存儲的路徑分配空間。
           */
          char * make_path(const char * src);
    
          /*
           * @version 1.0 2015/09/28
           * @author  wushengxin
           * @param   src 外部傳進(jìn)來的,是由 make_path 所分配的空間,將其釋放
           * @function 用于釋放 make_path 分配的內(nèi)存空間
           */
          void rele_path(char * src);
    
          /*
           * @version 1.0 2015/09/28
           * @author  wushengxin
           * @param   src_path 用于 FindFirstFile()
                      src_file 用于添加找到的目錄名,形成新的目錄路徑
           * @function 用于調(diào)整用戶從鍵盤輸入的路徑字符串,使他們變得一致,便于處理
          */          
          void adjust_path(char * __restrict src_path, char * __restrict src_file);
    
          /*
           * @version 1.0 2015/09/28
           * @author  wushengxin
           * @param   src 外部傳入的,用于調(diào)整
           * @function 用于替換路徑中的 / 為 \ 的
           */
          void repl_str(char * src);

    具體功能在文檔里已經(jīng)寫的很清楚了,唯一要解釋的就是最后兩個函數(shù),本來是一體的,后來被我拆開成了兩個函數(shù),為了也是功能更加清晰

    倒數(shù)第二個函數(shù) adjust_path 的作用是將路徑處理成符合 Windows 函數(shù) FindFirstFile要求可以具體看看。

  • showFiles.c 繼續(xù) show_structure 的實現(xiàn)

    • shows_tructure

              size_t length = strlen(from_dir_name);
              char * dir_buf = make_path(from_dir_name);  //路徑
              char * dir_file_buf = make_path(from_dir_name); //文件
              if (dir_buf == NULL || dir_file_buf == NULL)
                  return; /* 如果分配失敗就結(jié)束函數(shù) */
              adjust_path(dir_buf, dir_file_buf); /* 調(diào)整路徑和文件格式到標(biāo)準(zhǔn)格式 */
              repl_str(dir_buf);
              repl_str(dir_file_buf);
      這是調(diào)用 `WINDOWS API` 之前的所有操作,來一一實現(xiàn)他們
      
      首先是分配空間給路徑
    • make_path : 24 lines·

      對于這個函數(shù)的功能便是,為需要存儲的路徑分配空間。
      
                  int times = 0;
                  size_t len_of_src = strlen(src); /* 需要分配的長度 */
                  size_t len_of_dst = MIN_PATH_NAME;
      
                  if (len_of_src > MIN_PATH_NAME - 10) /* \\?\ //* 8個字符 */
                  { /* 這里用了10這個神奇的垃圾數(shù),所以必須做一點(diǎn)注釋,以防忘記 */
                      len_of_dst = LARGEST_PATH_NAME;
                      if (len_of_src > LARGEST_PATH_NAME - 10)
                      { 
                          fprintf(stderr, "The Path Name is larger than 32767, Which is not Support!\n%s", src);
                          return NULL;
                      }
                  }   
                  setjmp(alloc_jmp); /* alloc_jmp to here */
                  char * loc_buf = malloc(len_of_dst + 1);
                  if (loc_buf == NULL)
                  {
                      fprintf(stderr, "ERROR OCCUR When malloc the memory at %s\n Try the %d th times", __LINE__, times+1);
                      if (times++ < TRY_TIMES)
                          longjmp(alloc_jmp, 0); /* alloc_jmp from here */
                      return NULL;
                  }
                  //sprintf(loc_buf, "\\\\?\\%s", src); /* 作為日后的擴(kuò)展 */
                  strcpy(loc_buf, src);
                  return loc_buf; 
      對于 `10` 這個數(shù)的考慮是,至少留出 `8` 個空位給所說的字符,加 `2` 湊整。
      
      對于函數(shù) `malloc` ,在這里沒有進(jìn)行**包裹**,是因為這只是一個預(yù)熱的功能,后期在實現(xiàn)備份的時候,會對它進(jìn)行包裝,也使得錯誤處理的代碼隱藏,讓函數(shù)功能更加清晰。
    • adjust_path : 16 lines

      其次是調(diào)整路徑的函數(shù),功能就是調(diào)整路徑
      
          size_t length = strlen(src_path); /* 兩個參數(shù)的長度在此函數(shù)調(diào)用之前必定一致 */
          if (length == 1) /* 處理情況為,當(dāng)用戶輸入的是根目錄的情況 例如: C */
          {
              strcat(src_file, ":/");
          }
          else if (src_path[length - 1] != '\\' && src_path[length - 1] != '/')
          {
              strcat(src_file, "/");
          }
          else
          {
              src_path[length - 1] = '/';
          }
          strcpy(src_path, src_file);
          strcat(src_path, "/*");
          return;     
      當(dāng)用戶輸入的是一個字符的根目錄,我們要將其處理為 `C:/` 這樣的形式
      
      當(dāng)用戶輸入的是不帶`/`結(jié)尾的,我們需要將其添加上 `/`
      
      當(dāng)用戶輸入以 `\` 結(jié)尾的路徑時,將其替換為 `/`,雖然后方又全部換成了 `\`
      
      將目錄處理為帶 `/*` 結(jié)尾的,以達(dá)到 **API** 的要求
      
      `src_file` 用于將目錄下的子目錄名連接。生成新的目錄。
      `src_path` 用于遞交給 **API** 掃描目錄下的所有文件和文件夾。
    • repl_str : 7 lines

              size_t length = strlen(src);
              for (size_t i = 0; i <= length; ++i)
              {
                  if (src[i] == '/')
                      src[i] = '\\';
              }
              return;
      不再贅述這個函數(shù)的功能
      
      到此處,所有在第一個 **Windows API** 之前調(diào)用的函數(shù)都實現(xiàn)了,接下來要做什么?
  • 當(dāng)然是調(diào)用API函數(shù)啦

    • show_structure

          /* 開始調(diào)用 Windows API 獲取路徑下的文件和文件夾信息 */
          setjmp(get_hd_jmp);
          fileHandle = FindFirstFileA(dir_buf, &fileData);
          if (fileHandle == INVALID_HANDLE_VALUE) /* 如果無法獲取句柄超過上限次數(shù),就退出 */
          {
              fprintf(stderr, "The Handle getting Failure! \n");
              if (times++ < TRY_TIMES)
                  longjmp(get_hd_jmp, 0);
              return;
          }
      對于這一段代碼的解釋,其實核心就是第二句代碼,其中的函數(shù) `FindFirstFileA`需要解釋一下。
      
      在 **Windows API** 文檔 **MSDN** 中介紹的是 `FindFirstFile` ,但是**某些情況下**(定義了UNICODE宏,不知道有沒有記錯),這個官方提供的接口會被定義(`#define`)成 `FindFirstFileW`,如果使用 `char *` 的 **ANSI** 字符串當(dāng)成參數(shù)的話是會獲取句柄失敗的!并且另一個參數(shù)使用的 `file_data` 類型也是 **ANSI** 的 `WIN32_FIND_DATAA`
      
      所以這里**顯式地選擇調(diào)用** `FindFirstFileA` 而不是讓 **Windows** 幫我們選擇。
      
      **接下來我們要做的事情就是,遍歷這個目錄下的所有文件和文件夾,提取出來他們的信息:**
      
          do{
              char * tmp_dir_file_buf = make_path(dir_file_buf);
              if (fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
              { /* 如果是文件夾 */
                  fprintf(stderr, "%*s%s\t<DIR>\t\n", depth, "", fileData.cFileName);
                  if (strcmp(fileData.cFileName, ".") == 0 ||  /* . 和 .. 便是當(dāng)前文件夾和上一級文件夾, 世界上所有系統(tǒng)都一樣 */
                      strcmp(fileData.cFileName, "..") == 0)
                      continue;
                  strcat(tmp_dir_file_buf, fileData.cFileName); /* 將文件名連接到當(dāng)前文件夾路徑之后,形成文件路徑 */
                  show_structure(tmp_dir_file_buf, depth + 4);
              }
              else
              {
                  fileSize.LowPart = fileData.nFileSizeLow;   /* 輸出大小 */
                  fileSize.HighPart = fileData.nFileSizeHigh;
                  fprintf(stderr, "%*s%s    \t%ld bytes\t\n", depth, "", fileData.cFileName, 
                                            fileSize.QuadPart);
              }
              rele_path(tmp_dir_file_buf); /* 這個的實現(xiàn)稍后放出 */
          } while (FindNextFileA(fileHandle, &fileData));
      代碼是仿照 **MSDN** 提供的 [官方例子](https://msdn.microsoft.com/en-us/library/windows/desktop/aa364418(v=vs.85).aspx)改寫的。
      
      其中第五句代碼,用到了前方提到過的格式化輸出的一個不常用的技巧,占位,忘記的可以回去看看在**第一章**
      
      采用的方法是遞歸,循環(huán)的方法留給看者自己實現(xiàn),思路很簡單,用一個隊列或者棧存放所有找到的目錄和文件,依次取出直到?;蛘哧犃袨榭?。
      
      以及最后一段的代碼,用于收尾:
      
              FindClose(fileHandle);
              rele_path(dir_buf);
              rele_path(dir_file_buf);
              return;
    • rele_path : 4 lines

          free(src);
          src = NULL;
          return;
  • 最后在 Entry.cmain 函數(shù)中,在 switchcase 1 標(biāo)簽范圍內(nèi),加上一些獲取和處理輸入的函數(shù) : (因為這里只會使用一次,故采用的是系統(tǒng)棧而不是在堆上分配)

      char tmp[_MAX_PATH];
      ...
      case 1 :
          scanf("%s", tmp);
          printf("Enter : %s\n", tmp);
          getchar();  /* 前方提到過,作用是清理標(biāo)準(zhǔn)輸入流 */
          show_structure(tmp, 0);
          system("pause");
          break;

現(xiàn)在編譯運(yùn)行

  • 成功了!對自己的代碼很有信心,嗯!
  • 中文路徑也是可以的,別怕
  • 對于超過260字符的路徑?jīng)]有測試,大概能猜到是不行的。但是解決方案上方也有提到。
  • 這就是預(yù)熱的函數(shù),比較詳細(xì),后方代碼就不會如此贅述,而是更加簡潔干練的上代碼和解釋

寫在最后面

  • 總結(jié)一個詞,代碼橫陳,但是邏輯還算清晰
  • 只是一個預(yù)熱的作用,正片代碼只是為了提前讓思路更加清晰,且能測試出Windows API的某些潛在缺陷以及要求。
  • 下面將開始真正的進(jìn)入功能的編寫。