背景
復習 socket 編程的時候發現了以前沒有留意到的 2個函數:recvmsg
和 sendmsg
知識
先來看看函數原型:
#include <sys/types.h>
#include <sys/socket.h>
...
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
...
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; // protocol address
socklen_t msg_namelen; // size of protocol address
struct iovec *msg_iov; // scatter/gather array
int msg_iovlen; // elements in msg_iov
void *msg_control; // ancillary data (cmsghdr struct)
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
msg_name 和 msg_namelen
這兩個成員用於套接字未連接的場合(如未連接 UDP 套接字)。它們類似 recvfrom 和 sendto 的第五個和第六個參數:
- msg_name 指向一個套接字地址結構,調用者在其中存放接收者(對於 sendmsg 調用)或發送者(對於recvmsg調用)的協議地址。如果無需指明協議地址(如對於 TCP 套接字或已連接 UDP 套接字),msg_name 應置為空指針。
- msg_namelen 對於 sendmsg 是一個值參數,對於 recvmsg 卻是一個值-結果參數。
msg_iov 和 msg_iovlen
這兩個成員指定輸入或輸出緩沖區數組(即iovec結構數組),類似 readv 或 writev 的第二個和第三個參數。
msg_control 和 msg_controllen
這兩個成員指定可選的輔助數據的位置和大小。msg_controllen 對於 recvmsg 是一個值-結果參數。
flags
對於 recvmsg 和 sendmsg,必須區別它們的兩個標志變量:
- 一個是傳遞值的 flags 參數;
- 另一個是所傳遞 msghdr 結構的 msg_flags 成員,它傳遞的是引用,因為傳遞給函數的是該結構的地址。
只有 recvmsg 使用 msg_flags 成員。recvmsg 被調用時,flags 參數被復制到 msg_flags 成員,並由內核使用其值驅動接收處理過程。內核還依據 recvmsg 的結果更新 msg_flags 成員的值。
sendmsg 則忽略 msg_flags 成員,因為它直接使用 flags 參數驅動發送處理過程。這一點意味着如果想在某個 sendmsg 調用中設置 MSG_DONTWAIT 標志,那就把 flags 參數設置為該值,把 msg_flags 成員設置為該值不起作用。
recvmsg 返回的 7 個標志如下:
- MSG_BCAST:本標志隨 BSD/OS 引入,相對較新。它的返回條件是本數據包作為鏈路層廣播收取或者其目的 IP 地址是一個廣播地址。與 IP_RECVD-STADDR 套接字選項相比,本標志是用於判定一個 UPD 數據包是否發往某個廣播地址的更好方法。
- MSG_MCAST:本標志隨 BSD/OS 引入,相對較新。它的返回條件是本數據報作為鏈路層多播收取。
- MSG_TRUNC:本標志的返回條件是本數據報被截斷,也就是說,內核預備返回的數據超過進程事先分配的空間(所有 iov_len 成員之和)。
- MSG_CTRUNC:本標志的返回條件是本數據報的輔助數據被截斷,也就是說,內核預備返回的輔助數據超過進程事先分配的空間(msg_controllen)。
- MSG_EOR:本標志的返回條件是返回數據結束一個邏輯記錄。TCP 不使用本標志,因為它是一個字節流協議。
- MSG_OOB:本標志絕不為 TCP 帶外數據返回。它用於其他協議族(如 OSI 協議族)。
- MSG_NOTIFICATION:本標志由 SCTP 接收者返回,指示讀入的消息是一個事先通知,而不是數據消息。
圖解
下圖展示了一個 msghdr 結構以及它指向的各種信息。圖中假設進程即將對一個 UDP 套接字調用 recvmsg:
圖中給協議地址分配了 16 個字節,給輔助數據分配了 20 個字節。為緩沖數據初始化了一個由 3 個 iovec 結構構成的數組:第一個指定一個 100 字節的緩沖區,第二個指定一個 60 字節的緩沖區,第三個指定一個 80 字節的緩沖區。假設已為這個套接字設置了 IP_RECVDSTADDR 套接字選項,以接收所讀取 UDP 數據包的目的 IP 地址。
假設從 198.38.100:2000 到達一個 170 字節的 UDP 數據報,它的目的地是我們的 UDP 套接字,目的 IP 地址為 206.168.112.96.下圖展示了 recvmsg 返回時 msghdr 結構中的所有信息。
圖中被 recvmsg 修改過的字段標上了陰影。從第一幅圖到第二幅圖的變動包括以下幾點:
- 由 msg_name 成員指向的緩沖區被填以一個網際網套接字地址結構,其中有所收到數據報的源 IP 地址和源 UPD 端口號。
- msg_namelen 成員(一個值-結果參數)被更新為存放在 msg_name 所指緩沖區中的數據量。本成員並無變化,因為 recvmsg 調用前和返回后其值均為 16.
- 所收取數據報的前 100 個字節數據存放在第一個緩沖區,中 60 字節數據存放在第二個緩沖區,后 10 字節數據存放在第三個緩沖區。最后那個緩沖區的后 70 字節沒有改動。recvmsg 函數的返回值(即 170)就是該數據報的大小。
- 由 msg_control 成員指向的緩沖區被填以一個 cmsghdr 結構。該 cmsghdr 結構中,cmsg_len 成員值為 16,cmsg_level 成員值為 IPPROTO_IP,cmsg_type 成員值為 IP_RECVDSTADDR,隨后 4 個字節存放所收到 UDP 數據報的目的 IP 地址。這個 20 字節緩沖區的后 4 個字節沒有改動。
- msg_controllen 成員被更新為所存放輔助數據的實際數據量。本成員也是一個值-結果參數,recvmsg 返回時其結果為 16。
- msg_flags 成員同樣被 recvmsg 更新,不過沒有標志返回給進程。