# CSAPP CH11 Network Programming ## Client-server model 每一個網路應用 ( network application) 是基於 **client-server** model Server 管理資源, 並且提供 client 操作這些資源的服務 (service) Client-server transiction ( 過渡/ or 溝通) 有 4 點 1. 當 `client` 需要服務 (service), 它會發送 `request` 給 `server` e.g. Web browser 需要一張圖片, 它會送 request 給 Web server 2. `Server` 接收 `request`, 並且處理請求內容 3. `Server` 發送 `response` 給 `client`, 並且等到下次的請求 4. `Client` 接收到 `response`, 並且根據 response 做出相對應的動作 ![](https://hackmd.io/_uploads/BkY-Ji7N3.png) 這邊有一個概念: * client & servers 是 **processes**, 而非 `machine ( 或稱 host )` * 單個 host 可以同時運行許多不同的 client & servers, 並且 client & servers 間的溝通可以在同樣的 host, 也可以在不同的 host ## Networks 對一個 host 來說, `network` 只是一個 `I/O device` 罷了, 這個 I/O 只是一個可以用來傳輸和接收數據的裝置。因此,主機可以通過 `network` 與其他 `device` 或 `host` 進行數據交換,就像通過其他I/O裝置一樣。 對於主機來說,網絡只是作為傳送及接收數據的 I/O 設備。 一個插在 I/O bus 上的 adapter 為網路提供物理接口,從網路接收到的資料透過 I/O 及 memory buses 從 adapter 複製到記憶體中,通常透過 DMA 來傳輸,從記憶體複製到網路也是一樣的方法 每個 Ethernet adapter 都有 unique 的 48 bit Mac address. host 可以傳 chunk of bits(frame) 到 segment 上的其他 host,每個 frame 都有 固定的 header bits 來辨識來源跟目的地及 frame 的長度與資料。每個 host adapter 都可以看到 frame,但只有 destination 會讀到 網路是層層組織起的系統。最低層次的 LAN 是連接校園或者建築等有限範圍內的網路,大致由 hub,bridge 和 host 的集合形成,多個不相容的 LAN 則可由 router 所連接起,形成更高層次的 WAN,建構起一個 internet。 ## Frame/Packet header - Frame header: 通常是在數據鏈路層(Data Link Layer)中使用的,用於在局域網(LAN)中傳輸數據幀(Frame)。 Frame header中通常包含了一些基本信息,例如源MAC地址、目標MAC地址 [include/uapi/linux/if_ether.h](https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/if_ether.h#L32) ```c // This is an Ethernet frame header. /* allow libcs like musl to deactivate this, glibc does not implement this. */ #ifndef __UAPI_DEF_ETHHDR #define __UAPI_DEF_ETHHDR 1 #endif #if __UAPI_DEF_ETHHDR struct ethhdr { unsigned char h_dest[ETH_ALEN]; /* destination eth addr */ unsigned char h_source[ETH_ALEN]; /* source ether addr */ __be16 h_proto; /* packet type ID field */ } __attribute__((packed)); #endif ``` - Packet header: 通常是在網絡層(Network Layer)中使用的,用於在 network 中傳輸 Packet。 通常包含了一些與路由和網絡連接有關的信息,例如host IP address、target IP address、協議類型、TTL(生存時間)等。 Frame header 主要用於局域網內部的通信,存儲局域網內部傳輸數據時需要的基本信息,而 Packet header 主要用於不同網絡之間的通信,存儲傳輸數據時需要的路由和連接信息。 ## Protocal software internet 的重要特性是可以由不兼容的 LAN 和 WAN 所構成,何以使得數據的傳輸可以跨越這些不兼容的網路呢? 關鍵在於 host 和 router 之間的 protocal software。Protocol software 定義了一系列的規則使得 host 和 router 之間可以協同工作以傳輸數據。這種協議必須提供兩種基本的能力: Naming scheme: 每個 host 和 rounter 需要擁有至少一個且唯一的地址 Delivery mechanism: 標準的資料傳送單元 – 封包 (packet),每個封包包含了 header 和 payload。header 儲存封包的大小、其來源以及傳送的地址等資訊,payload 則乘載來自 host 所傳輸的數據本身。 ## Global IP internet 每一個 `Internet host` 的 `software` 皆運行 **TCP/IP**, Interent 中, client 及 server 用 **socket interface function ( sys call )** 溝通. TCP/IP 是一群協議(protocols) 組成的家族 例如: IP IP 提供 由一個 `host` 到另一個 `host` 基本的命名方案(naming scheme) 和傳遞機制(delivery mechanism) 需要注意的, `IP mechanism` 是不可靠的, 如果數據包(datagrams) 在網絡中丟失或重複,它不會進行恢復。 UDP (Unreliable DatagramProtocol) 對上述缺點做出了改善, 使 `datagrams` 可以在 process 和 process 間進行傳輸, 而非 host 對 host. Internet 可以視為 **collection of hosts** * The set of hosts is mapped to a set of 32-bit IP address * IP address mapped 為一組 `Internet domain name` 的 identifer * 任何一個在網路上 host 的 process 可以透過 `connection` 來和令一個在網路上的 host溝通 ## IP Address * 32-bit unsigned integer * 由於 Internet hosts 可以有不同的 host byte orders (e.g. host1 是 big endian, host2 是 liitle ,...), TCP/IP 定義了 **uniform network byte order** (big-enhdian byte order) * 因為 TCP/IP 有定義了自己的 byte orders, 所以即使 host byte order 不同, unix 可以透過已經定義好的 funciton 來轉換這些不同 network 及不同的 byte order * 使用 **decimal value and separated from the other bytes by a period** * e.g. 0x8002c2f2 * 被拆成: 0x80, 0x02, 0xc2, 0xf2 轉成對應10進制 * 變成:128, 2, 194, 242 * 並且用.隔開來 * 所以最終表示法變成 128.2.194.242 ```linux hostname -i // 顯示 ip 如 128.0.0.1 (一定是四個數字) ``` ### host order 與 network byte order ```c /* IP address structure*/ struct in_addr { uint32_t s_addr; /* Address in network byte order (big-endian) */ } ``` 主機可能有不同的 byte orders (big/little-endien),而存在 IP address structer 的 address 永遠是 network byte order (big-endien) ```c uint32_t htonl(uint32_t hostlong); uint32_t ntohl(uint32_t netlong); ``` `htonl` 將 32 位元整數從 host order 轉乘 network byte order,而 `ntohl` 將 network byte order 轉成 host byte order ### dotted-decimal notation IP address 通常會使用 dotted-decimal notation 表示(每個 byte 用十進位表示,每個 byte 之間用 . 分開) e.g. 128.2.194.242 = 0x8002c2f2 ```c #include <arpa/inet.h> int inet_pton(AF_INET, const char *src, void *dst); const char *inet_ntop(AF_INET, const void *src, char *dst, socklen_t size); ``` n: network p: presentation `inet_pton`: 將 dotted-decimal(src) 轉成 binary IP address in network byte order(dst) `inet_ntop`: 將 binary IP address in network byte order(src) 轉成 dotted-decimal(dst) ## Internet Domain Names Internet client & server 是透過 IP address 溝通, 但是這些數字很難被記得, 所以 Internet 定義了一個不同的 set, 這個 set 裡面都是一些對人類來說友善的名子, 這個 set 裡面可以是名字/ 數字/ dashes, 也是用 period(.) 分開, e.g. ncku.ics.cs.edu 之類的 ## Internet Connections 發送與接收 string of bytes over connections 來進行 client 與 server 間的溝通,是 **point-to-point**, 它會連結一對 processes ### Socket 以電話系統而言,只要將電話的插座設定好某一號碼,任何一部電話都可透過這個插座和其它電話通訊。對使用者而言,不用理會電話系統是如何撥接、或是如何路由選擇到達目的,這些功能完全由電話公司負責。網路上的 Socket 也如同電話的插座一樣,任何一個 Socket 都給予一個特殊號碼(IP number + TCP port),使用者之間只要記住對方的 Socket 號碼,便可以直接通訊,而不用考慮到底是經過何種網路、或主機放在什麼地方,這些尋找主機的工作是網路提供者所必須負責的。因此,對於 Socket 我們可以定義如下: 『Socket 就是一個網路上的通訊端點,使用者或應用程式只要連接到 Socket 便可以和網路上任何一個通訊端點連線,Socket 之間通訊就如同作業系統內程序(Process)之間通訊一樣。』 socket client 的 port 是在 client 發起 connection request 時由 kernel 自動分配的臨時 port,而 server 的 socket address 通常有固定的 port number ## Socket Interface Socket interface是指一組用於在應用程式中建立、傳輸和接收資料的 API,。它提供了一個標準化的方式,讓應用程式能夠使用網路協定(例如TCP、UDP等)來進行通訊,並在網路上傳輸資料。 ![](https://i.imgur.com/sOs3YWV.png) ### socket address structure `in_` suffix is short for internet ```c struct sockaddr_in { uint16_t sin_family; /* Protocol family (always AF_INET) */ uint16_t sin_port; /* Port number in network byte order */ struct in_addr sin_addr; /* IP address in network byte order */ unsigned char sin_zero[8]; /* Pad to sizeof(struct sockaddr) */ }; /* Generic socket address structure (for connect, bind, and accept) */ struct sockaddr { uint16_t sa_family; /* Protocol family */ char sa_data[14]; /* Address data */ }; ``` `connect` 、 `bind` 、 `accept` require a pointer to a protocol specific socket address structure. 遇到的問題是要怎麼讓 function 可以接收各種不同的 socket address structure,當時還沒有 `void *`,所以使用 `sockaddr` ### `socket` Function Client 與 server 使用 `socket` 來創建一個 socket descriptor ```c #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); Returns: nonnegative descriptor if OK, −1 on error ``` 讓 socket 成為 endpoint of connection, 則可以使用下列方法來 call socket ```c clientfd = Socket(AF_INET, SOCK_STREAM, 0); ``` - `AF_INET`: 32 bit IP address - `SOCK_STREAM`: indicates that the socket will be an end point for a connection 通常是用 `getaddrinfo` 來自動得到這些變數,讓程式可以在不同 protocol 中使用。 回傳的 `clientfd` 是部分開啟且目前還不能用於讀取和寫入,接下來的操作會依是 client 還是 server 決定,若是 client 則會呼叫 `connect`,server 會呼叫 `bind`。 ### `connect` Function client 呼叫 `connect` 與 server 建立連線 ```c #include <sys/socket.h> int connect(int clientfd, const struct sockaddr *addr, socklen_t addrlen); Returns: 0 if OK, −1 on error ``` - `addr`: server socket address - `addrlen`: 使用的 socket address 結構的大小,sizeof(sockaddr_in) 若成功則 `clientfd` descriptor 就可做讀取及寫入 ### `bind` Function 將 address 與 server socket 綁定,讓 server 知道自己的 address 與 port,讓 client 可以通過該 address 與 port 訪問 ```c #include <sys/socket.h> int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); Returns: 0 if OK, −1 on error ``` `bind` 函式會要求核心將 server 的 socket address (在 addr 參數中)與 `sockfd` socket descriptor 連接起來。`addrlen` 參數的值應為 sizeof(sockaddr_in)。 就像 socket() 和 connect() 一樣,使用 getaddrinfo() 函式提供 bind() 的引數是最佳做法 ### `listen` Function client 會主動建立 connection request,而 server 是被動等待 client 的 connection request。 預設情況下,核心假設 `socket()` 函式建立的 descriptor 對應到一個主動的 socket,該 socket 會存在於連接的客戶端端點。伺服器端需要呼叫 listen() 函式告訴核心該 socket 將用於 server 而不是 client。 ```c #include <sys/socket.h> int listen(int sockfd, int backlog); Returns: 0 if OK, −1 on error ``` 把 `sockfd` 從 active socket 轉換成可以從 client 接收 connection request 的 listening socket,`backlog` 代表 kernel 應保留的 request 數量,超過就要開始拒絕連線請求 ### `accept` Function Servers wait for connection requests from clients by calling the accept function. ```c #include <sys/socket.h> int accept(int listenfd, struct sockaddr *addr, int *addrlen); Returns: nonnegative connected descriptor if OK, −1 on error ``` 等待 client 的連接請求到達 listening descriptor `listenfd`,然後在 `addr` 填入 client socket address,最後回傳 connected descriptor,可以使用 Unix I/O 函式與 client 進行通信。 listening descriptor 用作 client 連接請求的端點。它通常只會被創建一次,並存在於 server 的整個生命週期中。connection request 是 client 和 server 之間建立的連接的端點。每當 server 接受連接請求時,就會創建一個已連接 connection request,並且只會存在於 server 為 client 提供服務的時間內。 ![](https://i.imgur.com/iYA83Ux.png) 1. server 調用 accept 函式,它等待連接請求到達 listening descriptor 上,這裡我們假設它是 descriptor 3(descriptor 0-2 保留用於標準文件) 2. client 調用 connect 函式,它向 listenfd 發送一個連接請求 3. accept 函式打開一個新的 connected descriptor connfd(我們假設它是 descriptor 4),建立 clientfd 和 connfd 之間的連接,然後將 connfd 返回給應用程序。此時,客戶端也從 connect 函式返回,從這個時刻開始,客戶端和服務器可以通過讀取和寫入分別的 clientfd 和 connfd 來傳遞數據。 ## Host and Service Conversion linux 提供 `getaddrinfo` 與 `getnameinfo`,可以在 binary socket address structure 與 主機名、主機地址、服務名稱以及端口號的字符串表示之間進行轉換,可以讓我們在寫網路程式時不依賴於任何特定版本的 IP protocol ### [getaddrinfo](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) 可以將 hostnames, host addresses, ports, service names 轉成對應的 struct sockaddr 結合 `gethostbyname` 和 `getservbyname`,且可 reentrant 與適用於 IPV4 及 IPV6 :::warning hostname: domain name host & service: 這裡指的是 socket address (ip : port) ::: ```c int getaddrinfo(const char *restrict node, const char *restrict service, const struct addrinfo *restrict hints, struct addrinfo **restrict res); ``` ![](https://i.imgur.com/wbkLSXU.png) 給定 host(node) 和 service(socket address 組成的兩部分),回傳指到一個 `addrinfo` 的 linked list,每個 node 都指向對應到 host and service 的 socket address structure - client 呼叫: 遍歷這個 list,並嘗試對每個 socket address 進行 socket + connect 直到成功 - server 呼叫: 遍歷這個 list,並嘗試對每個 socket address 進行 socket + bind 直到成功 - list 需要透過 `freeaddrinfo` 釋放 - 如果 getaddrinfo 返回非零的 error code,可以呼叫 `gai_streeror` 得到對應的錯誤訊息 ```c struct addrinfo { int ai_flags; /* Hints argument flags */ int ai_family; /* First arg to socket function */ int ai_socktype; /* Second arg to socket function */ int ai_protocol; /* Third arg to socket function */ char *ai_canonname; /* Canonical host name */ size_t ai_addrlen; /* Size of ai_addr struct */ struct sockaddr *ai_addr; /* Ptr to socket address structure */ struct addrinfo *ai_next; /* Ptr to next item in linked list */ }; ``` - ai_family: `getaddrinfo` 可支援 IPv4 與 IPv6,設定成 `AF_INET` 可限制 list 為 IPv4 address, `AF_INET6` 可限制為 IPv6 - ai_socktype: `getaddrinfo` 可回傳三種不同的 `addrinfo` structure This field specifies the preferred socket type, for example SOCK_STREAM or SOCK_DGRAM. Specifying 0 in this field indicates that socket addresses of any type can be returned by getaddrinfo(). - ai_flags: - ai_protocol: ### `getnameinfo` 將 socket address structure 轉回對應的 host 、 service name ## Echo Client and Server Echo client 和 server 是電腦網路中常用的通訊模型之一。基本上,伺服器會監聽來自客戶端的連線,並回傳相同的資料給客戶端。這種通訊方式常用於測試和除錯網路連線和應用程式。 Echo server 會在特定的埠口上監聽連線,並將從客戶端收到的任何資料都發送回給該客戶端。另一方面,Echo client 則會與 Echo server 建立連線,並向其發送資料。然後,伺服器會將相同的資料發送回客戶端。這使得開發人員可以測試網路連線和傳送資料的可靠性。 「Echo」一詞指的是伺服器僅回傳從客戶端接收到的相同資料。這種通訊模型通常用於簡單的測試目的,不太會用於實際的生產環境中。 ### What does EOF on a connection mean? First, we need to understand that there is no such thing as an EOF character. Rather, EOF is a condition that is detected by the kernel. An application finds out about the EOF condition when it receives a zero return code from the read function. - For disk files, EOF occurs when the current file position exceeds the file length. - For Internet connections, EOF occurs when a process closes its end of the connection. The process at the other end of the connection detects the EOF when it attempts to read past the last byte in the stream. ## Web Servers Web client 和 server 使用一種稱為 HTTP(hypertext transfer protocol)的基於文本的應用層協議進行互動。Web client(browser)打開一個 Internet connection 連接到 server 並請求一些內容。server 響應所請求的內容,然後關閉連接。瀏覽器讀取內容並顯示。 ### Web Content To Web clients and servers, content is a sequence of bytes with an associated MIME (multipurpose internet mail extensions) type. ![](https://i.imgur.com/h37p7nx.png) #### Web server 用兩種方式提供 content 給 client - 檢索 disk file 並將其內容返回給 client。disk file 被稱為靜態內容,將文件返回給 client 的過程被稱為 serving static content - 執行 executable file 並將其輸出返回給 client。執行時可執行文件產生的輸出被稱為動態內容,運行程序並將其輸出返回給 client 的過程被稱為提供動態內容 每個由 Web 伺服器返回的內容都與其管理的某個文件相關聯。這些文件中的每一個都有一個稱為 URL(universal resource locator)的唯一名稱,例如,URL http://www.google.com:80/index.html 識別了一個名為 /index.html 的 HTML 檔案,在聆聽 80 端口的 Web 伺服器上管理,該伺服器位於 Internet host www.google.com 上。 executable files 的 URL 可以在檔案名稱後包含 program arguments。'?' 字元將檔案名稱與引數分開,每個引數由 '&' 字元分隔。例如,URL http://bluefish.ics.cs.cmu.edu:8000/cgi-bin/adder?15000&213 ,有一個可執行檔被命名為 /cgi-bin/adder,將會被使用兩個參數字串 15000 和 213 呼叫。在 Transactions 過程中,客戶端和伺服器使用不同的URL部分。例如,客戶端使用前綴 http://bluefish.ics.cs.cmu.edu:8000 來確定要聯絡哪種類型的 server、server 的位置以及所監聽的 port。而 server 則使用後綴/index.html來找尋其檔案系統上的檔案,並判斷該請求是否為靜態或動態內容。 #### server 如何理解 URL 後綴: 沒有標準的規則來判斷 URL 是否是靜態或動態內容。每個 server 都有自己管理檔案的規則。一種經典的方法是識別一組目錄,例如 cgi-bin,所有執行檔都必須位於其中。 後綴中的初始斜線符號 `/` 不表示 Linux 根目錄。它代表所要求內容的主目錄。例如,一個 server 可能被配置為所有靜態內容存儲在目錄/usr/httpd/html中,而所有動態內容則存儲在目錄/usr/httpd/cgi-bin中。 最簡單的 URL 後綴是 `/`,所有 server 都會將其擴展為某些預設主頁,例如 /index.html 。這解釋了為什麼只需在瀏覽器中輸入網域名稱就可以瀏覽網站主頁。瀏覽器會將缺少的 `/` 附加到URL中,然後將其傳遞給 server,server 會將 `/` 擴展為某個預設的檔案名稱。 ### HTTP Transactions(傳輸) 由於 HTTP 是基於通過 Internet 連接傳輸的 text line,因此我們可以使用 Linux 的 telnet 程式與 Internet 上的任何 Web server 進行 transactions。Telnet 程式已被 ssh 作為遠程登錄工具所取代,但對於與客戶端通過連接使用 text line 交流的伺服器進行使用非常方便。 #### 使用 telnet 從 AOL Web 伺服器請求主頁。 ![](https://i.imgur.com/SMvz8r6.png) 在第1行中,從 Linux shell 運行 telnet 並要求其打開 AOL Web server 的連接。Telnet 將三行輸出打印到 terminal,打開連接,然後等待我們輸入文字(第5行)。每次我們輸入一個文本行並按下 enter 時,telnet讀取該行,附加換行符(C 中為“\r\n”),並將該行發送到伺服器。這與 HTTP標準一致,該標準要求每個文本行以換行符終止。為了啟動 transactions,我們輸入了一個 HTTP 請求(第5-7行)。伺服器用HTTP回應進行回覆(第8-17行),然後關閉連接(第18行)。 #### HTTP Requests An HTTP request consists of a request line (line 5), followed by zero or more request headers (line 6), followed by an empty text line that terminates the list of headers (line 7). A request line has the form method(`Get`) URI(`/`) version(`HTTP/1.1`) The GET method 指示 server to 生成並回傳由 URL (uniform resource identifier)識別的內容。 URI是相應URL的後綴,其中包括 filename 和 optional arguments request line 中的版本欄位表示請求符合的HTTP版本。最新的 HTTP 版本是HTTP / 1.1 。 HTTP / 1.0 是1996年的早期、簡單得多的版本。HTTP / 1.1 定義了額外的標頭,提供對進階功能(如緩存和安全性)的支持,以及一種允許 client 和 server 在同一持久連接上執行多個 transactions 的機制。在實踐中,這兩個版本是相容的,因為 HTTP / 1.0 client 和 server 只是忽略未知的 HTTP / 1.1標頭。 第5行的請求行要求 server 擷取並返回 HTML 文件 `/index.html`,同時通知 server request 的其餘部分將以 HTTP / 1.1 格式進行。 Request headers 提供額外的資訊給 server,例如瀏覽器的品牌名稱或瀏覽器了解的 MIME 類型。請求標頭的格式為: header-name: header-data 對於我們的目的而言,唯一需要關注的標頭是 host header(第6行 `Host: www.aol.com`),這在 HTTP/1.1 請求中是必需的,但在 HTTP/1.0 請求中不是必需的。主機標頭由 proxy caches 使用,有時它們作為瀏覽器和管理所請求檔案的起源 server 之間的中介。在所謂的 proxy chain 中,可以存在多個代理。主機標頭中的資料,它識別了起源 server 的域名,允許代理在代理鏈的中間確定它是否具有所請求的內容的本地快取副本。 第7行中的空文本行(通過在鍵盤上按 Enter 鍵生成)結束了 header,並指示 server 發送請求的 HTML 檔案。 #### Http Response Http Response 回應包含一個回應行 response line (line 8 `HTTP/1.0 200 OK`) response line 的格式為: version (HTTP/1.0) status-code (200) status-message (OK) - version: response 的 HTTP 版本HPPT 版本 - status-code: 三位數的正整數,用於表示 request 的處理情況 - status-message status-code 對應的 message ![](https://i.imgur.com/6quxiwx.png) 第 9-13 行的 response header 提供有關回應的其他信息。對於我們來說,最重要的兩個標頭是 Content-Type(第12行 `Content-Type: text/html`),它告訴客戶端 response body 中內容的 MIME 類型,以及 Content-Length(第13行 `Content-Length: 42092`),它指示 response body 的大小(以字節為單位)。在第14行中終止回應標頭的空文本行後面是 response body (第15-17行),其中包含請求的內容。 ### Serving Dynamic Content client 如何將任何程式引數傳遞給 server?server 如何將這些引數傳遞給它所建立的子程序?server 如何將其他子程序需要生成內容的信息傳遞給它?子程序將其輸出發送到哪裡?這些問題由一個名為 CGI(Common Gateway Interface,通用網關介面)的非官方標準所解決。 #### How Does the Client Pass Program Arguments to the Server? GET 請求的參數是通過 URI 傳遞。`?` 將文件名與參數分隔開來,每個參數之間用 `&` 分隔。參數中不允許有空格,必須用 `%20` 字符串表示。其他特殊字符也有類似的編碼方式。 #### How Does the Server Pass Arguments to the Child? 當服務器收到類似下面這樣的請求: GET /cgi-bin/adder?15000&213 HTTP/1.1 它會調用 `fork` 來創建一個子進程,然後調用 `execve` 在子進程的上下文中運行 /cgi-bin/adder 程式。像 adder 這樣的程式通常被稱為 CGI 程式,因為它們遵循 CGI 標準的規則。在調用 `execve` 之前,子進程將 CGI 環境變量 QUERY_STRING 設置為 15000&213,這樣adder程式可以在運行時使用 Linux 的 `getenv` 函式來引用它。 #### How Does the Server Pass Other Information to the Child? CGI 定義了許多其他環境變數,CGI 程式可以在運行時期望這些變數被設置 ![](https://i.imgur.com/kOqSBEg.png) #### Where Does the Child Send Its Output? 一個 CGI 程式將其動態內容發送到標準輸出。在子行程載入並運行 CGI 程式之前,它使用 Linux `dup2` 函式將標準輸出重定向到與 client 關聯的 connected descriptor. 因此,CGI 程式寫入標準輸出的任何內容都直接發送到 client 。 需要注意的是,由於父行程不知道子行程生成的內容的類型或大小,子行程負責生成 Content-type 和 Content-length response header,以及終止標頭的空行。 ## Putting It Together: The Tiny Web Server