msghdr和cmsghdr相关总结

1.简单的描述符传递

fork调用返回之后,子进程共享父进程的所有打开的描述符;

exec调用之后,所有的描述符通常保持状态不变;

但是如果我们想要在子进程把描述符传给父进程,该怎么办呢?更或者两个毫无关系的进程间传递描述符应该怎么办呢?

这种技术要求首先在两个进程之间创建一个unix域套接字,然后使用sendmsg跨套接字发送一个特殊消息。

2.常规的描述符传递

结构体msghdr :

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

s就是socket的fd。buf是待发送的icmp数据包,len是buf的长度。flags可以是下列标志位的位或:

MSG_DONTROUTE

在发送分组时,不使用网关,只有直接连接在本网络中的主机才能接收到数据。这个标志通常仅用于诊断和路由程序。可路由的协议族才能使用这个标志;packet sockets不可以使用。

MSG_DONTWAIT

使用非阻塞操作。

MSG_NOSIGNAL

当流式套接字的另一端中断连接时不发送SIGPIPE信号,但仍然返回EPIPE错误。

MSG_CONFIRM (仅用于Linux 2.3以上版本)

通知链路层发生了转发过程:得到了另一端的成功应答。如果链路层没有收到通知,它将按照常规探测网络上的相邻主机(比如通过免费arp)。只能用于 SOCK_DGRAM和SOCK_RAW类型的套接字,且仅对IPv4和IPv6有效。

除此之外,还有MSG_MORE,MSG_OOB,MSG_EOR。

to是这个数据包发送的目地端地址,tolen是to的长度。

系统调用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,用于接收来自应用层的数据包。

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

第一组是msg_name和msg_namelen,记录这个消息的名字,其实就是数据包的目的地址。msg_name是指向一个结构体struct sockaddr的指针。长度为20:

struct sockaddr{

uint8_t sa_len;

sa_family_t sa_family;

char  sa_addr[14];

}

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

strcut sockaddr_in{

uint8_t sin_len;           //1个字节

sa_family_t  sin_family;   //一个字节

unsigned short int sin_port;    //2个字节

struct in_addr  sin_addr;    //4个字节

char sin_zero[8]    //8个字节

};

struct in_addr{

in_addr_t s_addr;

}

在我们的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标志,即带外数据。

PS:此部分转自摘自http://lameck.blog.163.com/blog/static/388113742008825104426803/

结构体cmsghdr

这里要关注的附属数据对象是文件描述符和证书结构。在每一个对象之前都有一个struct cmsghdr结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个cmsghdr之前也许要有更多的填充字节。

控制信息头部由下面的C结构定义

struct cmsghdr { 
    socklen_t cmsg_len; 
    int       cmsg_level; 
    int       cmsg_type; 
};

cmsg_len       附属数据的字节计数,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。
cmsg_level    这个值表明了原始的协议级别(例如,SOL_SOCKET)。
cmsg_type    这个值表明了控制信息类型(例如,SCM_RIGHTS)。

几个相关的宏:

CMSG_LEN()宏
这个宏接受我们希望放置在附属数据缓冲区中的对象尺寸作为输入参数。如果我们回顾一个我们前面的介绍,我们就会发现这个宏会计算cmsghdr头结构加上所需要的填充字符的字节长度。这个值用来设置cmsghdr对象的cmsg_len成员。
下面的例子演示了如果附属数据是一个文件描述符,我们应如何来计算cmsg_len成员的值:
int fd;   /* File descriptor */
printf("cmsg_len = %d/n",CMSG_LEN(sizeof fd));

CMSG_SPACE()宏
这个宏用来计算附属数据以及其头部所需的总空白。尽管CMSG_LEN()宏计算了一个相似的长度,CMSG_LEN()值并不包括可能的结尾的填充字符。CMSG_SPACE()宏对于确定所需的缓冲区尺寸是十分有用的,如下面的示例代码所示:
int fd; /* File Descriptor */
char abuf[CMSG_SPACE(sizeof fd)];
这个例子在abuf[]中声明了足够的缓冲区空间来存放头部,填充字节以及附属数据本身,和最后的填充字节。如果在缓冲区中有多个附属数据对象,一定要同时添加多个CMSG_SPACE()宏调用来得到所需的总空间。

CMSG_DATA()宏
这个宏接受一个指向cmsghdr结构的指针。返回的指针值指向跟随在头部以及填充字节之后的附属数据的第一个字节(如果存在)。如果指针mptr指向一个描述文件描述符的可用的附属数据信息头部,这个文件描述符可以用下面的代码来得到:
struct cmsgptr *mptr;
int fd; /* File Descriptor */
. . .
fd = *(int *)CMSG_DATA(mptr);
CMSG_ALIGN()宏
这是一个Linux扩展宏,而不是Posix.1g标准的一部分。指定一个字节长度作为输入,这个宏会计算一个新的长度,这个新长度包括为了维护对齐所需要的额外的填充字节。

CMSG_FIRSTHDR()宏
这个宏用于返回一个指向附属数据缓冲区内的第一个附属对象的struct cmsghdr指针。输入值为是指向struct msghdr结构的指针(不要与struct cmsghdr相混淆)。这个宏会估计msghdr的成员msg_control与msg_controllen来确定在缓冲区中是否存在附属对象。然后,他会计算返回的指针。如果不存在附属数据对象则返回的指针值为NULL。否则,这个指针会指向存在的第一个struct cmsghdr。这个宏用在一个for循环的开始处,来开始在附属数据对象中遍历。

CMSG_NXTHDR()宏
这个用于返回下一个附属数据对象的struct cmsghdr指针。这个宏会接受两个输入参数:
指向struct msghdr结构的指针
指向当前struct cmsghdr的指针
如果没有下一个附属数据对象,这个宏就会返回NULL。

struct msghdr msg;
struct iovec iov[1];

#define HAVE_MSGHDR_CONTROL

union{
	struct cmsghdr cm;
	char control[CSMG_SPACE(sizeof(int))];
}control_un;
struct csmghdr *cmptr;

msg.msg_control=control_un.control;
msg.msg_controllen=sizeof(control_un.control);

cmptr=CSMG_FIRSTHDR(&msg);
cmptr->cmsg_len=CMSG_LEN(sizeof(int));
cmptr->cmsg_level=SOL_SOCKET;
cmptr->cmsg_type=SCM_RIGHTS;
*((int *)CSMG_DATA(cmptr))=sendfd;

#else

msg.msg_accrights=(caddr_t)&sendfd;
msg.msg_accrightslen=sizeof(int);

#endif

msg.msg_name=NULL;
msg.msg_namelen=0;

iov[0].iov_base=ptr;
iov[0].iov_len=nbytes;
msg.msg_iov=iov;
msg.msg_iovlen=1;

sendmsg(fs,&msg,0);

转载于:https://my.oschina.net/NGINX08/blog/114140

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值