高级套接口-(sendmsg和recvmsg)

7 篇文章 0 订阅

http://www.tuicool.com/articles/Yre2Un

sendmsg和recvmsg这两个接口是高级套接口,这两个接口支持一般数据的发送和接收,还支持多缓冲区的报文发送和接收(readv和sendv支持多缓冲区发送和接收),还可以在报文中带辅助数据。这些功能是常用的send、recv等接口无法完成的。 

#include  < sys / socket . h >

ssize_t recvmsg ( int  sockfd ,  struct msghdr  * msg ,   int  flags ) ; 

ssize_t sendmsg ( int  sockfd ,  struct msghdr  * msg ,   int  flags ) ;

上述接口的参数分别是套接字描述符,消息的头部,已经对应的标识,这些标识主要用于对该套接口进行设置,如:MSG_DONTWAI(将本操作设置为非阻塞模式),MSG_OOB(发送或接受带外数据)等。

在该接口的声明中包含了在其他发送接收函数中不用使用的接口体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;
};其中的前两个成员主要用于保存当前使用的协议的地址,比如使用了tcp协议、udp协议、UNIX domain协议等,每种地址都存在一定的差异,比如unix domain的地址就是(AF_UNIX, file_path)。这样就使得该接口更加的通用,针对各种类型的协议都是有效的。 
接下来的两个成员是关于接受和发送数据的的。其中的strcut iovec是io向量,如下所示: 

点击( 此处 )折叠或打开

  1. struct iovec  { 
  2.     void  * iov_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。其余的处理都差不多。
 


进程间传递描述符一

每个进程都拥有自己独立的进程空间,这使得描述符在进程之间的传递变得有点复杂,这个属于高级进程间通信的内容,下面就来说说。顺便把 Linux 和 Windows 平台都讲讲。

Linux 下的描述符传递

Linux 系统系下,子进程会自动继承父进程已打开的描述符,实际应用中,可能父进程需要向子进程传递“后打开的描述符”,或者子进程需要向父进程传递;或者两个进程可能是无关的,显然这需要一套传递机制。

简单的说,首先需要在这两个进程之间建立一个 Unix 域套接字接口作为消息传递的通道( Linux 系统上使用socketpair 函数可以很方面便的建立起传递通道),然后发送进程调用 sendmsg 向通道发送一个特殊的消息,内核将对这个消息做特殊处理,从而将打开的描述符传递到接收进程。

然后接收方调用 recvmsg 从通道接收消息,从而得到打开的描述符。然而实际操作起来并不像看起来那样单纯。

先来看几个注意点:

1 需要注意的是传递描述符并不是传递一个 int 型的描述符编号,而是在接收进程中创建一个新的描述符,并且在内核的文件表中,它与发送进程发送的描述符指向相同的项。

2 在进程之间可以传递任意类型的描述符,比如可以是 pipe , open , mkfifo 或 socket , accept 等函数返回的描述符,而不限于套接字。

3 一个描述符在传递过程中(从调用 sendmsg 发送到调用 recvmsg 接收),内核会将其标记为“在飞行中”( in flight )。在这段时间内,即使发送方试图关闭该描述符,内核仍会为接收进程保持打开状态。发送描述符会使其引用计数加 1 。

4 描述符是通过辅助数据发送的(结构体 msghdr 的 msg_control 成员),在发送和接收描述符时,总是发送至少1 个字节的数据,即使这个数据没有任何实际意义。否则当接收返回 0 时,接收方将不能区分这意味着“没有数据”(但辅助数据可能有套接字)还是“文件结束符”。

5 具体实现时, msghdr 的 msg_control 缓冲区必须与 cmghdr 结构对齐,可以看到后面代码的实现使用了一个union 结构来保证这一点。

msghdr 和 cmsghdr 结构体

上面说过,描述符是通过结构体 msghdr 的 msg_control 成员送的,因此在继续向下进行之前,有必要了解一下msghdr 和 cmsghdr 结构体,先来看看 msghdr 。

[cpp]  view plain  copy
  1. struct msghdr {  
  2.     void       *msg_name;  
  3.     socklen_t    msg_namelen;  
  4.     struct iovec  *msg_iov;  
  5.     size_t       msg_iovlen;  
  6.     void       *msg_control;  
  7.     size_t       msg_controllen;  
  8.     int          msg_flags;  
  9. };   

结构成员可以分为下面的四组,这样看起来就清晰多了:

1 套接口地址成员 msg_name 与 msg_namelen ;

只有当通道是数据报套接口时才需要; msg_name 指向要发送或是接收信息的套接口地址。 msg_namelen 指明了这个套接口地址的长度。

msg_name 在调用 recvmsg 时指向接收地址,在调用 sendmsg 时指向目的地址。注意, msg_name 定义为一个(void *) 数据类型,因此并不需要将套接口地址显示转换为 (struct sockaddr *) 。

2 I/O 向量引用 msg_iov 与 msg_iovlen

它是实际的数据缓冲区,从下面的代码能看到,我们的 1 个字节就交给了它;这个 msg_iovlen 是 msg_iov 的个数,不是什么长度。

msg_iov 成员指向一个 struct iovec 数组, iovc 结构体在 sys/uio.h 头文件定义,它没有什么特别的。

[cpp]  view plain  copy
  1. struct iovec {  
  2.      ptr_t iov_base; /* Starting address */  
  3.      size_t iov_len; /* Length in bytes */  
  4. };  

有了 iovec ,就可以使用 readv 和 writev 函数在一次函数调用中读取或是写入多个缓冲区,显然比多次 read ,write 更有效率。 readv 和 writev 的函数原型如下:

[cpp]  view plain  copy
  1. #include <sys/uio.h>  
  2. int readv(int fd, const struct iovec *vector, int count);  
  3. int writev(int fd, const struct iovec *vector, int count);  

3 附属数据缓冲区成员 msg_control 与 msg_controllen ,描述符就是通过它发送的,后面将会看到, msg_control指向附属数据缓冲区,而 msg_controllen 指明了缓冲区大小。

4 接收信息标记位 msg_flags ;忽略

 

轮到 cmsghdr 结构了,附属信息可以包括若干个单独的附属数据对象。在每一个对象之前都有一个 struct cmsghdr结构。头部之后是填充字节,然后是对象本身。最后,附属数据对象之后,下一个 cmsghdr 之前也许要有更多的填充字节。

[cpp]  view plain  copy
  1. struct cmsghdr {  
  2.     socklen_t cmsg_len;  
  3.     int       cmsg_level;  
  4.     int       cmsg_type;  
  5.     /* u_char     cmsg_data[]; */  
  6. };  

cmsg_len   附属数据的字节数,这包含结构头的尺寸,这个值是由 CMSG_LEN() 宏计算的;

cmsg_level  表明了原始的协议级别 ( 例如, SOL_SOCKET) ;

cmsg_type  表明了控制信息类型 ( 例如, SCM_RIGHTS ,附属数据对象是文件描述符; SCM_CREDENTIALS ,附属数据对象是一个包含证书信息的结构 ) ;

被注释的 cmsg_data 用来指明实际的附属数据的位置,帮助理解。

对于 cmsg_level 和 cmsg_type ,当下我们只关心 SOL_SOCKET 和 SCM_RIGHTS 。

msghdr 和 cmsghdr 辅助宏

这些结构还是挺复杂的, Linux 系统提供了一系列的宏来简化我们的工作,这些宏可以在不同的 UNIX 平台之间进行移植。这些宏是由 cmsg(3) 的 man 手册页描述的,先来认识一下:

#include <sys/socket.h>

struct cmsghdr *CMSG_FIRSTHDR(struct msghdr *msgh);

struct cmsghdr *CMSG_NXTHDR(struct msghdr *msgh, struct cmsghdr *cmsg);

size_t CMSG_ALIGN(size_t length);

size_t CMSG_SPACE(size_t length);

size_t CMSG_LEN(size_t length);

void *CMSG_DATA(struct cmsghdr *cmsg);

 

CMSG_LEN() 宏

输入参数:附属数据缓冲区中的对象大小;

计算 cmsghdr 头结构加上附属数据大小,包括必要的对其字段,这个值用来设置 cmsghdr 对象的 cmsg_len 成员。

CMSG_SPACE() 宏

输入参数:附属数据缓冲区中的对象大小;

计算 cmsghdr 头结构加上附属数据大小,并包括对其字段和可能的结尾填充字符,注意 CMSG_LEN() 值并不包括可能的结尾填充字符。 CMSG_SPACE() 宏对于确定所需的缓冲区尺寸是十分有用的。

注意如果在缓冲区中有多个附属数据,一定要同时添加多个 CMSG_SPACE() 宏调用来得到所需的总空间。

下面的例子反映了二者的区别:

[cpp]  view plain  copy
  1. printf("CMSG_SPACE(sizeof(short))=%d/n", CMSG_SPACE(sizeof(short))); // 返回16  
  2. printf("CMSG_LEN(sizeof(short))=%d/n", CMSG_LEN(sizeof(short))); // 返回14  

CMSG_DATA() 宏

输入参数:指向 cmsghdr 结构的指针 ;

返回跟随在头部以及填充字节之后的附属数据的第一个字节 ( 如果存在 ) 的地址,比如传递描述符时,代码将是如下的形式:

[cpp]  view plain  copy
  1. struct cmsgptr *cmptr;  
  2. . . .  
  3. int fd = *(int *)CMSG_DATA(cmptr); // 发送:*(int *)CMSG_DATA(cmptr) = fd;  

CMSG_FIRSTHDR() 宏

输入参数:指向 struct msghdr 结构的指针;

返回指向附属数据缓冲区内的第一个附属对象的 struct cmsghdr 指针。如果不存在附属数据对象则返回的指针值为NULL 。

CMSG_NXTHDR() 宏

输入参数:指向 struct msghdr 结构的指针,指向当前 struct cmsghdr 的指针;

这个用于返回下一个附属数据对象的 struct cmsghdr 指针,如果没有下一个附属数据对象,这个宏就会返回 NULL。

通过这两个宏可以很容易遍历所有的附属数据,像下面的形式:

[cpp]  view plain  copy
  1. struct msghdr msgh;  
  2. struct cmsghdr *cmsg;  
  3. for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL;  
  4.     cmsg = CMSG_NXTHDR(&msgh,cmsg) {  
  5.     // 得到了cmmsg,就能通过CMSG_DATA()宏取得辅助数据了   

函数 sendmsg 和 recvmsg

函数原型如下:

[cpp]  view plain  copy
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. int sendmsg(int s, const struct msghdr *msg, unsigned int flags);  
  4. int recvmsg(int s, struct msghdr *msg, unsigned int flags);  

二者的参数说明如下:

s, 套接字通道,对于 sendmsg 是发送套接字,对于 recvmsg 则对应于接收套接字;

msg ,信息头结构指针;

flags , 可选的标记位, 这与 send 或是 sendto 函数调用的标记相同。

函数的返回值为实际发送 / 接收的字节数。否则返回 -1 表明发生了错误。

具体参考 APUE 的高级 I/O 部分,介绍的很详细。

好了准备工作已经做完了,下面就准备进入正题。


发送描述符

经过了前面的准备工作,是时候发送描述符了,先来看看函数原型:

int write_fd(int fd, void *ptr, int nbytes, int sendfd);

参数说明如下:

@fd :发送 TCP 套接字接口;这个可以是使用socketpair返回的发送套接字接口

@ptr :发送数据的缓冲区指针;

@nbytes :发送的字节数;

@sendfd :向接收进程发送的描述符;

函数返回值为写入的字节数, <0 说明发送失败;

废话少说,代码先上,发送描述符的代码相对简单一些,说明见代码内注释。

先说明一下,旧的 Unix 系统使用的是 msg_accrights 域来传递描述符,因此我们需要使用宏HAVE_MSGHDR_MSG_CONTROL 以期能同时支持这两种版本。

[cpp]  view plain  copy
  1. int write_fd(int fd, void *ptr, int nbytes, int sendfd)  
  2. {  
  3.     struct msghdr msg;  
  4.     struct iovec iov[1];  
  5.     // 有些系统使用的是旧的msg_accrights域来传递描述符,<a href="http://lib.csdn.net/base/linux" class='replace_word' title="Linux知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Linux</a>下是新的msg_control字段  
  6. #ifdef HAVE_MSGHDR_MSG_CONTROL  
  7.     union// 前面说过,保证cmsghdr和msg_control的对齐  
  8.         struct cmsghdr cm;  
  9.         char control[CMSG_SPACE(sizeof(int))];  
  10.     }control_un;  
  11.     struct cmsghdr *cmptr;   
  12.     // 设置辅助缓冲区和长度  
  13.     msg.msg_control = control_un.control;   
  14.     msg.msg_controllen = sizeof(control_un.control);  
  15.     // 只需要一组附属数据就够了,直接通过CMSG_FIRSTHDR取得  
  16.     cmptr = CMSG_FIRSTHDR(&msg);  
  17.     // 设置必要的字段,数据和长度  
  18.     cmptr->cmsg_len = CMSG_LEN(sizeof(int)); // fd类型是int,设置长度  
  19.     cmptr->cmsg_level = SOL_SOCKET;   
  20.     cmptr->cmsg_type = SCM_RIGHTS;  // 指明发送的是描述符  
  21.     *((int*)CMSG_DATA(cmptr)) = sendfd; // 把fd写入辅助数据中  
  22. #else  
  23.     msg.msg_accrights = (caddr_t)&sendfd; // 这个旧的更方便啊  
  24.     msg.msg_accrightslen = sizeof(int);  
  25. #endif  
  26.     // UDP才需要,无视  
  27.     msg.msg_name = NULL;  
  28.     msg.msg_namelen = 0;  
  29.     // 别忘了设置数据缓冲区,实际上1个字节就够了  
  30.     iov[0].iov_base = ptr;  
  31.     iov[0].iov_len = nbytes;  
  32.     msg.msg_iov = iov;  
  33.     msg.msg_iovlen = 1;  
  34.     return sendmsg(fd, &msg, 0);  
  35. }   

  接收描述符

发送方准备好之后,接收方准备接收,函数原型为:

int read_fd(int fd, void *ptr, int nbytes, int *recvfd);

参数说明如下:

@fd :接收 TCP 套接字接口; 这个可以是使用 socketpair返回的接收套接字接口

@ptr :接收数据的缓冲区指针;

@nbytes :接收缓冲区大小;

@recvfd :用来接收发送进程发送来的描述符;

函数返回值为读取的字节数, <0 说明读取失败;

接收函数代码如下,相比发送要复杂一些。

[cpp]  view plain  copy
  1. int read_fd(int fd, void *ptr, int nbytes, int *recvfd)  
  2. {  
  3.     struct msghdr msg;  
  4.     struct iovec iov[1];  
  5.     int n;  
  6.     int newfd;  
  7. #ifdef HAVE_MSGHDR_MSG_CONTROL  
  8.     union// 对齐  
  9.     struct cmsghdr cm;  
  10.     char control[CMSG_SPACE(sizeof(int))];  
  11.     }control_un;  
  12.     struct cmsghdr *cmptr;  
  13.     // 设置辅助数据缓冲区和长度  
  14.     msg.msg_control = control_un.control;  
  15.     msg.msg_controllen = sizeof(control_un.control);  
  16. #else  
  17.     msg.msg_accrights = (caddr_t) &newfd; // 这个简单  
  18.     msg.msg_accrightslen = sizeof(int);  
  19. #endif   
  20.       
  21.     // TCP无视  
  22.     msg.msg_name = NULL;  
  23.     msg.msg_namelen = 0;  
  24.     // 设置数据缓冲区  
  25.     iov[0].iov_base = ptr;  
  26.     iov[0].iov_len = nbytes;  
  27.     msg.msg_iov = iov;  
  28.     msg.msg_iovlen = 1;  
  29.     // 设置结束,准备接收  
  30.     if((n = recvmsg(fd, &msg, 0)) <= 0)  
  31.     {  
  32.         return n;  
  33.     }  
  34. #ifdef HAVE_MSGHDR_MSG_CONTROL  
  35.     // 检查是否收到了辅助数据,以及长度,回忆上一节的CMSG宏  
  36.     cmptr = CMSG_FIRSTHDR(&msg);  
  37.     if((cmptr != NULL) && (cmptr->cmsg_len == CMSG_LEN(sizeof(int))))  
  38.     {  
  39.     // 还是必要的检查  
  40.         if(cmptr->cmsg_level != SOL_SOCKET)  
  41.         {  
  42.             printf("control level != SOL_SOCKET/n");  
  43.             exit(-1);  
  44.         }  
  45.         if(cmptr->cmsg_type != SCM_RIGHTS)  
  46.         {  
  47.             printf("control type != SCM_RIGHTS/n");  
  48.             exit(-1);  
  49.         }  
  50.     // 好了,描述符在这  
  51.         *recvfd = *((int*)CMSG_DATA(cmptr));  
  52.     }  
  53.     else  
  54.     {  
  55.         if(cmptr == NULL) printf("null cmptr, fd not passed./n");  
  56.         else printf("message len[%d] if incorrect./n", cmptr->cmsg_len);  
  57.         *recvfd = -1; // descriptor was not passed  
  58.     }  
  59. #else  
  60.     if(msg.msg_accrightslen == sizeof(int)) *recvfd = newfd;   
  61.     else *recvfd = -1;  
  62. #endif  
  63.     return n;  
  64. }  

发送和接收函数就这么多,就像上面看到的,进程间传递套接字还是有点麻烦的。


http://blog.csdn.net/shisiye15/article/details/7762606

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


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值