socketpair

一.概念及用途
一个问题:如何创建全双工管道?
直接的办法当然是pipe两次,创建两组管道,但是有没有更简单的呢?
socketpair就可以了,man socketpair:

socketpair - create a pair of connected sockets, The two sockets are indistinguishable,也就是说,用socketpair创建出来的两个描述符应该是等价的。

socketpair函数概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);

sys/types.h文件需要用来定义一些C宏常量。sys/socket.h文件必须包含进来定义socketpair函数原型。
socketpair函数需要四个参数。他们是:

套接口的域            套接口类型               使用的协议                          指向存储文件描述符的指针
类型参数声明了我们希望创建哪种类型的套接口。socketpair函数的选择如下:
SOCK_STREAM
SOCK_DGRAM
对于socketpair函数,protocol参数必须提供为0。
参数sv[2]是接收代表两个套接口的整数数组。每一个文件描述符代表一个套接口,并且与另一个并没有区别。
如果函数成功,将会返回0值。否则将会返回-1表明创建失败,并且errno来表明特定的错误号。

关于流程。socketpair()函数创建出两个进程,fork()之后这两个进程都会执行主程序中的代码,这个一定要注意!尤其是bind的时候,如果bind两次的话,那就会出错了。一般会在子进程里调用一个带死循环的函数,这样就好了。(这个情况的例子会在综合运用中讲解)

二.用法

[cpp]  view plain copy
  1. #define DATA1 "test string 1"  
  2. #define DATA2 "test string 2"  
  3. #include <stdio.h>  
  4. #include <string.h>  
  5. #include <unistd.h>  
  6. #include <sys/socket.h>  
  7. #include <sys/un.h>  
  8. #include <sys/types.h>  
  9. #include <sys/wait.h>  
  10. #include <errno.h>  
  11. main()  
  12. {  
  13.   int sockets[2], child;  
  14.   char buf[1024];  
  15.   /* Get the socket pair */  
  16.   if (socketpair(AF_UNIX, SOCK_STREAM,  
  17.         0, sockets) < 0) {  
  18.     printf("error %d on socketpair ", errno);  
  19.     exit(1);  
  20.   }  
  21.   /* create child process */  
  22. if ((child = fork()) == -1) {  
  23.     printf("fork error %d ", errno);  
  24.     exit(1);  
  25.   }  
  26.   if (child != 0) { /* this is the parent */  
  27.     /* close child's end of socket */  
  28.     close(sockets[0]);  
  29.     /* read message from child */  
  30.     if (read(sockets[1], buf, sizeof(buf)) < 0) {  
  31.       printf("error %d reading socket ", errno);  
  32.       exit(1);  
  33.     }  
  34.     printf("parent:-->%s /n", buf);  
  35.     /* write message to child */  
  36.     if (write(sockets[1], DATA1, sizeof(DATA1)) < 0) {  
  37.       printf("error %d writing socket ", errno);  
  38.       exit(1);  
  39.     }  
  40.     /* finished */  
  41.     close(sockets[1]);  
  42.  } else { /* the child */  
  43.     /* close parent's end of socket */  
  44.     close(sockets[1]);  
  45.     /* send message to parent */  
  46.     if (write(sockets[0], DATA2, sizeof(DATA1)) < 0) {  
  47.       printf("error %d writing socket ", errno);  
  48.       exit(1);  
  49.     }  
  50.     /* get message from parent */  
  51.     if (read(sockets[0], buf, sizeof(buf)) < 0) {  
  52.       printf("error %d reading socket ", errno);  
  53.       exit(1);  
  54.     }  
  55.     printf("child: -->%s/n ", buf);  
  56.     /* finished */  
  57.     close(sockets[0]);  
  58.   }  
  59. }  
从上面的代码中,我们可以发现,父进程从sockets[1]中读写,子进程从sockets[0]中读写,的确是全双工形态。
唯一值得考虑的是为什么在父子进程中,要分别关闭sockets[0]和sockets[1]呢?

  三。一个实现
[cpp]  view plain copy
  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. #include <netinet/in.h>  
  4. #include <fcntl.h>  
  5. #include <stdio.h>  
  6. socketpair(af, type, protocol, fd)  
  7. int af;  
  8. int type;  
  9. int protocol;  
  10. int fd[2];  
  11.  {  
  12.  int listen_socket;  
  13.  struct sockaddr_in sin[2];  
  14.  int len;  
  15.  /* The following is only valid if type == SOCK_STREAM */  
  16.  if (type != SOCK_STREAM)  
  17.   return -1;  
  18.  /* Create a temporary listen socket; temporary, so any port is good */  
  19.  listen_socket = socket(af, type, protocol);  
  20.  if (listen_socket < 0)  
  21.   {  
  22.   perror("creating listen_socket");  
  23.   return -1;  
  24.   }  
  25.  sin[0].sin_family = af;  
  26.  sin[0].sin_port = 0; /* Use any port number */  
  27.  sin[0].sin_addr.s_addr = inet_makeaddr(INADDR_ANY, 0);  
  28.  if (bind(listen_socket, &sin[0], sizeof(sin[0])) < 0)  
  29.   {  
  30.   perror("bind");  
  31.   return -1;  
  32.   }  
  33.  len = sizeof(sin[0]);  
  34.  /* Read the port number we got, so that our client can connect to it */  
  35.  if (getsockname(listen_socket, &sin[0], &len) < 0)  
  36.   {  
  37.   perror("getsockname");  
  38.   return -1;  
  39.   }  
  40.  /* Put the listen socket in listening mode */  
  41.  if (listen(listen_socket, 5) < 0)  
  42.   {  
  43.   perror("listen");  
  44.   return -1;  
  45.   }  
  46.  /* Create the client socket */  
  47.  fd[1] = socket(af, type, protocol);  
  48.  if (fd[1] < 0)  
  49.   {  
  50.   perror("creating client_socket");  
  51.   return -1;  
  52.   }  
  53.  /* Put the client socket in non-blocking connecting mode */  
  54.  fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL, 0) | O_NDELAY);  
  55.  if (connect(fd[1], &sin[0], sizeof(sin[0])) < 0)  
  56.   {  
  57.   perror("connect");  
  58.   return -1;  
  59.   }  
  60.  /* At the listen-side, accept the incoming connection we generated */  
  61.  len = sizeof(sin[1]);  
  62.  if ((fd[0] = accept(listen_socket, &sin[1], &len)) < 0)  
  63.   {  
  64.   perror("accept");  
  65.   return -1;  
  66.   }  
  67.  /* Reset the client socket to blocking mode */  
  68.  fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL, 0) & ~O_NDELAY);  
  69.  close(listen_socket);  
  70.  return 0;  
  71.  }  
采用的是unix domain socket的技术,首先创建一个监听socket,因为是临时性的,其绑定的端口和Ip地址都可以任意(由系统指定即可), 然后执行listen;接着创建client socket,其描述符为fd[1],执行connect;最后返回服务端,执行accept,返回的描述符就是实际通信的描述符fd[0]。

我们知道父进程在子进程被fork出来之前打开的文件描述符是能被子进程继承下来的,但是一旦子进程已经创建后,父进程打开的文件描述符要怎样才能传递给子进程呢?Unix提供相应的技术来满足这一需求,这就是同一台主机上进程间的文件描述符传递,很美妙而且强大的技术。

想象一下我们试图实现一个服务器,接收多个客户端的连接,我们欲采用多个子进程并发的形式来处理多客户端的同时连接,这时候我们可能有两种想法:
1、客户端每建立一条连接,我们fork出一个子进程负责处理该连接;
2、预先创建一个进程池,客户端每建立一条链接,服务器就从该池中选出一个空闲(Idle)子进程来处理该连接。
后者显然更高效,因为减少了子进程创建的性能损耗,反应的及时性大大增强。这里恰恰就出现了我们前面提到的问题,所有子进程都是在服务器Listen到一条连接以前就已经fork出来了,也就是说新的连接描述符子进程是不知道的,需要父进程传递给它,它接收到相应的连接描述符后,才能与相应的客户端进行通信处理。这里我们就可以使用'传递文件描述符'的方式来实现。

在'UNIX网络编程第1卷'的14.7小节中对这种技术有详细的阐述,实际上这种技术就是利用sendmsg和recvmsg在一定的UNIX域套接口(或者是某种管道)上发送和接收一种特殊的消息,这种消息可以承载'文件描述符'罢了,当然操作系统内核对这种消息作了特殊的处理。在具体一点儿'文件描述符'是作为辅助数据(Ancillary Data)通过msghdr结构中的成员msg_control(老版本中称为msg_accrights)发送和接收的。值得一提的是发送进程在将'文件描述符'发送出去后,即使立即关闭该文件描述符,该文件描述符对应的文件设备也没有被真正的关闭,其引用计数仍然大于一,直到接收进程成功接收后,再关闭该文件描述符,如果这时文件设备的引用计数为0,那么才真正关闭该文件设备。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值