sysprog2020
完成 CS:APP 3/e 第 11 章的 Homework 11.6 到 11.13
結果:未完成
Github
此題為讓 Tiny 將從 client 端收到的 request line 和 request header 回傳給 client 端,原本的 Tiny server 是以 doit function 讀取 request line 和 header,再用 parse_uri function 判斷 web content 是 static 還是 dynamic,最後回傳 response headers 和 body 給 client。
將 Tiny server 裡的 doit function 改為 echo function,把 request line 和 header 作為 response body 回傳給 client 端。
修改 echo
function 取代 doit
,使用 open
function 創造一個 logfile
檔案,在寄送 response body 時也寫入檔案。
根據 logfile 的內容可以得知 Chrome 使用的 HTTP version 為 1.1。
下方為 logfile 的內容,URI 為 /favicon.ico 是因為 Chrome 會自動送出第二個 http request,內容就是向 server 要 favicon.ico 檔案,所以 logfile 裡原本的 request line 和 header 被覆蓋掉了,favicon 為 favorites icon 的縮寫,詳情請見 wiki Favicon。
在 RFC2616 Section 14 有 header
的定義,如:Accept
在 section 14.1,Referer
在 section 14.36,User-Agent
在 section 14.36,但是 DNT(Do Not Track)
呢?,我並沒有在 RFC Editor
找到有關 DNT header
的文件,只有在 W3C 找到 Tracking Preference Expression (DNT)。
瀏覽器會根據 http response 的 MIME type,來判定如何對URL進行處理,所以伺服器要在 http response 的 Content-Type header 裡放入正確的 MIME type,否則瀏覽器很有可能轉譯錯誤或是無法正常運作,造成下載的檔案無法被正常處理。
Tiny server 是用 get_filetype
function 去判斷檔案的類別,在 function 裡多加處理 MPG 檔的程式碼( line 14~15 ),檢查 URI 裡的檔名裡有沒有.mpeg
判斷是否為 MPEG video file,讓瀏覽器可以下載 MPG 的影片檔。
國際網路號碼分配局(Internet Assigned Numbers Authority, IANA) 負責所有的MIME類別,可以從他們的 Media Types 頁面找到最新且完整的類別清單。
測試結果:
Server output:
SIGCHLD
handler instead of explicitly waiting for them to terminate.在 CSAPP 第八章第五小節有提到:
A
signal
is a small message that notifies a process that an event of some type has occurred in the system.
SIGCHLD
為一種 Linux signal,當 child process "stopped" 或 "terminated" 時,parent 就會收到此 signal,通知 parent 有 child 停止或結束了。
CSAPP 第八章的 sigchld_handler:
用 while loop 的原因在於當 signal handler 在執行時,如果又有複數個 child 執行完,系統只會保留一個 SIGCHLD,所以除了第一次的 SIGCHLD 以外,之後的 SIGCHLD 都只代表 至少 有一個 child 發出 SIGCHLD signal,所以 while 是處理可能不只一個 child 發出 SIGCHLD signal 的情況。
CSAPP 第八章第五小節就有說明 handler 一次只處理一個 child process 可能發生的情況:
The first signal is received and caught by the parent.While the handler is still processing the first signal, the second signal is delivered and added to the set of pending signals. However, since SIGCHLD signals are blocked by the SIGCHLD handler, the second signal is not received. Shortly thereafter, while the handler is still processing the first signal, the third signal arrives. Since there is already a pending SIGCHLD, this third SIGCHLD signal is discarded. Sometime later, after the handler has returned, the kernel notices that there is a pending SIGCHLD signal and forces the parent to receive the signal. The parent catches the signal and executes the handler a second time. After the handler finishes processing the second signal, there are no more pending SIGCHLD signals, and there never will be, because all knowledge of the third SIGCHLD has been lost.
The crucial lesson is that signals cannot be used to count the occurrence of events in other processes.
waitpid
的第二個參數 status
(在上面的例子是傳 NULL
進去),根據 man page:
If status is not NULL, wait() and waitpid() store status information in the int to which it points. This integer can be inspected with the following macros (which take the integer itself as an argument, not a pointer to it, as is done in wait() and waitpid()!)
以這題而言只是要 reap child process
的確不需要 child 的 exit status
,以 web server 的角度,有哪些情況會需要 child 的 exit status
?
加上 handler parent 就不需要在 serve dynamic 執行 wait,如果不讓 parent 在 serve dynamic 執行 wait
,那假如 parent 先回到 main 執行 Close(connfd)
,cgi child 執行的寫入還有用嗎?
答案是有用的,這牽扯到 CSAPP 9.8 Memory Mapping
Kernel 如何產生 child process、 10.8 Sharing Files
中 Kernel 是如何紀錄 open files (三個 data structure),在 fork
後,child 和 process 都有自己的 Descriptor table,而 child 的 Descriptor table 便是從 parent 的 Descriptor table 複製出來的,假設 parent 是 fd 3
reference 到 socket file (每個 process 一開始預設 fd 0
、fd 1
、fd 2
分別是 stdin
、stdout
、stderr
),那 child 也是,當 parent 執行 Close(connfd)
,只有 close parent 的 Descriptor table 裡的 connfd
,所以 child 的 connfd
still refenence 到同樣的 socket file,在 child 執行完 adder
並執行 exit
後,child 所擁有的資源都會被 kernel 釋放,socket file 的 reference count 減為 0,socket file 被 kernel 刪除。
malloc
, rio_readn
, and rio_writen
, instead of mmap
and rio_writen
.HTML form:
form data
要送往 server 的路徑method
CSAPP 提供的 adder.c 預設 URL 後面給程式的參數字串是 number&number
的格式
http://140.116.245.27:5000/cgi-bin/adder?number&number
但實際執行格式為 name=number&name=number
http://140.116.245.27:5000/cgi-bin/adder?name=number&name=number
所以要修改 adder.c 取參數的部分(line 11~19 )
adder.c:
修改成 adder4Web.c
:
strchr
搜尋 =
字元:sscanf
function:Test result:
在 RFC7231 Section 4.3 Method Definitions 有寫所有的 method 定義 (RFC2616 Section 9 也有):
The HEAD method is identical to GET except that the server MUST NOT send a message body in the response (i.e., the response terminates at the end of the header section). The server SHOULD send the same header fields in response to a HEAD request as it would have sent if the request had been a GET, except that the payload header fields MAY be omitted.
REQUEST_METHOD
(line 13)。telnet test result:
method
為 POST
的表格read_requesthdrs
、doit
functionThe POST method requests that the target resource process the representation enclosed in the request according to the resource's own specific semantics.
也就是說原本要傳給 adder 的參數不會放在 URL 裡,會放在 request message body 送給 server。
由於 request message body 最後不是以'\n'
結尾,又因為 Rio_readlineb
的終止條件是讀取到換行符號,所以在 read_requesthdrs
裡不能使用 Rio_readlineb
讀取 message body
,否則會卡住,需使用 Rio_readnb
,這裡的修改是讓 read_requesthdrs
只讀取 header
與 區隔 body
與 header
最後結尾的 CRLF
,由 doit
讀取 body
,adder_POST
與 11.10 的 adder11.10 是一樣的不需作修改。
read_requesthdrs
:
Content-Length:
header 找出 message body 有幾個字元並回傳。doit
:
content_len
變數存 message body
有幾個字元Rio_readnb
讀取 message body
至 buf array
(用 buf array
存參數)method
為 POST 時將 buf array
傳入 serve_dynamic
(因為用 buf array
存參數,非 cgiargs
)測試結果:
SIGPIPE
signals and EPIPE
errors that occur when the write function attempts to write to a prematurely closed connection.SIGPIPE
signals ?CSAPP 第11章第1000頁的 Aside
有提到 SIGPIPE
signals 的產生及為什麼要處理它(防止 server 因為與 client 連線中斷而停止):
For example, if a server writes to a connection that has already been closed by the client (say, because you clicked the “Stop” button on your browser), then the first such write returns normally, but the second write causes the delivery of a SIGPIPE signal whose default behavior is to terminate the process. If the SIGPIPE signal is caught or ignored, then the second write operation returns −1 with errno set to EPIPE. The strerr and perror functions report the EPIPE error as a “Broken pipe,” a nonintuitive message that has confused generations of students. The bottom line is that a robust server must catch these SIGPIPE signals and check write function calls for EPIPE errors.
是在 client 斷線後 server 寫入第二次時才會產生 SIGPIPE
signal
Tiny server 都是用 Rio_writen
寫入資料,而 Rio_writen
是 CSAPP 打包的函式,加上了 error-handling。
Rio_writen
:
接著看 rio_writen
:
write
因為 signal handler 中斷或沒寫入 n bytes 至 fd 就繼續呼叫 write
write
失敗就 return -1再來是 write
:
節錄至 Linux man page
The write function copies at most n bytes from memory location buf to the
current file position of descriptor fd.
buf
複製 "最多" n bytes 到 file descriptor fd
。Upon successful completion, write() shall return the number of bytes actually written to the file associated with fd. This number shall never be greater than n byte. Otherwise, -1 shall be returned and errno set to indicate the error.
The write() function shall fail if:
EPIPE
- A write was attempted on a socket that is shut down for writing, or is no longer connected. In the latter case, if the socket is of type SOCK_STREAM, the SIGPIPE signal is generated to the calling process.
Rio_writen
在
rio_writen
失敗後要去檢查 error 是否為 EPIPE
,
unix_error
,rio_writen
一樣回傳 -1 (採用 Tiny web server 的方法)。rio_writen
成功後回傳 0修改後的 Rio_writen
: