71-recvmsg 和 sendmsg 函式
這兩個函式只適用於套接字描述符。read、readv、recv 和 recvfrom 能用的地方,recvmsg 都能使用,而且,recvmsg 能提供更多的功能。同樣的,各種 output 型別的函式都可以替換成 sendmsg 函式。
所以,recvmsg 和 sendmsg 是之前我們學過的讀寫類函式的究極形態。這麼強大的函式,使用起來也會相當的複雜,在本文,我們只討論它的一部分功能,剩下的功能,以後再說。
1. 函式原型
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
這兩個函式,把大多數的引數都放進了一個 struct msghdr 型別的結構體。接下來,我們就來看看這個結構體的樣子。
2. msghdr{}
struct msghdr {
void *msg_name; /* 套接字地址 */
socklen_t msg_namelen; /* 地址長度 */
struct iovec *msg_iov; /* 散佈/聚焦陣列 */
size_t msg_iovlen; /* msg_iov 的元素個數 */
void *msg_control; /* 輔助資料 */
size_t msg_controllen; /* 輔助資料大小 */
int msg_flags; /* 接收資料的標誌,僅用於 recvmsg */
};
圖1 msghdr{} 結構體
- msg_name 和 msg_namelen
這兩個成員僅用於無連線的場合(比如無連線的 UDP 套接字)。它相當於 recvfrom 和 sendto 的最後兩個引數。
msg_name 指向一個套接字地址結構,msg_namelen 則是該套接字地址結構的大小。對於 sendto 來說,msg_name 存入接收者的地址。對於 recv_msg 來說,它用來接收返回值(值-結果引數)。
- msg_iov 和 msg_iovlen
這兩個成員和 writev 和 readv 函式沒什麼兩樣。
- msg_control 和 msg_controllen
這兩個成員表示輔助資料的位置和大小。對於 recvmsg 來說,這個引數也是一個值-結果引數。關於輔助資料的含義,我們先不討論它,以後我們遇到它的時候,再進行詳細學習。因為就目前來說,我們還用不上它。
- msg_flags
這個成員只針對 recvmsg 函式有效。
recvmsg 被呼叫時,recvmsg 的最後一個引數 flags 的值會被複制到 msg_flags 成員並交由核心使用。當 recvmsg 返回時,msg_flags 會儲存返回結果,因此 msg_flags 也是一個值結果引數。msg_flags 的返回結果可能是下面這些值:MSG_EOR、MSG_OOB、MSG_BCAST、MSG_MCAST、MSG_TRUNC、MSG_CTRUNC、MSG_NOTIFCATION. 千萬別暈菜,這些東西你目前來說不需要去理解它是幹嘛的……所以我們目前忽略掉它。
sendmsg 要想使用 flags,只能使用 sendmsg 函式的最後一個引數,而不應該使用 msg_flags 成員。
3. 實驗
根據第 2 節中的情況,我們掌握前 4 個 msghdr{} 的前 4 個成員:msg_name, msg_namelen, msg_iov, msg_iovlen 即可。實驗中,我們將之前寫的 udp 回射程式進行改寫,將 sendto 和 recvfrom 改成 sendmsg 和 recvmsg 即可。
程式路徑:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已經 clone 過這個程式碼了,請使用 git pull
更新一下。本節程式所使用的程式路徑是 unp/program/advcio/rwmsg
udp 回射程式主要修改了兩個地方:
- 將客戶端的 sendto 修改成了 sendmsg
- 將伺服器的 recvfrom 修改成了 recvmsg
3.1 程式程式碼
- 伺服器端關鍵程式碼
void doServer(int sockfd) {
char buf[4096];
int nr, nw;
struct sockaddr_in cliaddr;
struct msghdr msg;
struct iovec iov;
socklen_t len;
while(1) {
// 填充 msghdr{} 結構體
iov.iov_base = buf;
iov.iov_len = 4096;
msg.msg_name = &cliaddr;
msg.msg_namelen = sizeof(cliaddr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = NULL;
msg.msg_controllen = 20;
msg.msg_flags = 0;
// 接收資料
nr = recvmsg(sockfd, &msg, 0);
if (nr < 0) {
if (errno == EINTR) continue;
ERR_EXIT("recvfrom");
}
// 後面的部分是將資料處理後傳送給客戶端
// ...
}
}
}
- 客戶端程式碼
void doClient(int sockfd) {
int ret, len, nr, nw;
struct sockaddr_in servaddr;
char prompt[64];
char buf[4096];
struct msghdr msg;
struct iovec iov[2];
strcpy(prompt, "send: ");
ret = resolve(g_option.hostname, g_option.port, &servaddr);
if (ret < 0) ERR_EXIT("resolve");
while(1) {
nr = iread(STDIN_FILENO, buf, 4096);
if (nr < 0) {
ERR_EXIT("iread");
}
else if (nr == 0) break;
// 填充 msghdr{} 結構體
iov[0].iov_base = prompt;
iov[0].iov_len = strlen(prompt);
iov[1].iov_base = buf;
iov[1].iov_len = nr;
msg.msg_name = &servaddr;
msg.msg_namelen = sizeof(servaddr);
msg.msg_iov = iov;
msg.msg_iovlen = 2;
msg.msg_control = NULL;
msg.msg_controllen = 0;
msg.msg_flags = 0;
nw = sendmsg(sockfd, &msg, 0);
// 從伺服器接收資料
// ...
}
}
3.2 執行結果
- 客戶端
圖2 客戶端執行結果
- 伺服器
圖3 伺服器執行結果
4. 總結
- 掌握 recvmsg 和 sendmsg 的基本用法