プロセス間でsocketを渡す方法

6956 ワード

変換元:http://blog.sina.com.cn/s/blog_4ed630e801000be4.html
親プロセスがforkによってサブプロセスが出てくる前に開いたファイル記述子はプロセスに継承されることを知っていますが、サブプロセスが作成されると、親プロセスが開いたファイル記述子はどのようにしてサブプロセスに渡されますか?Unixは、同じホスト上のプロセス間のファイル記述子伝達であり、素晴らしい強力な技術を提供します.
想像してみてください.私たちは1つのサーバーを実現し、複数のクライアントの接続を受信しようとしています.私たちは複数のサブプロセスが同時に接続を処理する形式を採用したいと思っています.この時、私たちは2つの考えがあります.1、クライアントが接続を確立するたびに、私たちはforkがサブプロセスを出して接続を処理します.2.事前にプロセスプールを作成し、クライアントがリンクを確立するたびに、サーバはそのプールから空き(Idle)サブプロセスを選択して接続を処理する.後者は、サブプロセスによって作成される性能損失を低減するため、反応のタイムリー性が大幅に向上することは明らかである.ここでは、サーバListenが接続に接続される前からforkが出ているという問題が発生しています.つまり、新しい接続記述子サブプロセスは知られていません.親プロセスがそれに伝達され、対応する接続記述子を受信してから、対応するクライアントと通信処理を行う必要があります.ここでは、「ファイル記述子を渡す」方法で実現できます.「UNIXネットワークプログラミング第1巻」の14.7節では、sendmsgとrecvmsgを利用して、特定のUNIXドメインスリーブインタフェース(または何らかのパイプ)上で特殊なメッセージを送信し、受信する技術について詳しく説明しています.このメッセージは「ファイル記述子」を搭載することができます.もちろん、オペレーティングシステムカーネルはこのメッセージを特殊に処理しています.具体的には「ファイル記述子」は、msghdr構造のメンバーmsg_を介して補助データ(Ancillary Data)として使用されるコントロール(古いバージョンではmsg_accrightsと呼ばれています)が送信および受信します.なお、送信プロセスは、「ファイル記述子」を送信した後、直ちにファイル記述子を閉じても、そのファイル記述子に対応するファイルデバイスが実際に閉じられず、受信プロセスが正常に受信されるまで参照カウントが1より大きくなり、ファイル記述子が閉じられ、ファイルデバイスの参照カウントが0になると、では、ファイルデバイスを本当に閉じます.OK、以下は簡単なファイル記述子伝達の例であり、この例は、サブプロセスが親プロセスがそれに伝達するファイル記述子に対応するファイルの末尾に特定の「LOGO」文字列を付加する機能を実現する.例環境はSolaris 9+GCC 3.2
/* test_fdpass.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>


#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#include <sys/socket.h> /* for socketpair */


#define MY_LOGO         "-- Tony Bai"


static int send_fd(int fd, int fd_to_send)
{
        struct iovec    iov[1];
        struct msghdr   msg;
        char            buf[1];


        if (fd_to_send >= 0) {
                msg.msg_accrights       = (caddr_t)&fd_to_send;
                msg.msg_accrightslen    = sizeof(int);
        } else {
                msg.msg_accrights       = (caddr_t)NULL;
                msg.msg_accrightslen    = 0;
        }


        msg.msg_name    = NULL;
        msg.msg_namelen = 0;


        iov[0].iov_base = buf;
        iov[0].iov_len  = 1;
        msg.msg_iov     = iov;
        msg.msg_iovlen  = 1;


        if(sendmsg(fd, &msg, 0) < 0) {
                printf("sendmsg error, errno is %d
", errno); return errno; } return 0; } static int recv_fd(int fd, int *fd_to_recv) { struct iovec iov[1]; struct msghdr msg; char buf[1]; msg.msg_accrights = (caddr_t)fd_to_recv; msg.msg_accrightslen = sizeof(int); msg.msg_name = NULL; msg.msg_namelen = 0; iov[0].iov_base = buf; iov[0].iov_len = 1; msg.msg_iov = iov; msg.msg_iovlen = 1; if (recvmsg(fd, &msg, 0) < 0) { return errno; } if(msg.msg_accrightslen != sizeof(int)) { *fd_to_recv = -1; } return 0; } int x_sock_set_block(int sock, int on) { int val; int rv; val = fcntl(sock, F_GETFL, 0); if (on) { rv = fcntl(sock, F_SETFL, ~O_NONBLOCK&val); } else { rv = fcntl(sock, F_SETFL, O_NONBLOCK|val); } if (rv) { return errno; } return 0; } int main() { pid_t pid; int sockpair[2]; int rv; char fname[256]; int fd; rv = socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair); if (rv < 0) { printf("Call socketpair error, errno is %d
", errno); return errno; } pid = fork(); if (pid == 0) { /* in child */ close(sockpair[1]); for ( ; ; ) { rv = x_sock_set_block(sockpair[0], 1); if (rv != 0) { printf("[CHILD]: x_sock_set_block error, errno is %d
", rv); break; } rv = recv_fd(sockpair[0], &fd); if (rv < 0) { printf("[CHILD]: recv_fd error, errno is %d
", rv); break; } if (fd < 0) { printf("[CHILD]: child process exit normally!
"); break; } /* fd */ rv = write(fd, MY_LOGO, strlen(MY_LOGO)); if (rv < 0) { printf("[CHILD]: write error, errno is %d
", rv); } else { printf("[CHILD]: append logo successfully
"); } close(fd); } exit(0); } /* in parent */ for ( ; ; ) { memset(fname, 0, sizeof(fname)); printf("[PARENT]: please enter filename:
"); scanf("%s", fname); if (strcmp(fname, "exit") == 0) { rv = send_fd(sockpair[1], -1); if (rv < 0) { printf("[PARENT]: send_fd error, errno is %d
", rv); } break; } fd = open(fname, O_RDWR | O_APPEND); if (fd < 0) { if (errno == ENOENT) { printf("[PARENT]: can't find file '%s'
", fname); continue; } printf("[PARENT]: open file error, errno is %d
", errno); } rv = send_fd(sockpair[1], fd); if (rv != 0) { printf("[PARENT]: send_fd error, errno is %d
", rv); } close(fd); } wait(NULL); return 0; }

コンパイル:gcc-o test_fdpass -lsocket -lnsl test_fdpass.c
実行:test_fdpass(事前に同じディレクトリの下にファイルkk.logを作成)
[PARENT]: please enter filename:
kk.log
[CHILD]: append logo successfully
[PARENT]: please enter filename:
cc.log
[PARENT]: can't find file 'cc.log'
exit
[CHILD]: child process exit normally!
kk.logの内容の末尾に私の独特なLOGO'--Tony Bai'が加わっていることがわかります.^^;
ファイルディスクリプタ伝達の詳細については、W.Richard Stevensの「UNIXネットワークプログラミング第1巻」と「UNIX環境高度プログラミング」の2冊に詳細が記載されており、参照すればよい.