深究標準IO的緩存

来源:http://www.cnblogs.com/orlion/archive/2017/01/07/6258691.html
-Advertisement-
Play Games

前言 在最近看了APUE的標準IO部分之後感覺對標準IO的緩存太模糊,沒有搞明白,APUE中關於緩存的部分一筆帶過,沒有深究緩存的實現原理,這樣一本被吹上天的書為什麼不講透徹呢?今天早上爬起來趕緊找了幾篇文章看看,直到發現了這篇博客:http://blog.sina.com.cn/s/blog_65 ...


前言

  在最近看了APUE的標準IO部分之後感覺對標準IO的緩存太模糊,沒有搞明白,APUE中關於緩存的部分一筆帶過,沒有深究緩存的實現原理,這樣一本被吹上天的書為什麼不講透徹呢?今天早上爬起來趕緊找了幾篇文章看看,直到發現了這篇博客:http://blog.sina.com.cn/s/blog_6592a07a0101gar7.html。講的很不錯。

一、IO緩存

  系統調用:只操作系統提供給用戶程式調用的一組介面-------獲得內核提供的服務。

  在實際中程式員使用的通常不是系統調用,而是用戶編程介面API,也稱為系統調用編程介面。它是遵循Posix標準(Portable operation system interface),API函數可能要一個或者幾個系統調用才能完成函數功能,此函數通過c庫(libc)實現,如read,open。   fsync是把內核緩衝刷到磁碟上。   fflush:是把C庫中的緩衝調用write函數寫到磁碟[其實是寫到內核的緩衝區]。     linux對IO文件的操作分為:
  • 不帶緩存:open  read。posix標準,在用戶空間沒有緩衝,在內核空間還是進行了緩存的。數據-----內核緩存區----磁碟。假設內核緩存區長度為100位元組,你調用ssize_t write (int fd,const void * buf,size_t count);寫操作時,設每次寫入count=10位元組,那麼你要調用10次這個函數才能把這個緩存區寫滿,沒寫滿時數據還是在內核緩衝區中,並沒有寫入到磁碟中,內核緩存區滿了之後或者執行了fsync(強制寫入硬碟)之後,才進行實際的IO操作,吧數據寫入磁碟上。
  • 帶緩存區:fopen fwrite fget 等,是c標準庫中定義的。數據-----流緩存區-----內核緩存區----磁碟。假設流緩存區長度為50位元組,內核緩存區100位元組,我們用標準c庫函數fwrite()將數據寫入到這個流緩存中,每次寫10位元組,需要寫5次流緩存區滿後調用write()(或調用fflush()),將數據寫到內核緩存區,直到內核緩存區滿了之後或者執行了fsync(強制寫入硬碟)之後,才進行實際的IO操作,吧數據寫入磁碟上。標準IO操作fwrite()最後還是要掉用無緩存IO操作write。

  以fgetc / fputc 為例,當用戶程式第一次調用fgetc 讀一個位元組時,fgetc 函數可能通過系統調用 進入內核讀1K位元組到I/O緩衝區中,然後返回I/O緩衝區中的第一個位元組給用戶,把讀寫位置指 向I/O緩衝區中的第二個字元,以後用戶再調fgetc ,就直接從I/O緩衝區中讀取,而不需要進內核 了,當用戶把這1K位元組都讀完之後,再次調用fgetc 時,fgetc 函數會再次進入內核讀1K位元組 到I/O緩衝區中。在這個場景中用戶程式、C標準庫和內核之間的關係就像在“Memory Hierarchy”中 CPU、Cache和記憶體之間的關係一樣,C標準庫之所以會從內核預讀一些數據放 在I/O緩衝區中,是希望用戶程式隨後要用到這些數據,C標準庫的I/O緩衝區也在用戶空間,直接 從用戶空間讀取數據比進內核讀數據要快得多。另一方面,用戶程式調用fputc 通常只是寫到I/O緩 沖區中,這樣fputc 函數可以很快地返回,如果I/O緩衝區寫滿了,fputc 就通過系統調用把I/O緩衝 區中的數據傳給內核,內核最終把數據寫回磁碟或設備。有時候用戶程式希望把I/O緩衝區中的數據立刻 傳給內核,讓內核寫回設備或磁碟,這稱為Flush操作,對應的庫函數是fflush,fclose函數在關閉文件 之前也會做Flush操作。

  雖然write 系統調用位於C標準庫I/O緩衝區的底 層,被稱為Unbuffered I/O函數,但在write 的底層也可以分配一個內核I/O緩衝區,所以write 也不一定是直接寫到文件的,也 可能寫到內核I/O緩衝區中,可以使用fsync函數同步至磁碟文件,至於究竟寫到了文件中還是內核緩衝區中對於進程來說是沒有差別 的,如果進程A和進程B打開同一文件,進程A寫到內核I/O緩衝區中的數據從進程B也能讀到,因為內核空間是進程共用的, 而c標準庫的I/O緩衝區則不具有這一特性,因為進程的用戶空間是完全獨立的.

 

  下麵是一個利用buffered I/O讀取數據的例子:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  char buf[5];
  FILE *myfile = stdin;
  fgets(buf, 5, myfile);
  fputs(buf, myfile);
  
  return 0;
}

  buffered I/O中的"buffer"到底是指什麼呢?這個buffer在什麼地方呢?FILE是什麼呢?它的空間是怎麼分配的呢  要弄清楚這些問題,就要看看FILE是如何定義和運作的了.(特別說明,在平時寫程式時,不用也不要關心FILE是如何定義和運作的,最好不要直接操作它,這裡使用它,只是為了說明buffered IO)下麵的這個是glibc給出的FILE的定義,它是實現相關的,別的平臺定義方式不同.

struct _IO_FILE {
int _flags;
#define _IO_file_flags _flags



char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base;
char* _IO_write_base;
char* _IO_write_ptr;
char* _IO_write_end;
char* _IO_buf_base;
char* _IO_buf_end;

char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
};

  上面的定義中有三組重要的欄位:

1.
char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base;
2.
char* _IO_write_base;
char* _IO_write_ptr;
char* _IO_write_end;
3.
char* _IO_buf_base;
char* _IO_buf_end;
  其中,
  _IO_read_base 指向"讀緩衝區"
  _IO_read_end  指向"讀緩衝區"的末尾
  _IO_read_end - _IO_read_base "讀緩衝區"的長度

  _IO_write_base 指向"寫緩衝區"
  _IO_write_end 指向"寫緩衝區"的末尾
  _IO_write_end - _IO_write_base "寫緩衝區"的長度

  _IO_buf_base  指向"緩衝區"
  _IO_buf_end   指向"緩衝區"的末尾
  _IO_buf_end - _IO_buf_base "緩衝區"的長度

  上面的定義貌似給出了3個緩衝區,實際上上面的_IO_read_base,_IO_write_base, _IO_buf_base都指向了同一個緩衝區.這個緩衝區跟上面程式中的char buf[5];沒有任何關係.他們在第一次buffered I/O操作時由庫函數自動申請空間,最後由相應庫函數負責釋放.(再次聲明,這裡只是glibc的實現,別的實現可能會不同,後面就不再強調了)

  請看下麵的程式(這裡給的是stdin,行緩衝的例子):
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  char buf[5];
  FILE *myfile =stdin;
  printf("before reading/n");
  printf("read buffer base %p/n", myfile->_IO_read_base);
  printf("read buffer length %d/n", myfile->_IO_read_end - myfile->_IO_read_base);
  printf("write buffer base %p/n", myfile->_IO_write_base);
  printf("write buffer length %d/n", myfile->_IO_write_end - myfile->_IO_write_base);
  printf("buf buffer base %p/n", myfile->_IO_buf_base);
  printf("buf buffer length %d/n", myfile->_IO_buf_end - myfile->_IO_buf_base);
  printf("/n");
  fgets(buf, 5, myfile);
  fputs(buf, myfile);
  printf("/n");
  printf("after reading/n");
  printf("read buffer base %p/n", myfile->_IO_read_base);
  printf("read buffer length %d/n", myfile->_IO_read_end - myfile->_IO_read_base);
  printf("write buffer base %p/n", myfile->_IO_write_base);
  printf("write buffer length %d/n", myfile->_IO_write_end - myfile->_IO_write_base);
  printf("buf buffer base %p/n", myfile->_IO_buf_base);
  printf("buf buffer length %d/n", myfile->_IO_buf_end - myfile->_IO_buf_base);

  return 0;
}

  可以看到,在讀操作之前,myfile的緩衝區是沒有被分配的,在一次讀之後,myfile的緩衝區才被分配.這個緩衝區既不是內核中的緩衝區,也不是用戶分配的緩衝區,而是有用戶進程空間中的由buffered I/O系統負責維護的緩衝區.(當然,用戶可以可以維護該緩衝區,這裡不做討論了)

  上面的例子只是說明瞭buffered I/O緩衝區的存在,下麵從全緩衝,行緩衝和無緩衝3個方面看一下buffered I/O是如何工作的.


二、 全緩衝

  下麵是APUE上的原話:全緩衝"在填滿標準I/O緩衝區後才進行實際的I/O操作.對於駐留在磁碟上的文件通常是由標準I/O庫實施全緩衝的"書中這裡"實際的I/O操作"實際上容易引起誤導,這裡並不是讀寫磁碟,而應該是進行read或write的系統調用,下麵兩個例子會說明這個問題:
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  char buf[5];
  char *cur;
  FILE *myfile;
  myfile = fopen("bbb.txt", "r");
  printf("before reading, myfile->_IO_read_ptr: %d/n", myfile->_IO_read_ptr - myfile->_IO_read_base);
  fgets(buf, 5, myfile); //僅僅讀4個字元
  cur = myfile->_IO_read_base;
  while (cur <</span> myfile->_IO_read_end) //實際上讀滿了這個緩衝區
  {
    printf("%c",*cur);
    cur++;
  }
  printf("/nafter reading, myfile->_IO_read_ptr: %d/n", myfile->_IO_read_ptr - myfile->_IO_read_base);
  return 0;
}

  上面提到的bbb.txt文件的內容是由很多行的"123456789"組成上例中,fgets(buf, 5, myfile); 僅僅讀4個字元,但是,緩衝區已被寫滿,但是_IO_read_ptr卻向前移動了5位,下次再次調用讀操作時,只要要讀的位數不超過myfile->_IO_read_end - myfile->_IO_read_ptr那麼就不需要再次調用系統調用read,只要將數據從myfile的緩衝區拷貝到buf即可(從myfile->_IO_read_ptr開始拷貝)

  全緩衝讀的時候,_IO_read_base始終指向緩衝區的開始,_IO_read_end始終指向已從內核讀入緩衝區的字元的下一個(對全緩衝來說,buffered I/O讀每次都試圖都將緩衝區讀滿),IO_read_ptr始終指向緩衝區中已被用戶讀走的字元的下一個(_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時則已經到達文件末尾其中_IO_buf_base-_IO_buf_end是緩衝區的長度
  一般大體的工作情景為:第一次fgets(或其他的)時,標準I/O會調用read將緩衝區充滿,下一次fgets不調用read而是直接從該緩衝區中拷貝數據,直到緩衝區的中剩餘的數據不夠時,再次調用read.在這個過程中,_IO_read_ptr就是用來記錄緩衝區中哪些數據是已讀的,
哪些數據是未讀的.   
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  char buf[2048]={0};
  int i;
  FILE *myfile;
  myfile = fopen("aaa.txt", "r+");
  i= 0;
  while (i<</span>2048)
  {
    fwrite(buf+i, 1, 512, myfile);
    i +=512;
    //註釋掉這句則可以寫入aaa.txt
    myfile->_IO_write_ptr = myfile->_IO_write_base;
    printf("%p write buffer base/n", myfile->_IO_write_base);
    printf("%p buf buffer base /n", myfile->_IO_buf_base);
    printf("%p read buffer base /n", myfile->_IO_read_base);
    printf("%p write buffer ptr /n", myfile->_IO_write_ptr);
    printf("/n");
  }
  return 0;
}

  上面這個是關於全緩衝寫的例子.全緩衝時,只有當標準I/O自動flush(比如當緩衝區已滿時)或者手工調用fflush時,標準I/O才會調用一次write系統調用.例子中,fwrite(buf+i, 1, 512, myfile);這一句只是將buf+i接下來的512個位元組寫入緩衝區,由於緩衝區未滿,標準I/O並未調用write.此時,myfile->_IO_write_ptr = myfile->_IO_write_base;會導致標準I/O認為沒有數據寫入緩衝區,所以永遠不會調用write,這樣aaa.txt文件得不到寫入.註釋掉myfile->_IO_write_ptr = myfile->_IO_write_base;前後,看看效果

  全緩衝寫的時候:_IO_write_base始終指向緩衝區的開始,_IO_write_end全緩衝的時候,始終指向緩衝區的最後一個字元的下一個(對全緩衝來說,buffered I/O寫總是試圖在緩衝區寫滿之後,再系統調用write),_IO_write_ptr始終指向緩衝區中已被用戶寫入的字元的下一個,flush的時候,將_IO_write_base和_IO_write_ptr之間的字元通過系統調用write寫入內核


三、 行緩衝

  下麵是APUE上的原話:行緩衝"當輸入輸出中遇到換行符時,標準I/O庫執行I/O操作. "書中這裡"執行O操作"也容易引起誤導,這裡不是讀寫磁碟,而應該是進行read或write的系統調用
  下麵兩個例子會說明這個問題
  第一個例子可以用來說明下麵這篇帖子的問題
  http://bbs.chinaunix.net/viewthread.php?tid=954547
  
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
  char buf[5];
  char buf2[10];
  
  fgets(buf, 5, stdin); //第一次輸入時,超過5個字元

  puts(stdin->_IO_read_ptr);//本句說明整行會被一次全部讀入緩衝區,

                                         //而非僅僅上面需要的個字元
  stdin->_IO_read_ptr = stdin->_IO_read_end; //標準I/O會認為緩衝區已空,再次調用read
                                             //註釋掉,再看看效果
  printf("/n");
  puts(buf);
  
  fgets(buf2, 10, stdin);
  puts(buf2);
  
  return 0;
}

  上例中, fgets(buf, 5, stdin); 僅僅需要4個字元,但是,輸入行中的其他數據也被寫入緩衝區,但是_IO_read_ptr向前移動了5位,下次再次調用fgets操作時,就不需要再次調用系統調用read,只要將數據從stdin的緩衝區拷貝到buf2即可(從stdin->_IO_read_ptr開始拷貝)stdin->_IO_read_ptr = stdin->_IO_read_end;會導致標準I/O會認為緩衝區已空,再次fgets則需要再次調用read.比較一下將該句註釋掉前後的效果


  
行緩衝讀的時候,
  _IO_read_base始終指向緩衝區的開始
  _IO_read_end始終指向已從內核讀入緩衝區的字元的下一個
  _IO_read_ptr始終指向緩衝區中已被用戶讀走的字元的下一個
  (_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時則已經到達文件末尾
  其中_IO_buf_base-_IO_buf_end是緩衝區的長度
  
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

char buf[5]={'1','2', '3', '4', '5'}; //最後一個不要是/n,是/n的話,標準I/O會自動flush的
                                                    //這是行緩衝跟全緩衝的重要區別

void writeLog(FILE *ftmp)
{
  fprintf(ftmp, "%p write buffer base/n", stdout->_IO_write_base);
  fprintf(ftmp, "%p buf buffer base /n", stdout->_IO_buf_base);
  fprintf(ftmp, "%p read buffer base /n", stdout->_IO_read_base);
  fprintf(ftmp, "%p write buffer ptr /n", stdout->_IO_write_ptr);
  fprintf(ftmp, "/n");
}

int main(void)
{
  int i;
  FILE *ftmp;
  ftmp = fopen("ccc.txt", "w");
  i= 0;
  while (i<</span>4)
  {
    fwrite(buf, 1, 5, stdout);
    i++;
    *stdout->_IO_write_ptr++ = '/n';//可以單獨把這句打開,看看效果
    //getchar();//getchar()會標準I/O將緩衝區輸出
    //打開下麵的註釋,你就會發現屏幕上什麼輸出也沒有
    //stdout->_IO_write_ptr = stdout->_IO_write_base;
    writeLog(ftmp); //這個只是為了查看緩衝區指針的變化  
  }
  return 0;
}

  這個例子將將FILE結構中指針的變化寫入的文件ccc.txt,

  運行後可以有興趣的話,可以看看.

  上面這個是關於行緩衝寫的例子.stdout->_IO_write_ptr = stdout->_IO_write_base;會使得標準I/O認為緩衝區是空的,從而沒有任何輸出.可以將上面程式中的註釋分別去掉,看看運行結果

  行緩衝時,下麵3個條件之一會導致緩衝區立即被flush
  1. 緩衝區已滿
  2. 遇到一個換行符;比如將上面例子中buf[4]改為'/n'時
  3. 再次要求從內核中得到數據時;比如上面的程式加上getchar()會導致馬上輸出

  行緩衝寫的時候:
  _IO_write_base始終指向緩衝區的開始
  _IO_write_end始終指向緩衝區的開始
  _IO_write_ptr始終指向緩衝區中已被用戶寫入的字元的下一個

  flush的時候,將_IO_write_base和_IO_write_ptr之間的字元通過系統調用write寫入內核

四、無緩衝
  無緩衝時,標準I/O不對字元進行緩衝存儲.典型代表是stderr。這裡的無緩衝,並不是指緩衝區大小為0,其實,還是有緩衝的,大小為1
#include <</span>stdlib.h>
#include <</span>stdio.h>
#include <</span>sys/types.h>
#include <</span>sys/stat.h>
#include <</span>fcntl.h>

int main(void)
{
  fputs("stderr", stderr);
  printf("%d/n", stderr->_IO_buf_end - stderr->_IO_buf_base);

  return 0;
}

  對無緩衝的流的每次讀寫操作都會引起系統調用


五、 feof的問題

  這裡從緩衝區的角度去考察一下.對於一個空文件,為什麼要先讀一下,才能用feof判斷出該文件到了結尾了呢?
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void)
{
  char buf[5];
  char buf2[10];

  fgets(buf, sizeof(buf), stdin);//輸入要於4個,少於13個字元才能看出效果
  puts(buf);

  //交替註釋下麵兩行
  //stdin->_IO_read_end = stdin->_IO_read_ptr+1;

  stdin->_IO_read_end = stdin->_IO_read_ptr + sizeof(buf2)-1;
   
  fgets(buf2, sizeof(buf2), stdin);
  puts(buf2);
  if (feof(stdin))
    printf("input end/n");
  return 0;
}

 

  運行上面的程式,輸入多於4個,少於13個字元,並且以連按兩次ctrl+d為結束(不要按回車),從上面的例子,可以看出,每當滿足(_IO_read_end < (_IO_buf_base-_IO_buf_end)) && (_IO_read_ptr == _IO_read_end)時,標準I/O則認為已經到達文件末尾,feof(stdin)才會被設置其中_IO_buf_base-_IO_buf_end是緩衝區的長度。

  也就是說,標準I/O是通過它的緩衝區來判斷流是否要結束了的.這就解釋了為什麼即使是一個空文件,標準I/O也需要讀一次,才能使用feof判斷釋放為空。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 上一篇, 都是從別人那裡拷過來的, 主要是介紹規則和說明的. 這一篇, 才是重點, 講實際使用. 首先介紹項目中最常用的配置文件方式. 一、log4net.config 文件方式 我習慣, 把log4net的配置, 放在一個單獨的配置文件中, 而不是放在 app.config或者web.config ...
  • 再簡單的功能,也需要一坨代碼的支持。Profile 的編輯功能主要就是修改個人的信息。比如用戶名、頭像、性別、電話……雖然只是一個編輯界面,但添加下來,涉及了6個文件的修改和7個新創建的文件。各種生成的和手寫的代碼,共有934行之多。 1. Account 和 Profile 分離 什麼是 Acco ...
  • 項目過程中, 不可避免的, 需要使用到日誌功能. 在我接觸過的項目中, 也有自己弄一套日誌的, 但是更多的, 還是使用別人成熟的dll, 比如log4. log4相關的文檔, 真是非常的多, 也非常的全, 但是本著溫故而知新的目的, 還是想把這個過一遍. 先放一個小Demo在上面 主要有五個組成部分 ...
  • 現在這個時代是信息化的時代,大數據的時代。各種行業都在向線上管理等方向轉型。這時一系列的問題就自然而然的出來了。信息化管理的公司、企業,如何來開發設計自己的管理系統?選擇何種渠道來獲取符合自己需求的管理系統? 在這個時代,軟體公司遍地可尋。軟體開發行業蓬勃發展。儘管有這麼多的資源和產品,總多企業仍然 ...
  • 眼看快到婚期了,我在單位愉快的加著班!本人出身農村,11年畢業於一個不上檔次的院校,在學校里學的是電腦相關專業,這種專業在家裡基本上是不太好找工作的,想想好多學長帶著夢想去了北上廣深等地發展,畢竟這種行業在這些大城市裡比較發達。畢業之後,我孤身一人帶著我的北漂夢想,踏上了去往北京的征途。來了北京不 ...
  • ThinkPHP 模板substr的截取字元串函數在Common/function.php加上以下代碼 前端頁面需要截取字元串時 /********************************************案例************************************** ...
  • 前端獲取數據: thinkphp讀取資料庫數據: ...
  • 寫了一個圖片處理的Image類,1.能生成縮略圖,不失真,不出現黑邊;2.添加水印圖,能根據圖片大小,自動縮放水印圖。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...