php內核探索

引自:http://www.nowamagic.net/librarys/veda/detail/1285

SAPI:Server Application Programming Interface 服務器端應用編程端口。研究過PHP架構的同學應該知道這個東東的重要性,它提供了一個接口,使得PHP可以和其他應用進行交互數據。 本文不會詳細介紹每個PHP的SAPI,只是針對最簡單的CGI SAPI,來說明SAPI的機制。

我們先來看看PHP的架構圖:

SAPI指的是PHP具體應用的編程接口, 就像PC一樣,無論安裝哪些操作系統,只要滿足了PC的接口規范都可以在PC上正常運行, PHP腳本要執行有很多種方式,通過Web服務器,或者直接在命令行下,也可以嵌入在其他程序中。

通常,我們使用Apache或者Nginx這類Web服務器來測試PHP腳本,或者在命令行下通過PHP解釋器程序來執行。 腳本執行完后,Web服務器應答,瀏覽器顯示應答信息,或者在命令行標準輸出上顯示內容。

我們很少關心PHP解釋器在哪里。雖然通過Web服務器和命令行程序執行腳本看起來很不一樣, 實際上它們的工作流程是一樣的。命令行參數傳遞給PHP解釋器要執行的腳本, 相當于通過url請求一個PHP頁面。腳本執行完成后返回響應結果,只不過命令行的響應結果是顯示在終端上。

腳本執行的開始都是以SAPI接口實現開始的。只是不同的SAPI接口實現會完成他們特定的工作, 例如Apache的mod_php SAPI實現需要初始化從Apache獲取的一些信息,在輸出內容是將內容返回給Apache, 其他的SAPI實現也類似。

SAPI提供了一個和外部通信的接口, 對于PHP5.2,默認提供了很多種SAPI, 常見的給apache的mod_php5,CGI,給IIS的ISAPI,還有Shell的CLI,本文就從CGI SAPI入手 ,介紹SAPI的機制。 雖然CGI簡單,但是不用擔心,它包含了絕大部分內容,足以讓你深刻理解SAPI的工作原理。

要定義個SAPI,首先要定義個sapi_module_struct, 查看 PHP-SRC/sapi/cgi/cgi_main.c:

01*/
02static?sapi_module_struct cgi_sapi_module = {
03#if PHP_FASTCGI
04????"cgi-fcgi",?????????????????????/* name */
05????"CGI/FastCGI",??????????????????/* pretty name */
06#else
07????"cgi",??????????????????????????/* name */
08????"CGI",??????????????????????????/* pretty name */
09#endif
10??
11????php_cgi_startup,????????????????/* startup */
12????php_module_shutdown_wrapper,????/* shutdown */
13??
14????NULL,???????????????????????????/* activate */
15????sapi_cgi_deactivate,????????????/* deactivate */
16??
17????sapi_cgibin_ub_write,???????????/* unbuffered write */
18????sapi_cgibin_flush,??????????????/* flush */
19????NULL,???????????????????????????/* get uid */
20????sapi_cgibin_getenv,?????????????/* getenv */
21??
22????php_error,??????????????????????/* error handler */
23??
24????NULL,???????????????????????????/* header handler */
25????sapi_cgi_send_headers,??????????/* send headers handler */
26????NULL,???????????????????????????/* send header handler */
27??
28????sapi_cgi_read_post,?????????????/* read POST data */
29????sapi_cgi_read_cookies,??????????/* read Cookies */
30??
31????sapi_cgi_register_variables,????/* register server variables */
32????sapi_cgi_log_message,???????????/* Log message */
33????NULL,???????????????????????????/* Get request time */
34??
35????STANDARD_SAPI_MODULE_PROPERTIES
36};

這個結構,包含了一些常量,比如name, 這個會在我們調用php_info()的時候被使用。一些初始化,收尾函數,以及一些函數指針,用來告訴Zend,如何獲取,和輸出數據。

1. php_cgi_startup, 當一個應用要調用PHP的時候,這個函數會被調用,對于CGI來說,它只是簡單的調用了PHP的初始化函數:

1static?int?php_cgi_startup(sapi_module_struct *sapi_module)
2{
3????if?(php_module_startup(sapi_module, NULL, 0) == FAILURE) {
4????????return?FAILURE;
5????}
6????return?SUCCESS;
7}

2. php_module_shutdown_wrapper , 一個對PHP關閉函數的簡單包裝。只是簡單的調用php_module_shutdown;

3. PHP會在每個request的時候,處理一些初始化,資源分配的事務。這部分就是activate字段要定義的,從上面的結構我們可以看出,對于CGI來說,它并沒有提供初始化處理句柄。對于mod_php來說,那就不同了,他要在apache的pool中注冊資源析構函數, 申請空間, 初始化環境變量,等等。

4. sapi_cgi_deactivate, 這個是對應與activate的函數,顧名思義,它會提供一個handler, 用來處理收尾工作,對于CGI來說,他只是簡單的刷新緩沖區,用以保證用戶在Zend關閉前得到所有的輸出數據:

01static?int?sapi_cgi_deactivate(TSRMLS_D)
02{
03????/* flush only when SAPI was started. The reasons are:
04????????1. SAPI Deactivate is called from two places: module init and request shutdown
05????????2. When the first call occurs and the request is not set up, flush fails on
06????????????FastCGI.
07????*/
08????if?(SG(sapi_started)) {
09????????sapi_cgibin_flush(SG(server_context));
10????}
11????return?SUCCESS;
12}

5. sapi_cgibin_ub_write, 這個hanlder告訴了Zend,如何輸出數據,對于mod_php來說,這個函數提供了一個向response數據寫的接口,而對于CGI來說,只是簡單的寫到stdout:

01static?inline?size_t?sapi_cgibin_single_write(const?char?*str, uint str_length TSRMLS_DC)
02{
03#ifdef PHP_WRITE_STDOUT
04????long?ret;
05#else
06????size_t?ret;
07#endif
08??
09#if PHP_FASTCGI
10????if?(fcgi_is_fastcgi()) {
11????????fcgi_request *request = (fcgi_request*) SG(server_context);
12????????long?ret = fcgi_write(request, FCGI_STDOUT, str, str_length);
13????????if?(ret <= 0) {
14????????????return?0;
15????????}
16????????return?ret;
17????}
18#endif
19#ifdef PHP_WRITE_STDOUT
20????ret = write(STDOUT_FILENO, str, str_length);
21????if?(ret <= 0)?return?0;
22????return?ret;
23#else
24????ret =?fwrite(str, 1, MIN(str_length, 16384), stdout);
25????return?ret;
26#endif
27}
28??
29static?int?sapi_cgibin_ub_write(const?char?*str, uint str_length TSRMLS_DC)
30{
31????const?char?*ptr = str;
32????uint remaining = str_length;
33????size_t?ret;
34??
35????while?(remaining > 0) {
36????????ret = sapi_cgibin_single_write(ptr, remaining TSRMLS_CC);
37????????if?(!ret) {
38????????????php_handle_aborted_connection();
39????????????return?str_length - remaining;
40????????}
41????????ptr += ret;
42????????remaining -= ret;
43????}
44??
45????return?str_length;
46}

把真正的寫的邏輯剝離出來,就是為了簡單實現兼容fastcgi的寫方式。

6. sapi_cgibin_flush, 這個是提供給zend的刷新緩存的函數句柄,對于CGI來說,只是簡單的調用系統提供的fflush;

7.NULL, 這部分用來讓Zend可以驗證一個要執行腳本文件的state,從而判斷文件是否據有執行權限等等,CGI沒有提供。

8. sapi_cgibin_getenv, 為Zend提供了一個根據name來查找環境變量的接口,對于mod_php5來說,當我們在腳本中調用getenv的時候,就會間接的調用這個句柄。而對于CGI來說,因為他的運行機制和CLI很類似,直接調用父級是Shell, 所以,只是簡單的調用了系統提供的genenv:

01static?char?*sapi_cgibin_getenv(char?*name,?size_t?name_len TSRMLS_DC)
02{
03#if PHP_FASTCGI
04????/* when php is started by mod_fastcgi, no regular environment
05???????is provided to PHP.? It is always sent to PHP at the start
06???????of a request.? So we have to do our own lookup to get env
07???????vars.? This could probably be faster somehow.? */
08????if?(fcgi_is_fastcgi()) {
09????????fcgi_request *request = (fcgi_request*) SG(server_context);
10????????return?fcgi_getenv(request, name, name_len);
11????}
12#endif
13????/*? if cgi, or fastcgi and not found in fcgi env
14????????check the regular environment */
15????return?getenv(name);
16}

9. php_error, 錯誤處理函數, 到這里,說幾句題外話,上次看到php maillist 提到的使得PHP的錯誤處理機制完全OO化, 也就是,改寫這個函數句柄,使得每當有錯誤發生的時候,都throw一個異常。而CGI只是簡單的調用了PHP提供的錯誤處理函數。

10. 這個函數會在我們調用PHP的header()函數的時候被調用,對于CGI來說,不提供。

11. sapi_cgi_send_headers, 這個函數會在要真正發送header的時候被調用,一般來說,就是當有任何的輸出要發送之前:

01static?int?sapi_cgi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
02{
03????char?buf[SAPI_CGI_MAX_HEADER_LENGTH];
04????sapi_header_struct *h;
05????zend_llist_position pos;
06??
07????if?(SG(request_info).no_headers == 1) {
08????????return??SAPI_HEADER_SENT_SUCCESSFULLY;
09????}
10??
11????if?(cgi_nph || SG(sapi_headers).http_response_code != 200)
12????{
13????????int?len;
14??
15????????if?(rfc2616_headers && SG(sapi_headers).http_status_line) {
16????????????len = snprintf(buf, SAPI_CGI_MAX_HEADER_LENGTH,
17???????????????????????????"%s\r\n", SG(sapi_headers).http_status_line);
18??
19????????????if?(len > SAPI_CGI_MAX_HEADER_LENGTH) {
20????????????????len = SAPI_CGI_MAX_HEADER_LENGTH;
21????????????}
22??
23????????}?else?{
24????????????len =?sprintf(buf,?"Status: %d\r\n", SG(sapi_headers).http_response_code);
25????????}
26??
27????????PHPWRITE_H(buf, len);
28????}
29??
30????h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
31????while?(h) {
32????????/* prevent CRLFCRLF */
33????????if?(h->header_len) {
34????????????PHPWRITE_H(h->header, h->header_len);
35????????????PHPWRITE_H("\r\n", 2);
36????????}
37????????h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
38????}
39????PHPWRITE_H("\r\n", 2);
40??
41????return?SAPI_HEADER_SENT_SUCCESSFULLY;
42???}

12. NULL, 這個用來單獨發送每一個header, CGI沒有提供

13. sapi_cgi_read_post, 這個句柄指明了如何獲取POST的數據,如果做過CGI編程的話,我們就知道CGI是從stdin中讀取POST DATA的:

01static?int?sapi_cgi_read_post(char?*buffer, uint count_bytes TSRMLS_DC)
02{
03????uint read_bytes=0, tmp_read_bytes;
04#if PHP_FASTCGI
05????char?*pos = buffer;
06#endif
07??
08????count_bytes = MIN(count_bytes, (uint) SG(request_info).content_length - SG(read_post_bytes));
09????while?(read_bytes < count_bytes) {
10#if PHP_FASTCGI
11????????if?(fcgi_is_fastcgi()) {
12????????????fcgi_request *request = (fcgi_request*) SG(server_context);
13????????????tmp_read_bytes = fcgi_read(request, pos, count_bytes - read_bytes);
14????????????pos += tmp_read_bytes;
15????????}?else?{
16????????????tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
17????????}
18#else
19????????tmp_read_bytes = read(0, buffer + read_bytes, count_bytes - read_bytes);
20#endif
21??
22????????if?(tmp_read_bytes <= 0) {
23????????????break;
24????????}
25????????read_bytes += tmp_read_bytes;
26????}
27????return?read_bytes;
28}

14. sapi_cgi_read_cookies, 這個和上面的函數一樣,只不過是去獲取cookie值:

1static?char?*sapi_cgi_read_cookies(TSRMLS_D)
2{
3????return?sapi_cgibin_getenv((char?*)?"HTTP_COOKIE",sizeof("HTTP_COOKIE")-1 TSRMLS_CC);
4}

15. sapi_cgi_register_variables, 這個函數給了一個接口,用以給$_SERVER變量中添加變量,對于CGI來說,注冊了一個PHP_SELF,這樣我們就可以在腳本中訪問$_SERVER['PHP_SELF']來獲取本次的request_uri:

1static?void?sapi_cgi_register_variables(zval *track_vars_array TSRMLS_DC)
2{
3????/* In CGI mode, we consider the environment to be a part of the server
4?????* variables
5?????*/
6????php_import_environment_variables(track_vars_array TSRMLS_CC);
7????/* Build the special-case PHP_SELF variable for the CGI version */
8????php_register_variable("PHP_SELF", (SG(request_info).request_uri ? SG(request_info).request_uri :?""), track_vars_array TSRMLS_CC);
9}

16. sapi_cgi_log_message ,用來輸出錯誤信息,對于CGI來說,只是簡單的輸出到stderr:

01static?void?sapi_cgi_log_message(char?*message)
02{
03#if PHP_FASTCGI
04????if?(fcgi_is_fastcgi() && fcgi_logging) {
05????????fcgi_request *request;
06????????TSRMLS_FETCH();
07??
08????????request = (fcgi_request*) SG(server_context);
09????????if?(request) {
10????????????int?len =?strlen(message);
11????????????char?*buf =?malloc(len+2);
12??
13????????????memcpy(buf, message, len);
14????????????memcpy(buf + len,?"\n",?sizeof("\n"));
15????????????fcgi_write(request, FCGI_STDERR, buf, len+1);
16????????????free(buf);
17????????}?else?{
18????????????fprintf(stderr,?"%s\n", message);
19????????}
20????????/* ignore return code */
21????}?else
22#endif /* PHP_FASTCGI */
23????fprintf(stderr,?"%s\n", message);
24}

經過分析,我們已經了解了一個SAPI是如何實現的了, 分析過CGI以后,我們也就可以想象mod_php, embed等SAPI的實現機制。

延伸閱讀

此文章所在專題列表如下:

  1. PHP內核探索:從SAPI接口開始
  2. PHP內核探索:一次請求的開始與結束
  3. PHP內核探索:一次請求生命周期
  4. PHP內核探索:單進程SAPI生命周期
  5. PHP內核探索:多進程/線程的SAPI生命周期
  6. PHP內核探索:Zend引擎
  7. PHP內核探索:再次探討SAPI
  8. PHP內核探索:Apache模塊介紹
  9. PHP內核探索:通過mod_php5支持PHP
  10. PHP內核探索:Apache運行與鉤子函數
  11. PHP內核探索:嵌入式PHP
  12. PHP內核探索:PHP的FastCGI
  13. PHP內核探索:如何執行PHP腳本
  14. PHP內核探索:PHP腳本的執行細節
  15. PHP內核探索:操作碼OpCode
  16. PHP內核探索:PHP里的opcode
  17. PHP內核探索:解釋器的執行過程
  18. PHP內核探索:變量概述
  19. PHP內核探索:變量存儲與類型
  20. PHP內核探索:PHP中的哈希表
  21. PHP內核探索:理解Zend里的哈希表
  22. PHP內核探索:PHP哈希算法設計
  23. PHP內核探索:翻譯一篇HashTables文章
  24. PHP內核探索:哈希碰撞攻擊是什么?
  25. PHP內核探索:常量的實現
  26. PHP內核探索:變量的存儲
  27. PHP內核探索:變量的類型
  28. PHP內核探索:變量的值操作
  29. PHP內核探索:變量的創建
  30. PHP內核探索:預定義變量
  31. PHP內核探索:變量的檢索
  32. PHP內核探索:變量的類型轉換
  33. PHP內核探索:弱類型變量的實現
  34. PHP內核探索:靜態變量的實現
  35. PHP內核探索:變量類型提示
  36. PHP內核探索:變量的生命周期
  37. PHP內核探索:變量賦值與銷毀
  38. PHP內核探索:變量作用域
  39. PHP內核探索:詭異的變量名
  40. PHP內核探索:變量的value和type存儲
  41. PHP內核探索:全局變量Global
  42. PHP內核探索:變量類型的轉換
  43. PHP內核探索:內存管理開篇
  44. PHP內核探索:Zend內存管理器
  45. PHP內核探索:PHP的內存管理
  46. PHP內核探索:內存的申請與銷毀
  47. PHP內核探索:引用計數與寫時復制
  48. PHP內核探索:PHP5.3的垃圾回收機制
  49. PHP內核探索:內存管理中的cache
  50. PHP內核探索:寫時復制COW機制
  51. PHP內核探索:數組與鏈表
  52. PHP內核探索:使用哈希表API
  53. PHP內核探索:數組操作
  54. PHP內核探索:數組源碼分析
  55. PHP內核探索:函數的分類
  56. PHP內核探索:函數的內部結構
  57. PHP內核探索:函數結構轉換
  58. PHP內核探索:定義函數的過程
  59. PHP內核探索:函數的參數
  60. PHP內核探索:zend_parse_parameters函數
  61. PHP內核探索:函數返回值
  62. PHP內核探索:形參return value
  63. PHP內核探索:函數調用與執行
  64. PHP內核探索:引用與函數執行
  65. PHP內核探索:匿名函數及閉包
  66. PHP內核探索:面向對象開篇
  67. PHP內核探索:類的結構和實現
  68. PHP內核探索:類的成員變量
  69. PHP內核探索:類的成員方法
  70. PHP內核探索:類的原型zend_class_entry
  71. PHP內核探索:類的定義
  72. PHP內核探索:訪問控制
  73. PHP內核探索:繼承,多態與抽象類
  74. PHP內核探索:魔術函數與延遲綁定
  75. PHP內核探索:保留類與特殊類
  76. PHP內核探索:對象
  77. PHP內核探索:創建對象實例
  78. PHP內核探索:對象屬性讀寫
  79. PHP內核探索:命名空間
  80. PHP內核探索:定義接口
  81. PHP內核探索:繼承與實現接口
  82. PHP內核探索:資源resource類型
  83. PHP內核探索:Zend虛擬機
  84. PHP內核探索:虛擬機的詞法解析
  85. PHP內核探索:虛擬機的語法分析
  86. PHP內核探索:中間代碼opcode的執行
  87. PHP內核探索:代碼的加密與解密
  88. PHP內核探索:zend_execute的具體執行過程
  89. PHP內核探索:變量的引用與計數規則
  90. PHP內核探索:新垃圾回收機制說明

轉載于:https://www.cnblogs.com/lppblogs/archive/2013/02/21/2920111.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274353.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274353.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274353.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

hp-ux鎖定用戶密碼_UX設計101:用戶研究-入門需要了解的一切

hp-ux鎖定用戶密碼這是什么&#xff1f; (What is this?) This session is part of a learning curriculum that I designed to incrementally skill up and empower a team of Designers and Researchers whose skillset and ways of working needed to evolve to keep up wi…

等比數列前N項和的公式推導

設等比數列的前n項和為S(n), 等比數列的第一項為a1&#xff0c;比值為q。 &#xff08;1&#xff09;S(n) a1 a1 * q a1 * q ^ 2 .... a1 * q ^ (n - 1);&#xff08;2&#xff09;S(n1) a1 a1 * q a1 * q ^ 2 .... a1 * q ^ (n - 1) a1 * q ^ n;由&#xff08;2&am…

extjs6 引入ux_關于UX以及如何擺脫UX的6種常見誤解

extjs6 引入uxDo you ever browse social media, internet, or talk to colleagues and hear them say something UX related you disagree with so much that you just want to lecture them on the spot?您是否曾經瀏覽過社交媒體&#xff0c;互聯網或與同事交談&#xff0c…

Cocos2D-HTML5開源2D游戲引擎

http://www.programmer.com.cn/12198/ Cocos2D-HTML5是基于HTML5規范集的Cocos2D引擎的分支&#xff0c;于2012年5月發布。Cocos2D-HTML5的作者林順將在本文中介紹Cocos2D-HTML5的框架、API、跨平臺能力以及強大的性能。Cocos2D-HTML5是Cocos2D系列引擎隨著互聯網技術演進而產生…

illustrator下載_Illustrator筆工具練習

illustrator下載Adobe Illustrator is a fantastic vector creation tool and you can create a lot of things without ever using the Pen Tool. However, if you want to use Illustrator at its full potential, I personally believe that you need to master and become …

怎么更好練習數位板_如何設計更好的儀表板

怎么更好練習數位板重點 (Top highlight)Dashboard noun \?dash-?b?rd\ A screen on the front of a usually horse-drawn vehicle to intercept water, mud, or snow.儀表盤 名詞\ ?dash-?b?rd \\通常在馬拉的車輛前部的屏幕&#xff0c;用來攔截水&#xff0c;泥或雪。…

學習正則表達式

deerchao的blog Be and aware of who you are. 正則表達式30分鐘入門教程 來園子之前寫的一篇正則表達式教程&#xff0c;部分翻譯自codeproject的The 30 Minute Regex Tutorial。 由于評論里有過長的URL,所以本頁排版比較混亂,推薦你到原處查看,看完了如果有問題,再到這里來提…

人物肖像速寫_去哪兒? 優步肖像之旅

人物肖像速寫In early 2018, the Uber brand team started a rebranding exercise, exploring a fresh take on what it means to be a global transportation and technology company. A new logo was developed in tandem with a bespoke sans-serif typeface called Uber Mo…

js獲取和設置屬性

function square(num){ var total num*num;//局部變量 return total;}var total 50;//全局變量var number square(20);alert(total);//結果為50function square(num){ total num*num;//全局變量 return total;}var total 50;//全局變量var number square(20)…

hp-ux鎖定用戶密碼_我們如何簡化925移動應用程序的用戶入門— UX案例研究

hp-ux鎖定用戶密碼Prologue: While this is fundamentally a showcase of our process in the hopes of helping others, it’s also a story about the realism of limitations when working with clients and how we ultimately were able to deliver a product the client w…

微信公眾號無需二次登錄_您無需兩次解決問題-您需要一個設計系統

微信公眾號無需二次登錄重點 (Top highlight)The design system concept can be differently defined according to each person’s background. Designers may say that a design system is a style guide while developers may say it is UI standards, or specs, or even as…

android中AsyncTask和Handler對比

1 &#xff09; AsyncTask實現的原理,和適用的優缺點 AsyncTask,是android提供的輕量級的異步類,可以直接繼承AsyncTask,在類中實現異步操作,并提供接口反饋當前異步執行的程度(可以通過接口實現UI進度更新),最后反饋執行的結果給UI主線程. 使用的優點: l 簡單,快捷 l 過程可…

視覺工程師面試指南_選擇正確視覺效果的終極指南

視覺工程師面試指南When it comes to effective data visualization, the very first and also the most critical step is to select the right graph/visual for the data that you want to present. With a wide range of visualization software that is available offerin…

在 Linux 下使用 水星MW150cus (RTL8188CUS芯片)無線網卡

Fedora &#xff08;如果你不使用 PAE 內核&#xff0c;請去掉 PAE 字樣&#xff09;:yum install gcc kernel-PAE kernel-PAE-devel kernel-headers dkms Ubuntu: apt-get install make gcc linux-kernel-devel linux-headers-uname -r安裝原生驅動 注意&#xff1a;由于在 Li…

問題反饋模板_使用此模板可獲得更好,更有價值的UX反饋

問題反饋模板Feedback is an important part of UX design. To improve the work you do you need to be able to give and receive feedback. Receiving valuable feedback is for a very large part up to you.反饋是UX設計的重要組成部分。 為了改進您的工作&#xff0c;您需…

【轉載】Android Animation 簡介(官方文檔翻譯) ---- 翻譯的很好!

http://vaero.blog.51cto.com/4350852/849783轉載于:https://www.cnblogs.com/DonkeyTomy/articles/2945687.html

ubuntu 如何轉換 ppk ,連接 amazon ec2

轉 自 &#xff1a;http://www.ehow.com/how_8658327_convert-ppk-ssh-ubuntu.html1 Open a terminal window in Ubuntu, or log in if you are converting the keys on a remote Ubuntu server. 2 Type "sudo apt-get install putty-tools" at the terminal prompt …

iofd:文件描述符_文字很重要:談論設計時18個有意義的描述符

iofd:文件描述符As designers, many of us think we’re just visual creatures. But creating visuals is only half of the job. The other half is verbal communication — actually talking about design. Whether we’re showcasing our own work, giving or receiving c…

保護程序猿滴眼睛-----修改VS 2008 編輯器顏色 (修改 chrome瀏覽器的背景色)

前幾天更改了 chrome 的背景色后&#xff0c;雖然有些地方看起來不和諧&#xff0c;想百度的首頁&#xff0c;顯示出了大快的圖片區域&#xff0c;但是&#xff0c;整體感覺這個顏色設置真的對眼睛有一定保護作用。。。 所以&#xff0c;再順便修改一下 經常用的 vs2008 編輯器…

數據可視化 信息可視化_可視化哲學的黎明

數據可視化 信息可視化Note: this is the foreword of the book Data Visualization in Society (Amsterdam University Press, 2020)注意&#xff1a;這是《 社會中的數據可視化 》一書的前言 (阿姆斯特丹大學出版社&#xff0c;2020年) Geographer John Pickles once wrote …