1. 程式人生 > >71-recvmsg 和 sendmsg 函式

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_EORMSG_OOBMSG_BCASTMSG_MCASTMSG_TRUNCMSG_CTRUNCMSG_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 的基本用法