linux传递文件描述符

来源:互联网 发布:网络教育好毕业吗 编辑:程序博客网 时间:2024/05/16 01:56

http://blog.csdn.net/linuxdianc/article/details/5048189


在进程之间经常遇到需要在各进程之间传递文件描述符的情况,例如有一种设备它在加电期间只能打开一次,如果关闭后再次打开就会发生错误。这时就需要有一个调度程序,它调度多个相同设备,当有客户端需要此类型的设备时会向它发送一个请求,服务器会把某个设备的描述符给客户端。但是,由于不同进程之间的文件描述符所表示的对象是不同的,这需要一种特殊的机制来实现上述的要求。

Linux系统中提供了一种特殊的方法,可以从一个进程中将一个已经打开的文件描述符传递给其他的任何进程。其基本过程如下:

(1)创建一个字节流或者数据报的UNIX域套接字---普通套接字 不行

         如果目标是fork()一个子进程,让子进程打开描述符并将它返回给父进程,那么父进程可以用socketpair()创建一个流管道,用它来传递描述字。

         如果进程之间没有亲缘关系,那么服务器必须创建一个UNIX域字节流套接字,绑定一个路径名,让客户连接到这个套接字。然后客户端可以向服务器发送一个请求以打开某个描述字,服务器将描述符通过UNIX域套接字传回。在客户端和服务器之间也可以使用UNIX数据报套接字,但这样做没有什么好处,而且数据报存在丢失的可能性。

(2)进程可以用任何返回描述符的UNIX函数打开一个描述符:例如open()、pipe()、mkfifo()、socket()或者accept()。可以在进程间传递任何类型的描述符。

(3)发送进程建立一个msghdr结构,其中包含要传递的描述符。在POSIX中说明该描述符作为辅助数据发送,但老的实现使用msg_accright成员。发送进程调用sendmsg()通过第一部得到的UNIX域套接字发出套接字。这时这个描述符是在飞行中的。即使在发送进程调用sendmsg()之后,但在接受进程调用recvmsg()之前将描述符关闭,它仍会为接收进程保持打开状态。描述符的发送导致它的访问统计数加1。

(4)接收进程调用recvmsg()在UNIX域套接字上接收套接字。通常接收进程收到的描述符的编号和发送进程中的描述符的编号不同,但这没有问题。传递描述符不是传递描述符的编号,而是在接收进程中建立一个新的描述符,指向内核的文件表中与发送进程发送的描述符相同的项。


先来看几个注意点:

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 结构来保证这一点。

---不用union也可以,char control[CMSG_SPACE(sizeof(int))];  已经求出了对齐后的总大小了,直接用control指针当 struct cmsghdr *指针就可以


以下内容转载至:http://blog.csdn.net/sparkliang/article/details/5490242

发送描述符

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

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 plaincopy
  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域来传递描述符,Linux下是新的msg_control字段  
  6. #ifdef HAVE_MSGHDR_MSG_CONTROL  
  7.     union// 前面说过,保证cmsghdr和msg_control的对齐  
  8.         struct cmsghdr cm;  
  9.         char control[CMSG_SPACE(sizeof(int))];  //不用union也可以,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 plaincopy
  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. }  

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

0 0
原创粉丝点击