プロセス間通信の基本概念
前の章では、プロセス自体が独自の独立したメモリ空間を持ち、プロセスは互いに独立して存在すると述べました。したがって、それをサポートするメカニズムがなければ、プロセスは互いに孤立して存在すると考えることができます。
ただし、プロセス間でもある程度は「通信」する必要があるため、以下ではプロセス間通信の方法を正式に紹介します。
パイプを介したプロセス間通信
プロセス間通信を行うには、パイプを作成する必要があります。パイプはプロセスに属するリソースではありませんが、ソケットと同様にオペレーティング システムに属します (つまり、fork 関数のコピー オブジェクトではありません)。したがって、2 つのプロセスは、オペレーティング システムによって提供されるメモリ空間を介して通信します。パイプラインを作成する関数を以下に説明します。
#include<unistd.h>
int pipe(int filedes[2]);//成功时返回0,失败时返回-1。
filedes[0] //通过管道接收数据时使用的文件描述符,即管道出口。
filedes[1] //通过管道传输数据时使用的文件描述符,即管道入口。
上記の関数が 2 要素の int 配列アドレス値をパラメータとして呼び出した場合、配列には 2 つのファイル記述子が格納され、パイプラインの出口と入口として使用されます。親プロセスがこの関数を呼び出すと、パイプが作成され、入口と出口に対応するファイル記述子が取得されますが、このとき親プロセスは同じパイプを読み書きすることができます(誰もがそんな実験をしたことがあると思います)。ただし、親プロセスの目的は子プロセスとデータを交換することであるため、入り口または出口のファイル記述子を子プロセスに渡す必要があります。転送を完了するにはどうすればよいですか? 答えは、fork 関数を呼び出すことです。次の例で説明します。
#include <stdio.h>
#include <unistd.h>#
define BUF_SIZE 30
int main(int argc, char *argv[]){
int fds[2];
char str[]="who are you?";
char buf[BUF_SIZE];
pid_t pid;
pipe(fds);
pid=fork();
if(pid==0)write(fds[1], str, sizeof(str));
else{
read(fds[0], buf, BUF_SIZE);
puts(buf);
}
return 0;
}
上記のコードの重要な点は、親プロセスと子プロセスの両方がパイプラインの IO パスにアクセスできるが、子プロセスは入力パスのみを使用し、親プロセスは出力パスのみを使用することです。
パイプを介したプロセス間の双方向通信
最初に少し説明するためにサンプルコードを示します
#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[]){
int fds[2];
char str1[]="who are you?";
char str2[]="Thank you for your message";
char buf[BUF_SIZE];
pid_t pid;
pipe(fds);
pid=fork();
if(pid==0){
write(fds[1], str1, sizeof(str1));
sleep(2);
read(fds[0], buf, BUF_SIZE);
printf("Child proc output: %s \n",buf);
}
else{
read(fds[0], buf, BUF_SIZE);
printf("Parent proc output: %s \n",buf);
write(fds[1], str2, sizeof(str2));
sleep(3);
}
return 0;
}
実行結果は全員の期待と一致するはずです。今回はコードの 18 行目をコメント化し、再度実行します (必ず自分で実行してください)。
このコード行は実行時間を 2 秒遅らせるだけですが、実行時エラーがスローされます。理由は何ですか?
「パイプにデータを渡すとき、最初に読み取るプロセスがデータを奪います。」
ここで、モニターとはオペレーティング システムによって割り当てられるアドレス空間であり、これら 2 つのプロセスには属しません。次に、2 つのプログラムが実行されているときに、1 つのプロセスがコンテンツをこの「無人地帯」に配置します。入れた側が数秒も待たずにそのまま走り去ってしまうと、入れたコンテンツは勝手に奪われてしまいます。タイム スライスが使い果たされた後、別のプロセスの番になると、読み取り関数で無期限にブロックされ、エラーが生成されます。
上の例からわかるように、パイプが 1 つだけの双方向通信は簡単ではありません。これを実現するには、プログラムが動作の流れを予測して制御する必要がありますが、これはシステムごとに異なり、不可能とも言えます。では、どうやって双方向のコミュニケーションをとればよいのでしょうか?
「パイプラインを 2 つ作成します。」
これは非常に簡単で、1 つのパイプラインでは双方向通信タスクを完了できないため、それぞれが異なるデータ フローを担当する 2 つのパイプラインを作成する必要があります
。
以下はサンプルコードです
#include<stdio.h>
#include<unistd.h>
#define BUF_SIZE 30
int main(int argc, char *argv[]){
int fds1[2], fds2[2];
char stri[]="who are you?";
char str2[]="Thank you for your message";
char buf[BUF_SIZE];
pidt pid;
pipe(fds1), pipe(fds2);
pid=fork();
if(pid==0){
write(fds1[1], str1, sizeof(str1));
read(fds2[0], buf, BUF_SIZE);
printf("Child proc output: %s \n", buf);
}
else{
read(fds1[0], buf, BUF_SIZE);
printf("Parent proc output: %s \n", buf);
write(fds2[1], str2, sizeof(str2));
sleep(3);
}
return 0;
}
プロセス間通信を使用する
メッセージを保存するEchoサーバー側
第10章のエコーサーバーを拡張して、「エコークライアントから送信された文字列を順番にファイルに保存する」機能を追加しましょう。
このタスクを別のプロセスに委任したいと考えています。つまり、別プロセスを作成し、クライアントにサービスを提供するプロセスから文字列情報を読み込みます
。もちろん、このプロセスにはデータを受信するためのパイプラインの作成が必要です。
以下に例を示します。この例は、どのエコー クライアントでも動作します。
#include <'头声明与第10章的示例一致。'>
#define BUF_SIZE 100
void error_handling(char *message);
void read_childproc(int sig);
int main(int argc, char *argv[]){
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, cint_adr;
int fds[2];
pid_t pid;
struct sigaction act;
socklen_t adr_sz;
int str_len, state;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}
act.sa_handler=read_childproc;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
state=sigaction(SIGCHLD, &act, 0);
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock,5)==-1)
error_handiing("listen() error");
pipe(fds);
pid=fork();
if(pid==0){
FILE *fp=fopen("echomsg.txt","wt");
char msgbuf[BUF_SIZE];
int i, len;
for(i=0;i<10;i++){
len=read(fds[0], msgbuf, BUF_SIZE);
fwrite((void*)msgbuf,1, len, fp);
}
fclose(fp);
return 0;
}
while(1){
adr_sz=sizeof(clnt_adr);
clnt_sock=accept(serv_sock,(struct sockaddr*)&clnt_adr, &adr_sz);
if(clnt_sock==-1)
continue;
else
puts("new client connected...");
pid=fork();
if(pid==0){
close(serv_sock);
while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0){
write(clnt_sock, buf, str_len);//回声
write(fds[1], buf, str_len); //记录消息
}
close(clnt_sock);
puts("client disconnected...");
return 0;
}
else close(clnt_sock);
}
close(serv_sock);
return 0;
}
void read_childproc(int sig){
//与上一章示例一致,故省略。
}
void error_handling(char* message){
//与上一章示例一致,故省略。
}