send 與 recv(傳送與接收)
/ recv -> reh-SEEV /
一旦兩個程式連上了,整件事的重點就是來回傳遞位元組。send() 把位元組從你的通訊端推向對方;recv() 把從對方抵達的位元組拉進你的緩衝區。它們是一段已建立對話的兩個動詞——你真正想溝通的一切,都透過這兩個呼叫發生。
它們如何運作:recv(fd, buf, len, flags) 向核心索取至多 len 個已抵達通訊端 fd 的位元組,把它們複製進你的緩衝區 buf,並回傳它實際給了你多少(可能比你要的少)。回傳 0 代表對方關閉了連線——那是你的檔案結尾訊號。send(fd, buf, len, flags) 把 buf 中至多 len 個位元組交給核心去傳送,並回傳它實際排入佇列了多少(同樣,可能比 len 少)。在通訊端上,read() 與 write() 做的幾乎是同一件事;send() 與 recv() 只是多了一個 flags 引數,提供網路專屬選項。因為兩個呼叫處理的都是「至多」的數量,真實的程式碼會用迴圈把它們包起來。
為何重要:這是資料真正移動的呼叫,而它們誠實的行為常絆倒新手。recv() 不會回傳完整的訊息——它回傳的是目前為止抵達的任何位元組,可能是某則訊息的一個片段,或好幾則訊息的零碎拼湊。send() 在核心的送出緩衝區已滿時,可能只接受你資料的一部分。這兩個事實都意味著:你不能假設這端的一次 send() 等於對端的一次 recv();你必須用迴圈,並追蹤你已搬移了多少。把通訊端當成一條乾淨的訊息通道、而非原始的位元組串流,是最常見的網路程式錯誤。
ssize_t n = recv(fd, buf, sizeof buf, 0); if (n == 0) { /* 對端已關閉 */ } else if (n < 0) { /* 錯誤,檢查 errno */ } else { /* 取得 n 個位元組,1 <= n <= sizeof buf */ } 注意你要處理三種情況:已關閉(0)、錯誤(負值)、有資料(正值但可能是部分)。
recv() 回傳至多你要求的量;0 代表已關閉、負值代表錯誤——三種一定都要檢查。
recv() 回傳 0 代表對方有序關閉,而非「目前還沒資料」——別在它上頭無限迴圈。而且 send() 與 recv() 都不保證一次呼叫就搬完所有位元組;假設它們會,是一個經典而隱密的錯誤。