高级套接口-(sendmsg和recvmsg)

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。
附属数据缓冲区成员msg_control与msg_controllen。
接收信息标记位msg_flags。
在我们将这个结构分为上面的几类以后,结构看起来就不那样巨大了。
成员msg_name与msg_namelen
这些成员只有当我们的套接口是一个数据报套接口时才需要。msg_name成员指向我们要发送或是接收信息的套接口地址。成员msg_namelen指明了这个套接口地址的长度。
当调用recvmsg时,msg_name会指向一个将要接收的地址的接收区域。当调用sendmsg时,这会指向一个数据报将要发送到的目的地址。
注意,msg_name定义为一个(void *)数据类型。我们并不需要将我们的套接口地址转换为(struct sockaddr *)。
成员msg_iov与msg_iovlen
这些成员指定了我们的I/O向量数组的位置以及他包含多少项。msg_iov成员指向一个struct iovec数组。我们将会回忆起I/O向量指向我们的缓冲区。成员msg_iov指明了在我们的I/O向量数组中有多少元素。
成员msg_control与msg_controllen
这些成员指向了我们附属数据缓冲区并且表明了缓冲区大小。msg_control指向附属数据缓冲区,而msg_controllen指明了缓冲区大小。
成员msg_flags
当使用recvmsg时,这个成员用于接收特定的标记位(他并不用于sendmsg)。在这个位置可以接收的标记位如下表所示:
标记位        描述
MSG_EOR        当接收到记录结尾时会设置这一位。这通常对于SOCK_SEQPACKET套接口类型十分有用。
MSG_TRUNC    这个标记位表明数据的结尾被截短,因为接收缓冲区太小不足以接收全部的数据。
MSG_CTRUNC    这个标记位表明某些控制数据(附属数据)被截短,因为缓冲区太小。
MSG_OOB        这个标记位表明接收了带外数据。
MSG_ERRQUEUE    这个标记位表明没有接收到数据,但是返回一个扩展错误。
我们可以在recvmsg(2)与sendmsg(2)的man手册页中查看更多的信息。
附属数据结构与宏

recvmsg与sendmsg函数允许程序发送或是接收附属数据。然而,这些额外的信息受限于一定的格式规则。这一节将会介绍控制信息头与程序将会用来管理这些信息的宏。


上文来自:http://blog.csdn.net/shisiye15/article/details/7762606


已经工作了接近一年的时间,工作之余也只能看看书,了解一下相关的技术细节,在网络设备公司不可避免的要和socket打交道,但通常都是调用公司封装好的接口,没有去考虑这些封装背后的工作,回过头来看真的觉得进步很小,我只能逼自己看看书,看看一些好的代码。 
sendmsg和recvmsg这两个接口是高级套接口,这两个接口支持一般数据的发送和接收,还支持多缓冲区的报文发送和接收(readv和sendv支持多缓冲区发送和接收),还可以在报文中带辅助数据。这些功能是常用的send、recv等接口无法完成的。 
接口的声明如下: 

点击( 此处 )折叠或打开

  1. #include  < sys / socket . h > 

  2. ssize_t recvmsg ( int  sockfd ,  struct msghdr  * msg ,   int  flags ) ; 
  3. ssize_t sendmsg ( int  sockfd ,  struct msghdr  * msg ,   int  flags ) ;
上述接口的参数分别是套接字描述符,消息的头部,已经对应的标识,这些标识主要用于对该套接口进行设置,如:MSG_DONTWAI(将本操作设置为非阻塞模式),MSG_OOB(发送或接受带外数据)等。
在该接口的声明中包含了在其他发送接收函数中不用使用的接口体struct msghdr,该结构体是一个消息的头部结构体。  

点击( 此处 )折叠或打开

  1. struct msghdr  { 
  2.     void  * msg_name ;   / *  消息的协议地址  * / 
  3.     socklen_t msg_namelen ;   / *  地址的长度  * / 
  4.     struct iovec  * msg_iov ;   / *  多io缓冲区的地址  * / 
  5.      int  msg_iovlen ;   / *  缓冲区的个数  * / 
  6.     void  * msg_control ;   / *  辅助数据的地址  * / 
  7.     socklen_t msg_controllen ;   / *  辅助数据的长度  * / 
  8.      int  msg_flags ;   / *  接收消息的标识  * / 
  9. } ;
其中的前两个成员主要用于保存当前使用的协议的地址,比如使用了tcp协议、udp协议、UNIX domain协议等,每种地址都存在一定的差异,比如unix domain的地址就是(AF_UNIX, file_path)。这样就使得该接口更加的通用,针对各种类型的协议都是有效的。  
接下来的两个成员是关于接受和发送数据的的。其中的strcut iovec是io向量,如下所示:  

点击( 此处 )折叠或打开

  1. struct iovec  { 
  2.     void  * io_base ;   / *  buffer空间的基地址  * / 
  3.     size_t iov_len ;   / *  该buffer空间的长度  * / 
  4. } ;
多缓冲区的发送和接收处理就是一个struct iovec的数组,每个成员的io_base都指向了不同的buffer的地址。io_len是指该buffer中的数据长度。而在struct msghdr中的msg_iovlen是指buffer缓冲区的个数,即iovec数组的长度。 
msg_control字段的也是指向一段内存,msg_controllen是指该内存的总大小长度,通常该内存被用来存储辅助数据,辅助数据可用于一些特殊的处理。msg_control通常指向一个控制消息头部,其结构体如下所示: 

点击( 此处 )折叠或打开

  1. struct cmsghdr  { 
  2.     socklen_t cmsg_len ;   / *  包含该头部的数据长度  * / 
  3.      int  cmsg_level ;   / *  具体的协议标识  * / 
  4.      int  cmsg_type ;   / *  协议中的类型  * / 
  5. } ; 

  6. 其中的cmsg_level主要包含IPPROTO_IP ( ipv ) ,  IPPROTO_IPV6 ( ipv6 ) , SOL_SOCKET ( unix domain ) .
  7. 其中的cmsg_type是根据上述的类型有分别有不同的内容,比如SOL_SOCKET中主要包含:SCM_RIGHTS(发送接收描述字), SCM_CREDS(发送接收用户凭证)
该cmsghdr是辅助数据的数据头部,类似一个tlv的封装形式。关于辅助数据的功能在最后采用进程间传递描述字的代码来说明。 
关于辅助数据,在recvmsg的msg_control中可能携带多个cmsghdr, 可以采用对应的宏协助处理: CMSG_FIRSTHDR(), CMSG_NXTHDR(), CMSG_DATA(), CMSG_LEN(), CMSG_SPACE(). 
最后的字段是msg_flags,该字段主要用于在接收消息的过程中用来获取消息的相关属性。 
接下来采用进程间传递描述字的例子来说明上述函数的使用。 
描述字的传递,就是将一个进程中的描述字传递到另一个进程中,使得该描述字依然有效,也就是使得在一个进程中的描述字传递到另一个描述字依然有效。 
在多进程的网络CS模式下,服务器fork产生的子进程在fork调用返回后,子进程共享父进程的所有打开的描述字。即使在子进程中调用exec函数,所有描述字通常还是保持打开的状态,也就是描述字是跨exec函数的。这也是为什么在exec只有的子进程仍然可以调用父进程共享的套接字的原因。 
但是这种实现并不能解决子进程的描述字传递给父进程的需求,对于无亲缘关系的进程之间传递描述字就更加不可能,在unix中的实现是:在两个进程之间创建一个unix domain socket套接口,然后调用sendmsg跨这个套接口发送一个特殊的消息,该消息由内核进行特殊的处理,从而把打开的描述字从发送进程传递到接收进程(采用recvmsg接收)。分析主要包含如下几个过程: 
(1) 创建一个字节流或者数据报的unix domain socket套接口, 
     1、在父子进程之间可采用socketpair实现流管道,类似于pipe的实现,两个进程中分别有一个socket套接口 
     2、在无亲缘关系的进程之间采用基本的domain socket过程(服务器Bind, listen, accpet返回的套接口, 客户端connect返回的套接口),将两个进程关联起来。 
(2) 发送进程通过调用返回描述字的任一unix函数打开一个描述字(也就是返回fd类型的函数打开一个描述字)。 
(3) 发送进程创建一个msghdr结构,其中将待传递的描述字作为辅助数据发送,调用sendmsg跨越(1)中获得的套接口发送描述字。在发送完成以后,在发送进程即使关闭该描述字也不会影响接收进程的描述符,发送一个描述字导致该描述字的引用计数加1。 
(4) 接收进程调用recvmsg在(1)中获取的套接口上接收该描述字,该描述字在接收进程中的描述字号不同于在发送进程中的描述字号是正常的,也就是说如果在发送进程中描述字号是20,而在接收进程中对应的描述字号可能被使用,该进程会分配一个不一样的描述字号(如open对同一个文件进行多次打开,每一次的fd返回值都不一样是一个道理),此时就是说两个进程同时指向一个描述字。 
注意:在发送过程中由于没有报文,在接收的过程中会分不清是文件已经结束还是只是发送了辅助数据,因此通常在发送辅助数据的时候会传输至少一个字节的数据,该数据在接收过程中不做任何的处理。 
服务器端接收客户端发送过来的描述字: 

点击( 此处 )折叠或打开

  1. 服务器端接收客户端发送过来的描述字: 

  2. #include  "unp.h" 

  3. int  main ( int  argc ,  char  * argv [ ] ) 

  4.      int  clifd ,  listenfd ; 
  5.     struct sockaddr_un servaddr ,  cliaddr ; 
  6.      int  ret ; 
  7.     socklen_t clilen ; 
  8.     struct msghdr msg ; 
  9.     struct iovec iov [ 1 ] ; 
  10.     char buf [ 100 ] ; 
  11.     char  * testmsg  =   "test msg.\n" ; 
  12.      
  13.     union  { 
  14.         struct cmsghdr cm ; 
  15.         char control [ CMSG_SPACE ( sizeof ( int ) ) ] ; 
  16.      }  control_un ; 
  17.     struct cmsghdr  * pcmsg ; 
  18.      int  recvfd ; 
  19.      
  20.     listenfd  =  socket ( AF_UNIX ,  SOCK_STREAM ,  0 ) ; 
  21.      if   ( listenfd  <  0 )   { 
  22.         printf ( "socket failed.\n" ) ; 
  23.         return  - 1 ; 
  24.      } 
  25.      
  26.     unlink ( UNIXSTR_PATH ) ; 
  27.      
  28.     bzero ( & servaddr ,  sizeof ( servaddr ) ) ; 
  29.     servaddr . sun_family  =  AF_UNIX ; 
  30.     strcpy ( servaddr . sun_path ,  UNIXSTR_PATH ) ; 
  31.      
  32.     ret  =  bind ( listenfd ,   ( SA  * ) & servaddr ,  sizeof ( servaddr ) ) ; 
  33.      if   ( ret  <  0 )   { 
  34.         printf ( "bind failed. errno = %d.\n" ,  errno ) ; 
  35.         close  ( listenfd ) ; 
  36.         return  - 1 ; 
  37.      } 
  38.      
  39.     listen ( listenfd ,  5 ) ; 
  40.      
  41.      while   ( 1 )   { 
  42.         clilen  =  sizeof ( cliaddr ) ; 
  43.         clifd  =  accept ( listenfd ,   ( SA  * ) & cliaddr ,   & clilen ) ; 
  44.          if   ( clifd  <  0 )   { 
  45.             printf ( "accept failed.\n" ) ; 
  46.             continue ; 
  47.          } 
  48.          
  49.         msg . msg_name  =   NULL ; 
  50.         msg . msg_namelen  =  0 ; 
  51.         iov [ 0 ] . iov_base  =  buf ; 
  52.         iov [ 0 ] . iov_len  =  100 ; 
  53.         msg . msg_iov  =  iov ; 
  54.         msg . msg_iovlen  =  1 ; 
  55.         msg . msg_control  =  control_un . control ; 
  56.         msg . msg_controllen  =  sizeof ( control_un . control ) ; 
  57.          
  58.         ret  =  recvmsg ( clifd ,   & msg ,  0 ) ; 
  59.          if   ( ret  < =  0 )   { 
  60.             return ret ; 
  61.          } 
  62.          
  63.          if   ( ( pcmsg  =  CMSG_FIRSTHDR ( & msg ) )   ! =   NULL   & &   ( pcmsg -> cmsg_len  = =  CMSG_LEN ( sizeof ( int ) ) ) )   { 
  64.              if   ( pcmsg - > cmsg_level  ! =  SOL_SOCKET )   { 
  65.                 printf ( "cmsg_leval is not SOL_SOCKET\n" ) ; 
  66.                 continue ; 
  67.              } 
  68.              
  69.              if   ( pcmsg - > cmsg_type  ! =  SCM_RIGHTS )   { 
  70.                 printf ( "cmsg_type is not SCM_RIGHTS" ) ; 
  71.                 continue ; 
  72.              } 
  73.              
  74.             recvfd  =   * ( ( int   * )  CMSG_DATA ( pcmsg ) ) ; 
  75.             printf ( "recv fd = %d\n" ,  recvfd ) ; 
  76.              
  77.             write ( recvfd ,  testmsg ,  strlen ( testmsg )   +  1 ) ; 
  78.          } 
  79.      } 
  80.      
  81.     return 0 ; 
  82. }
客户端发送描述字: 

点击( 此处 )折叠或打开

  1. #include  "unp.h" 
  2. #define OPEN_FILE  "test" 
  3. int  main ( int  argc ,  char  * argv [ ] ) 

  4.      int  clifd ; 
  5.     struct sockaddr_un servaddr ; 
  6.      int  ret ; 
  7.     struct msghdr msg ; 
  8.     struct iovec iov [ 1 ] ; 
  9.     char buf [ 100 ] ; 
  10.     union  { 
  11.         struct cmsghdr cm ; 
  12.         char control [ CMSG_SPACE ( sizeof ( int ) ) ] ; 
  13.      }  control_un ; 
  14.     struct cmsghdr  * pcmsg ; 
  15.      int  fd ; 
  16.      
  17.     clifd  =  socket ( AF_UNIX ,  SOCK_STREAM ,  0 ) ; 
  18.      if   ( clifd  <  0 )   { 
  19.         printf ( "socket failed.\n" ) ; 
  20.         return  - 1 ; 
  21.      } 
  22.      
  23.     fd  =  open ( OPEN_FILE ,  O_CREAT |  O_RDWR ,  0777 ) ; 
  24.      if   ( fd  <  0 )   { 
  25.         printf ( "open test failed.\n" ) ; 
  26.         return  - 1 ; 
  27.      } 
  28.      
  29.     bzero ( & servaddr ,  sizeof ( servaddr ) ) ; 
  30.     servaddr . sun_family  =  AF_UNIX ; 
  31.     strcpy ( servaddr . sun_path ,  UNIXSTR_PATH ) ; 
  32.      
  33.     ret  =  connect ( clifd , ( SA  * ) & servaddr ,  sizeof ( servaddr ) ) ; 
  34.      if   ( ret  <  0 )   { 
  35.         printf ( "connect failed.\n" ) ; 
  36.         return 0 ; 
  37.      } 
  38.      
  39.     msg . msg_name  =   NULL ; 
  40.     msg . msg_namelen  =  0 ; 
  41.     iov [ 0 ] . iov_base  =  buf ; 
  42.     iov [ 0 ] . iov_len  =  100 ; 
  43.     msg . msg_iov  =  iov ; 
  44.     msg . msg_iovlen  =  1 ; 
  45.     msg . msg_control  =  control_un . control ; 
  46.     msg . msg_controllen  =  sizeof ( control_un . control ) ; 
  47.      
  48.     pcmsg  =  CMSG_FIRSTHDR ( & msg ) ; 
  49.     pcmsg - > cmsg_len  =  CMSG_LEN ( sizeof ( int ) ) ; 
  50.     pcmsg - > cmsg_level  =  SOL_SOCKET ; 
  51.     pcmsg - > cmsg_type  =  SCM_RIGHTS ; 
  52.      * ( ( int   * ) CMSG_DATA ( pcmsg ) )   = =  fd ; 
  53.      
  54.     ret  =  sendmsg ( clifd ,   & msg ,  0 ) ; 
  55.     printf ( "ret = %d.\n" ,  ret ) ; 
  56.     return 0 ; 
  57. }
上面的程序主要实现了两个进程(无亲缘关系)之间传递描述符的实现,主要使用了sendmsg和recvmsg的强大功能,利用了辅助数据来传递描述字(内核经过特殊处理的消息)。 
关于这两个函数的另一个应用是获取认证相关的处理,也是通过辅助数据的方式将用户的认证数据发送给对应的客户端。需要将cmsg->cmsg_type = SCM_CREDS。其余的处理都差不多。 

上文来自:http://www.tuicool.com/articles/Yre2Un

你可能感兴趣的:(C++,linux,unix,C++11)