在網絡程序設計中,如果沒有進程間通信,那麼軟件的功能肯定會大打折扣。
進程間通信有哪些途徑呢?
1.信號
2.管道
3.消息隊列
4.信號量
5.共享內存
管道:
管道就是將一個程序的輸出與另一個程序的輸入連接起來的單向通道。它是UNIX/Linux中古老而最廣泛的進程間通信的方式。特別是在shell中。
在C語言中我們用Pipe()函數來建立管道:
===============
系統調用: pipe();
函數聲明: int pipe( int fd[2] );
返回值: 0 on success
-1 on error: errno = EMFILE (no free descriptors)
EMFILE (system file table is full)
EFAULT (fd array is not valid)
注意: fd[0] 用來從管道中讀, fd[1] 用來向管道中寫
數組中的第一個元素(fd[0])是從管道中讀出數據的句柄,第二個元素(fd[1])是向
管道寫入數據的句柄。也即是說,fd[1]的寫入由 fd[0]讀出。
使用pipe在子進程中讀入數據,然後在父進程中讀出數據的例子:
====================================================================
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int fd[2];
pid_t childpid;
int nbytes;
char string[] = "Hello,world!\n";
char readbuffer[80];
pipe(fd);
if((childpid = fork()) == -1){
perror("fork error");
exit(1);
}
if(childpid == 0){
//close child process read pipe
close(fd[0]);
//write data by write pipe
write(fd[1], string, strlen(string));
printf("Sended string:%s", string);
_exit(0);
}else{
//close parent process write pipe
close(fd[1]);
// read data by read pipe
nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
printf("Received string:%s", readbuffer);
}
return 0;
}
編譯運行
==========
Received string:Hello,world!
Sended string:Hello,world!
>使用 dup()函數
有時候我們需要將子進程當中的管道的句柄定向到標準 I/O(stdin/stdout)上去。這樣,
在子進程中使用 exec()函數調用外部程序時,這個外部程序就會將管道作爲它的輸入/輸出。
這個過程可以用系統函數 dup()來實現。其函數原型如下:
系統調用: dup();
函數聲明: int dup( int oldfd );
返回值: new descriptor on success
-1 on error: errno = EBADF (oldfd is not a valid descriptor)
EBADF (newfd is out of range)
EMFILE (too many descriptors for the process)
雖然原句柄和新句柄是可以互換使用的,但爲了避免混淆,我們通常會將原句柄關閉
(close)
。同時要注意,在 dup()函數中我們無法指定重定向的新句柄,系統將自動使用未
被使用的最小的文件句柄(記住,句柄是一個整型量)作爲重定向的新句柄。請看下面的
例子:
......
......
pipe(fd);
childpid = fork();
if(childpid == 0)
{
/* 關閉子進程的文件句柄 0(stdin) */
close(0);
/* 將管道的讀句柄定義到 stdin */
dup(fd[0]);
execlp(“
sort” “
, sort” NULL);
,
......
}
>使用 dup2()函數
在 Linux 系統中還有一個系統函數 dup2()。單從函數名上我們也可以判斷出它和 dup()
函數的淵源。下面是它的原型:
系統調用: dup2();
函數聲明: int dup2( int oldfd, int newfd );
返回值: new descriptor on success
-1 on error: errno = EBADF (oldfd is not a valid descriptor)
EBADF (newfd is out of range)
EMFILE(too many descriptors for the process)
注意: 舊句柄將被 dup2()自動關閉
顯然,原來的 close 以及 dup 這一套調用現在全部由 dup2()來完成。這樣不僅簡便了程
序,更重要的是,它保證了操作的獨立性和完整性,不會被外來的信號所中斷。在原來的
dup()調用中,我們必須先調用 close()函數。假設此時恰好一個信號使接下來的 dup()調用不
能立即執行,這就會引發錯誤(進程沒有了 stdin )
。使用 dup2()就不會有這樣的危險。下
面的例子演示了 dup2()函數的使用:
......
pipe(fd);
.
childpid = fork();
if(childpid == 0)
{
/* 將管道的讀入端定向到 stdin */
dup2(0, fd[0]);
execlp("sort", "sort", NULL);
......
}
>使用 popen()/pclose()函數
看了 dup2()函數,一定有人會想,既然能把 close 和 dup 合成一個函數,那麼有沒有把
fork、exec 和 dup()結合的函數呢?答案是肯定的。它就是 linux 的系統函數 popen():
庫函數: popen();
函數聲明: FILE *popen ( char *command, char *type);
返回值: new file stream on success
NULL on unsuccessful fork() or pipe() call
NOTES: creates a pipe, and performs fork/exec operations using "command"
popen()函數首先調用 pipe()函數建立一個管道,然後它用 fork()函數建立一個子進程,
運行一個 shell 環境,然後在這個 shell 環境中運行"command"參數指定的程序。數據在管道
中流向由"type"參數控制。這個參數可以是"r"或者"w",分別代表讀和寫。需要注意的是,
"r"和"w"兩個參數不能同時使用!在 Linux 系統中,popen 函數將只使用"type"參數中第一
個字符,也就是說,使用"rw"和"r"作爲"type"參數的效果是一樣的,管道將只打開成讀狀態。
使用 popen 打開的管道必須用 pclose()函數來關閉。還記得 fopen 和 fclose 的配對使用
嗎?這裏再次顯示了管道和文件的相似性。
庫函數: pclose();
函數聲明: int pclose( FILE *stream );
返回值:
exit status of wait4() call
-1 if "stream" is not valid, or if wait4() fails
NOTES: waits on the pipe process to terminate, then closes the stream.
下面是popen和pclose的例子,但是在沒有運行成功,情景如下:
#include<stdio.h>
#define MAXSTRS 5
int main()
{
int cntr;
FILE *pipe_fp;
char *strings[MAXSTRS] = { "roy", "zixia", "gouki", "supper", "mmwan"};
// create pipe with popen
if((pipe_fp = popen("sort", "w")) == NULL ){
perror("popen failed");
exit(1);
}
// Processing loop
for(cntr = 0; cntr < MAXSTRS; cntr++){
fputs(strings[cntr], pipe_fp);
fputs('\n', pipe_fp);
}
//close pipe
pclose(pipe_fp);
return 0;
}
編譯運行
===========
[root@explore code]# gcc popen_pclose.c
popen_pclose.c: In function ‘main’:
popen_pclose.c:11:3: warning: incompatible implicit declaration of built-in function ‘exit’ [enabled by default]
popen_pclose.c:16:3: warning: passing argument 1 of ‘fputs’ makes pointer from integer without a cast [enabled by default]
/usr/include/stdio.h:684:12: note: expected ‘const char * __restrict__’ but argument is of type ‘int’
[root@explore code]# ./a.out
Segmentation fault (core dumped)
popen()\pclose()的例子
#include<stdio.h>
int main()
{
FILE *pipein_fp, *pipeout_fp;
char readbuffer[80];
// create a pipe to "ls read pipe"
if((pipein_fp = popen("ls", "r")) == NULL){
perror("popen");
exit(1);
}
// create a pipe to "sort write"
if((pipeout_fp = popen("sort", "w")) == NULL){
perror("popen");
exit(1);
}
// Processing loop
while(fgets(readbuffer, 80, pipein_fp)){
fputs(readbuffer, pipeout_fp);
}
// close pipe
pclose(pipein_fp);
pclose(pipeout_fp);
return 0;
}
編譯運行
============
[root@explore code]# gcc popen_pclose_2.c
popen_pclose_2.c: In function ‘main’:
popen_pclose_2.c:9:3: warning: incompatible implicit declaration of built-in function ‘exit’ [enabled by default]
popen_pclose_2.c:14:3: warning: incompatible implicit declaration of built-in function ‘exit’ [enabled by default]
[root@explore code]# ./a.out
a.out
gentmp.c
pipe.c
popen_pclose_2.c
popen_pclose.c
以下是一些在管道的使用中需要注意的問題:
1. ipe()的調用必須在 fork()之前;
p
2.及時關閉不需要的管道句柄;
3.使用 dup()之前確定定向的目標是最小的文件句柄;
4.管道只能實現父子進程間的通信,如果兩個進程之間沒有 fork()關係,就必須考慮
其他的進程通信方法。