一.概念及用途
一個問題:如何建立全雙工系統管道?
直接的辦法當然是pipe兩次,建立兩組管道,但是有沒有更簡單的呢?
socketpair就可以了,man socketpair:
socketpair - create a pair of connected sockets, The two sockets are indistinguishable,也就是說,用socketpair建立出來的兩個描述符應該是等價的。
二.用法
#define DATA1 "test string 1"
#define DATA2 "test string 2"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
main ( )
{
int sockets[2], child;
char buf[1024];
/* Get the socket pair */
if ( socketpair(AF_UNIX, SOCK_STREAM, 0, sockets ) < 0 ) {
printf("error %d on socketpair ", errno);
exit(1);
}
/* create child process */
if ( ( child = fork() ) == -1 ) {
printf("fork error %d ", errno);
exit(1);
}
if (child != 0) { /* this is the parent */
/* close child's end of socket */
close( sockets[0] );
/* read message from child */
if ( read(sockets[1], buf, sizeof(buf) ) < 0) {
printf("error %d reading socket ", errno);
exit(1);
}
printf("parent:-->%s \n", buf);
/* write message to child */
if ( write(sockets[1], DATA1, sizeof(DATA1)) < 0 ) {
printf("error %d writing socket ", errno);
exit(1);
}
/* finished */
close(sockets[1]);
}
else
{ /* the child */
/* close parent's end of socket */
close(sockets[1]);
/* send message to parent */
if (write(sockets[0], DATA2, sizeof(DATA1)) < 0) {
printf("error %d writing socket ", errno);
exit(1);
}
/* get message from parent */
if (read(sockets[0], buf, sizeof(buf)) < 0) {
printf("error %d reading socket ", errno);
exit(1);
}
printf("child: -->%s\n ", buf);
/* finished */
close(sockets[0]);
}
}
從上面的代碼中,我們可以發現,父進程從sockets[1]中讀寫,子進程從sockets[0]中讀寫,的確是全雙工系統形態。
唯一值得考慮的是為什麼在父子進程中,要分別關閉sockets[0]和sockets[1]呢?
三。一個實現
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdio.h>
socketpair(af, type, protocol, fd)
int af;
int type;
int protocol;
int fd[2];
{
int listen_socket;
struct sockaddr_in sin[2];
int len;
/* The following is only valid if type == SOCK_STREAM */
if (type != SOCK_STREAM)
return -1;
/* Create a temporary listen socket; temporary, so any port is good */
listen_socket = socket(af, type, protocol);
if (listen_socket < 0)
{
perror("creating listen_socket");
return -1;
}
sin[0].sin_family = af;
sin[0].sin_port = 0; /* Use any port number */
sin[0].sin_addr.s_addr = inet_makeaddr(INADDR_ANY, 0);
if (bind(listen_socket, &sin[0], sizeof(sin[0])) < 0)
{
perror("bind");
return -1;
}
len = sizeof(sin[0]);
/* Read the port number we got, so that our client can connect to it */
if (getsockname(listen_socket, &sin[0], &len) < 0)
{
perror("getsockname");
return -1;
}
/* Put the listen socket in listening mode */
if (listen(listen_socket, 5) < 0)
{
perror("listen");
return -1;
}
/* Create the client socket */
fd[1] = socket(af, type, protocol);
if (fd[1] < 0)
{
perror("creating client_socket");
return -1;
}
/* Put the client socket in non-blocking connecting mode */
fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL, 0) | O_NDELAY);
if (connect(fd[1], &sin[0], sizeof(sin[0])) < 0)
{
perror("connect");
return -1;
}
/* At the listen-side, accept the incoming connection we generated */
len = sizeof(sin[1]);
if ((fd[0] = accept(listen_socket, &sin[1], &len)) < 0)
{
perror("accept");
return -1;
}
/* Reset the client socket to blocking mode */
fcntl(fd[1], F_SETFL, fcntl(fd[1], F_GETFL, 0) & ~O_NDELAY);
close(listen_socket);
return 0;
}
採用的是unix domain socket的技術,首先建立一個監聽socket,因為是臨時性的,其綁定的連接埠和Ip地址都可以任意(由系統指定即可), 然後執行listen;接著建立client socket,其描述符為fd[1],執行connect;最後返回服務端,執行accept,返回的描述符就是實際通訊的描述符fd[0]。
四.補充
socketpair還常用於描述符傳遞的處理中。
我們知道父進程在子進程被fork出來之前開啟的檔案描述符是能被子進程繼承下來的,但是一旦子進程已經建立後,父進程開啟的檔案描述符要怎樣才能傳遞給子進程呢?Unix提供相應的技術來滿足這一需求,這就是同一台主機上進程間的檔案描述符傳遞,很美妙而且強大的技術。
想象一下我們試圖實現一個伺服器,接收多個用戶端的串連,我們欲採用多個子進程並發的形式來處理多用戶端的同時串連,這時候我們可能有兩種想法:
1、用戶端每建立一條串連,我們fork出一個子進程負責處理該串連;
2、預先建立一個進程池,用戶端每建立一條連結,伺服器就從該池中選出一個空閑(Idle)子進程來處理該串連。
後者顯然更高效,因為減少了子進程建立的效能損耗,反應的及時性大大增強。這裡恰恰就出現了我們前面提到的問題,所有子進程都是在伺服器Listen到一條串連以前就已經fork出來了,也就是說新的串連描述符子進程是不知道的,需要父進程傳遞給它,它接收到相應的串連描述符後,才能與相應的用戶端進行通訊處理。這裡我們就可以使用'傳遞檔案描述符'的方式來實現。