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

sendmsg 和 recvmsg 函式

1. 基礎介紹

  最通用的I/O函式,只要設定好引數,read、readv、recv、recvfrom和write、writev、send、sendto等函式都可以對應換成這兩個函式來呼叫。同時,各種輸出函式呼叫也可以替換成sendmsg呼叫。

#include <sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssizt_t sendmsg(int sockfd, struct msghdr *msg, int flags);

大部分引數都在 msghdr結構中

struct iovec
{                   /* Scatter/gather array items */
    void *iov_base; /* Starting address */
    size_t iov_len; /* Number of bytes to transfer */
};

struct msghdr
{
    void *msg_name;        /* optional address */
    socklen_t msg_namelen; /* size of address */
    struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ size_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };

struct msghdr 結構體引數說明:

  • msg_name : 指向一個套接字地址結構,用於存放接受者或者傳送者的協議地址。無需指明時,置為空 。
  • msg_iov,msg_iovlen : 指定輸入或輸出的緩衝區陣列。
  • msg_control,msg_controllen : 可選的輔助資料的位置和大小。

注意事項:

  1. sendmsg中,會忽略msg_flags成員,他會按照引數flags直接處理。那麼當我們去設定MSG_DONTWAIT(臨時非阻塞)是就把flags設為MSG_DONTWAIT而把msg_flags設為不起作用。
  2. recvmsg中,使用msg_flags引數,他會將flags複製到msg_flags中進行處理。另外核心還可能將msg_flags的值更改。(因為在呼叫這兩個函式的時候,第二個引數都是通過指標去呼叫的)

2. 圖解其結構

這裡寫圖片描述

  協議地址16位元組,輔助資料20位元組。然後 iovec() 是三個緩衝資料陣列。

這裡寫圖片描述

  • msg_name :填充了一個套接字地址結構。包括:源ip和埠
  • msg_namelen :因為呼叫前和呼叫後之沒有發生改變,所以還是返回 16
  • msg_control:填充了一個cmsghdr()結構
  • msg_controllen:,返回實際填入的位元組數—>16
  • msg_flags:也會被核心更新,但是在這裡沒有標誌返回給程序

   5組I/O函式的比較
這裡寫圖片描述

3. 輔助資料

輔助資料又叫作控制資訊,通過msg_controlmsg_controllen來實現傳送和接受。輔助資料的用途主要有:

這裡寫圖片描述

它由一個或者多個輔助物件構成。物件由頭部和身體組成 。頭部是struct cmsghdr結構,身體是實際資料。 類似於http報文的結構。可以在頭部與身體之間有填充位元組,也可以在身體與下一個物件之間有填充位元組。見下圖 :

struct cmsghdr {
     size_t cmsg_len;    /* Data byte count, including header
                             (type is socklen_t in POSIX) */
      int    cmsg_level;  /* Originating protocol */
      int    cmsg_type;   /* Protocol-specific type */
  /* followed by
      unsigned char cmsg_data[]; */
   };

注意事項: 由msg_control指向的輔助資料必須為cmsghdr結構適當的對齊。

   以下是在一個控制緩衝區中出現2個輔助資料物件的例子:

這裡寫圖片描述

以下是通過一個unix域套接字傳遞描述符 或者傳遞憑證時所用的cmsghdr結構:

這裡寫圖片描述

疑問:那麼假如對端傳遞過來了一個描述符的話,那我如何能夠獲取到該輔助資料吶?自己手動分配空間給cmsg_data[]嗎?當然不是,這些系統已經幫我們想好了。系統提供了以下的5個巨集來實現。

#include <sys/socket.h>
#include <sys/param.h>
struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *mhdrptr);
    //返回:指向第一個cmsghdr結構的指標,若無輔助資料則為NULL
struct cmsghdr *CMSG_NXTHDR(struct msghdr *mhdrptr, struct cmsghdr *cmsghdr);
    //返回:指向下一個cmsghdr結構的指標,若不再有輔助資料物件則為NULL
unsigned char *CMSG_DATA(struct cmsghdr *cmsgptr);
    //返回:指向與cmsghdr結構關聯的資料的第一個位元組的指標
unsigned char *CMSG_LEN(unsigned int length);
    //返回:給定資料量下存放到cmsg_len中的值
unsigned char *CMSG_SPACE(unsigned int length);
    //返回:給定資料量下一個輔助資料物件總的大小。

那麼就可以進行如下呼叫啦!!

struct msghdr msg;
struct cmsghdr *cmsgptr;
for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL;
     cmsgptr = CMSG_NXTHDR(&msg, cmsgptr))
{
    /* 判斷是否自己需要 msg_level和msg_type */
    u_char *ptr;
    ptr = CMSG_DATA(cmsgptr); /* 獲取輔助資料 */
    /*通過ptr處理身體部分*/
}

注意事項: CMSG_LEN不計身體與下一個物件之間的填充位元組,CMSG_SPACE反之。

例項1 :使用sendmsgrecvmsg在程序之間傳遞描述符,見下一篇部落格.

附錄:(1)recvfrom && sendto 函式

 #include <sys/types.h>
 #include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

  通常在UDP中使用,當然也可以用於TCP!因為UDP是無連線的,所以每次傳送和接受時需要指明(IP地址和埠號)。