sendmsg 和 recvmsg 函数

1. 基础介绍

  最通用的I/O函数,只要设置好参数,read、readv、recv、recvfrom和write、writev、send、sendto等函数都可以对应换成这两个函数来调用。同时,各种输出函数调用也可以替换成sendmsg调用。

#include 
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. 图解其结构

sendmsg 和 recvmsg 函数_第1张图片

  协议地址16字节,辅助数据20字节。然后 iovec() 是三个缓冲数据数组。

sendmsg 和 recvmsg 函数_第2张图片

  • msg_name :填充了一个套接字地址结构。包括:源ip和端口
  • msg_namelen :因为调用前和调用后之没有发生改变,所以还是返回 16
  • msg_control:填充了一个cmsghdr()结构
  • msg_controllen:,返回实际填入的字节数—>16
  • msg_flags:也会被内核更新,但是在这里没有标志返回给进程

   5组I/O函数的比较
sendmsg 和 recvmsg 函数_第3张图片

3. 辅助数据

辅助数据又叫作控制信息,通过msg_controlmsg_controllen来实现发送和接受。辅助数据的用途主要有:

sendmsg 和 recvmsg 函数_第4张图片

它由一个或者多个辅助对象构成。对象由头部和身体组成 。头部是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个辅助数据对象的例子:

sendmsg 和 recvmsg 函数_第5张图片

以下是通过一个unix域套接字传递描述符 或者传递凭证时所用的cmsghdr结构:

sendmsg 和 recvmsg 函数_第6张图片

疑问:那么假如对端传递过来了一个描述符的话,那我如何能够获取到该辅助数据呐?自己手动分配空间给cmsg_data[]吗?当然不是,这些系统已经帮我们想好了。系统提供了以下的5个宏来实现。

#include 
#include 
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 
 #include 

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地址和端口号)。

你可能感兴趣的:(服务端编程)