msghdr;nlmsghdr;iovec结构体的介绍

http://blog.csdn.net/ccccdddxxx/article/details/6368337


我们从一个实际的数据包发送的例子入手,来看看其发送的具体流程,以及过程中涉及到的相关数据结构。在我们的虚拟机上发送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;

        };

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

    第一组是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:

        strcut 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包没有目的端地址的端口号。

    第二组是msg_iov和msg_iovlen,记录这个消息的内容。msg_iov是一个指向结构体struct iovec的指针,实际上,确切地说,应该是一个结构体strcut 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程序的实例中:

    msg.msg_iov = { struct iovec = { iov_base = { icmp头+填充字符'E' }, iov_len = 40 } }

    msg.msg_len = 1

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

    第四组是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)

 

 

证书可以作为通信中所接收的附属数据的一部分而接收。附属数据对于通常数据来说是补充或是从属。这就引出需要在这里强调的几点问题:
证书是作为附属数据的一部分而接收的。
附属数据必须是补充通常数据的(他不可以独立传送)。
附属数据也可以包含其他的信息,例如文件描述符。
附属数据可以同时包含多个附属项目(例如同时包含证书与文件描述符)。
证书是由Linux内核提供的。他们从来不由客户程序提供。如果是这样,客户端就会被允许 个标识。因为内核是可信任的,证书就可以被对证书感兴趣的进程所信任。
现在我们已经了解文件描述符是作为附属数据来传送和接收的。然而,在我们开始编写套接口代码来使用这些附属数据元素时,我们需要介绍一些新的编程概念。
简介I/O向量
在我们了解使用附属数据工作的复杂函数之前,我们应该熟悉被readv(2)与writev(2)系统调用所使用的I/O向量。我们不仅将会发现这些函数是十分有用的,而他们的工作方式也被引入了一些附属数据函数中。这会使得后面的理解更为容易。
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在各种情况下分别确定了接收的最大长度以及实际写入的长度。
readv(2)与writev(2)函数
这些函数是作为read与write函数的衍生函数而被人所知的。他们以这样的方式进行设计是因为他们可以在一个原子操作中读取或是写入多个缓冲区。这些函数的原型如下:
#include 
int readv(int fd, const struct iovec *vector, int count);
int writev(int fd, const struct iovec *vector, int count);
这些函数需要三个参数:
要在其上进行读或是写的文件描述符fd
读或写所用的I/O向量(vector)
要使用的向量元素个数(count)
这些函数的返回值是readv所读取的字节数或是writev所写入的字节数。如果有错误发生,就会返回-1,而errno存有错误代码。注意,也其他I/O函数类似,可以返回错误码EINTR来表明他被一个信号所中断。
使用writev的例子
下面的程序代码展示了如何使用writev函数将三个独立的C字符串作为一次写操作写入标准输出。
/*
* writev.c
*
* Short writev(2) demo:
*/
#include 
int main(int argc,char **argv)
{
    static char part2[] = "THIS IS FROM WRITEV";
    static char part3[]    = "]/n";
    static char part1[] = "[";
    struct iovec iov[3];
    iov[0].iov_base = part1;
    iov[0].iov_len = strlen(part1);
    iov[1].iov_base = part2;
    iov[1].iov_len = strlen(part2);
    iov[2].iov_base = part3;
    iov[2].iov_len = strlen(part3);
    writev(1,iov,3);
    return 0;
}
编译运行程序:
$ make writev
gcc -g -c -D_GNU_SOURCE -Wall -Wreturn-type writev.c
gcc writev.o -o writev
$ ./writev
[THIS IS FROM WRITEV]
$
当程序运行时,我们可以看到无论所引用的缓冲区是如何分散,所有的缓冲区都会被输出形成最终的字符串。
也许我们希望多花一些时间来修改这个程序并做各种测试,但是要注意一定要将iov[[]数组分配得足够大。
sendmsg(2)与recvmsg(2)函数
这些函数为程序提供了一些其他的套接口I/O接口所不具备的高级特性。下面的内容我们将会先来看一下sendmsg来介绍这些主题。然后将会完整的介绍recvmsg函数,因为他们的函数接口是相似的。接下来,将会描述msghdr的完整结构。
sendmsg(2)函数
现在是时候进入这个大同盟了。从概念上说,sendmsg函数是所有写入函数的基础,而他是从属于套接口的。下面的列表以复杂增加的顺序列出了所有与入函数。在每一个层次上,同时列出了所增加的特性。
函数        增加的特性
write        最简单的套接口写入函数
send        增加了flags标记
sendto        增加了套接口地址与套接口长度参数
writev        没有标记与套接口地址,但是具有分散写入的能力
sendmsg        增加标记,套接口地址与长度,分散写入以及附属数据的能力
sendmsg(2)函数原型如下:
#include 
#include 
int sendmsg(int s, const struct msghdr *msg, unsigned int flags);
函数参数描述如下:
要在其上发送消息的套接口s
信息头结构指针msg,这会控制函数调用的功能
可选的标记位参数flags。这与send或是sendto函数调用的标记参数相同。
函数的返回值为实际发送的字节数。否则,返回-1表明发生了错误,而errno表明错误原因。
recvmsg(2)函数
recvmsg是与sendmsg函数相对的函数。这个函数原型如下:
#include 
#include 
int recvmsg(int s, struct msghdr *msg, unsigned int flags);
函数参数如下:
要在其上接收信息的套接口s
信息头结构指针msg,这会控制函数调用的操作。
可选标记位参数flags。这与recv或是recvfrom函数调用的标记参数相同。
这个函数的返回值为实际接收的字节数。否则,返回-1表明发生了错误,而errno表明错误原因。
理解struct msghdr
当我第一次看到他时,他看上去似乎是一个需要创建的巨大的结构。但是不要怕。其结构定义如下:
struct msghdr {
    void         *msg_name;
    socklen_t    msg_namelen;
    struct iovec *msg_iov;
    size_t       msg_iovlen;
    void         *msg_control;
    size_t       msg_controllen;
    int          msg_flags;
};
结构成员可以分为四组。他们是:
套接口地址成员msg_name与msg_namelen。
I/O向量引用msg_iov与msg_iovlen。

  • 1
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值