非同步與高效能輸入輸出

短讀/短寫的現實(partial read / short write)

你向販賣機一次要一千份零食,但它槽裡現在只有 340 份,於是它給你 340 份、並告訴你目前就這些。它並沒有失敗——它只是給了比你要的更少。這就是串流型描述符上 read() 與 write() 的日常現實:它們搬移的位元組可能「比你要求的少」,而把它們當成全有或全無,是網路程式碼中最常見的錯誤之一。

具體來說:read(fd, buf, 4096) 回傳它實際放入的位元組數,可以是 1 到 4096 之間的任何數(或在檔案結尾時為 0、出錯時為 -1)。在通訊端上,資料可能只是尚未全部抵達。write(fd, buf, 4096) 同樣回傳它實際接受的數量,當核心的送出緩衝區已部分填滿時,這可以小於 4096——一次「短寫」。正確的紀律是一個追蹤進度的迴圈:保留一個累進的偏移量,每次呼叫從該偏移量讀或寫「剩餘」的位元組,依回傳的數量前進,只有在你把全部搬完(或遇到 EOF/錯誤)時才停止。在非阻塞描述符上,一個 EAGAIN 的短結果意味「現在沒有更多了」——你存下位置,等就緒再次觸發時恢復。

為何重要:在大規模下,搭配非阻塞通訊端與部分填滿的核心緩衝區,短讀與短寫是「正常」情形、而非罕見邊角。一個假設「一次 write() 就送完整個回應」的伺服器,在負載下會無聲地截斷訊息;一個假設「一次 read() 回傳一整則訊息」的伺服器,會把資料切錯框。這也是為何緩衝區管理如此要緊——你必須保留一則回應尚未送出的尾巴,並把跨好幾次讀取、分段抵達的訊息重新組裝起來,因為你在意的邊界是你的,不是核心的。

size_t off = 0; while (off < len) { ssize_t n = write(fd, buf + off, len - off); if (n < 0) { if (errno == EAGAIN) break; /* 稍後恢復 */ else /* 錯誤 */ } off += (size_t)n; }

從累進偏移量對剩餘位元組做迴圈;永遠不要假設一次 write() 就把整個緩衝區排空了。

短讀/短寫不是錯誤——一個比要求更小的正數是正常的,尤其在通訊端與非阻塞描述符上。錯誤在於把該呼叫當成全有或全無;修正之道永遠是對剩餘部分做迴圈、並追蹤你自己的訊息邊界,因為 TCP 是沒有框界的位元組串流。

又稱
short reads and short writespartial I/O短讀與短寫