内核中用于数据接收的结构体struct msghdr以及iovec介绍

      我们从一个实际的数据包发送的例子入手,来看看其发送的具体流程,以及过程中涉及到的相关数据结构。在我们的虚拟机上发送icmp回显请求包,ping另一台主机172.16.48.1.我们使用系统调用sendto发送这个icmp包。

ssize_t sendto(int s, const void *buf, size_t len, int flags,
                const struct sockaddr *to, socklen_t tolen);

     系统调用sendto最终调用内核函数

asmlinkage long sys_sendto(int fd, void __user *buff, size_t len, unsigned flags, struct sockaddr __user *addr, int addr_len)

     sys_sendto构建一个结构体struct msghdr,用于接收来自应用层的数据包,下面是结构体struct msghdr的定义:

struct msghdr {
    void *msg_name; //存数据包的目的地址,网络包指向sockaddr_in
                    //向内核发数据时,指向sockaddr_nl
    int msg_namelen; //地址长度
    struct iovec *msg_iov;
    __kernel_size_t msg_iovlen;
    void *msg_control;
    __kernel_size_t msg_controllen;
    unsigned msg_flags;
};

     这个结构体的内容可以分为四组:

     (1) 第一组是msg_name和msg_namelen,记录这个消息的名字,其实就是数据包的目的地址。

     msg_name是指向一个结构体struct sockaddr的指针。长度为16:

struct sockaddr {
    sa_family_t sa_family;
    char sa_addr[14];
}

       所以,msg_namelen的长度为16.需要注意的是,结构体struct sockaddr只在进行参数传递时使用,无论是在用户态还是在内核态,我们把其强制转化为结构体struct sockaddr_in:

struct sockaddr_in {
    sa_family_t sin_family;
    unsigned short int sin_port;
    struct in_addr sin_addr;
    unsigned char __pad[__SOCK_SIZE__ -sizeof(short int) - 
                sizeof(unsigned short int) - sizeof(struct in_addr)];
};
struct in_addr {
    __u32 s_addr;
}

    __SOCK_SIZE__的值为16,所以,struct sockaddr中真正有用的数据只有8bytes。在我们的ping例子中,传入到内核的msghdr结构中:

msg.msg_name={sa_family_t=MY_AF_INET,sin_port=0,sin_addr.s_addr=172.16.48.1}
msg.msg_namelen=16

  请求回显icmp包没有目的端地址的端口号。

       (2) 第二组是msg_iov和msg_iovlen,记录这个消息的内容。msg_iov是一个指向结构体struct iovec的指针,实际上,确切地说,应该是一个结构体struct iovec的数组。下面是该结构体的定义:

struct iovec {
    void __user *iov_base;
    __kernel_size_t iov_len;
};

   iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,以数组的形式组织在msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff).在我们的ping程序的示例中:

扫描二维码关注公众号,回复: 3757368 查看本文章
msg.msg_iov = { struct iovec={iov_base={icmp头+填充字符'E'},iov_len = 40}}
msg.msg_len = 1

     (3) 第三组是msg_control和msg_controllen,它们可被用于发送任何的控制信息,在我们的例子中,没有控制信息要送。暂时略过。

     (4) 第四组是msg_flags.其值即为传入的参数flags。raw协议不支持MSG_OOB向标志,即带外数据。向内核发阿松msg

时使用msghdr,netlink socket使用自己的消息头nlmsghdr和自己的消息地址sockaddr_nl:

struct sockaddr_nl
{
    sa_family_t nl_family;
    unsigned short nl_pad;
    __u32 nl_pid;
    __u32 nl_groups;
};
struct nlmsghdr
{
    __u32 nlmsg_len; /* Length of message */
    __u16 nlmsg_type; /* Message type */
    __u16 nlmsg_flags; /* Additional flags */
    __u32 nlmsg_seq; /* Sequence number */
    __u32 nlmsg_pid; /* Sending process PID */
};   

      过程如下:

struct msghdr msg; 
memset(&msg, 0, sizeof(msg)); 
msg.msg_name = (void *)&(nladdr);
msg.msg_namelen = sizeof(nladdr);
{
    /*初始化一个strcut nlmsghdr结构存,nlmsghdr为netlink socket自己的消息头部,
            并使iov->iov_base指向在这个结构*/
    char buffer[] = "An example message";
    struct nlmsghdr nlhdr;
    nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_MSGSIZE));
    strcpy(NLMSG_DATA (nlhdr),buffer);//将数据存放在消息头指向的数据地址
    nlhdr->nlmsg_len = NLMSG_LENGTH(strlen(buffer));
    nlhdr->nlmsg_pid = getpid(); /* self pid */
    nlhdr->nlmsg_flags = 0;
    iov.iov_base = (void *)nlhdr;
    iov.iov_len = nlh->nlmsg_len;
}
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
fd=socket(AF_NETLINK, SOCK_RAW, netlink_type);
sendmsg(fd,&msg,0)

I/O向量(struct iovec)
readv(2)与writev(2)函数都使用一个I/O向量的概念。这是由所包含的文件定义的:
#include 
sys/uio.h头文件定义了struct iovc,其定义如下:

struct iovec {
    ptr_t iov_base; /* Starting address */
    size_t iov_len; /* Length in bytes */
};


struct iovec定义了一个向量元素。通常,这个结构用作一个多元素的数组。对于每一个传输的元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放
的是readv所接收的数据或是writev将要发送的数据。成员iov_len在各种情况下分别确定了接收的最大长度以及实际写入的长度。
 

猜你喜欢

转载自blog.csdn.net/zhangge3663/article/details/83383365