IP網絡編程之進程與間通信

進程間通信基本概念

進程間通信意味著兩個不同進程間可以交換數據,為了完成這一點,操作系統中應提供兩個進程可以同時訪問的內存空間。但我們知道,進程具有完全獨立的內存結構,就連通過fork函數創建的子進程也不會和父進程共享內存,因此,進程間通信只能通過其他特殊方法完成

基於管道實現進程間通信

圖1-1表示基於管道(PIPE)的進程間通信結構模型

TCP/IP網絡編程之進程與間通信

圖1-1 基於管道的進程間通信模型

從圖1-1可以看到,為了完成進程間通信,需要創建管道。管道並非屬於進程資源,而是和套接字一樣,屬於操作系統資源(也就不是fork函數的複製對象)。下面介紹創建管道函數

#include <unistd.h>

int pipe (int filedes[2]);//成功時返回0,失敗時返回-1

  

  • filedes[0]:通過管道接收數據時使用的文件描述符,即管道出口
  • filedes[1]:通過管道傳輸數據時使用的文件描述符,即管道入口

以長度為2的int數組地址值作為參數調用上述函數時,數組中存有兩個文件描述符,它們將被用作管道的出口和入口。父進程調用該函數時將創建管道,同時獲取對應於出入口的文件描述符,此時父進程可以讀寫同一管道。但父進程的目的是與子進程進行數據交換,因此需要將入口和出口中的一個文件描述符傳遞給子進程,如何完成傳遞呢?答案還是調用fork函數

pipe1.c

#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;

}

  

  • 第12行:調用pipe函數創建管道,fds數組中保存用於I/O的文件描述符
  • 第13行:接著調用fork函數,子進程將同時擁有通過12行函數調用獲取的兩個文件描述符。注意!複製的並非管道,而是用於管道I/O的文件描述符。至此,父子進程同時擁有I/O文件描述符
  • 第16、20行:子進程通過第16行代碼向管道傳遞字符串,父進程通過第20行代碼從管道接收字符串

編譯pipe1.c並運行

# gcc pipe1.c -o pipe1

# ./pipe1

Who are you?

上述示例中的通信方法及路徑如圖1-2所示,重點在於,父子進程都可以訪問管道的I/O路徑,但子進程僅用輸入路徑,父進程僅用輸出路徑

TCP/IP網絡編程之進程與間通信

圖1-2 示例pipe1.c的通信路徑

以上就是管道的基本原理及通信方法,應用管道時還有一部分內容需要注意,通過雙向通信示例進一步說明

通過管道進行進程間雙向通信

下面創建兩個進程通過一個管道進行雙向數據交換的示例,其通信方式如圖1-3所示

TCP/IP網絡編程之進程與間通信

圖1-3 雙向通信模型1

從圖1-3可以看出,通過一個管道可以進行雙向通信,但採用這種模型需格外小心,先給出示例,稍後再討論

pipe2.c

#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;

}

 

  • 第17~20行:子進程運行區域,通過第17行行傳輸數據,通過第19行接收數據。第18行的sleep函數至關重要,這一點稍後再討論
  • 第24~26行:父進程的運行區域,通過第24行接收數據,這是為了接收第17行子進程傳輸的數據。另外通過第26行傳輸數據,這些數據將被第19行的子進程接收
  • 第27行:父進程先終止時會彈出命令提示符,這時子進程仍然在工作,故不會產生問題。這條語句主要是為了防止子進程終止前彈出命令提示符(故可刪除)

編譯pipe2.c並運行

# gcc pipe2.c -o pipe2

# ./pipe2

Parent proc output: Who are you?

Child proc output: Thank you for your message

  

運行結果和我們設想一致,不過如果嘗試將18行的代碼註釋後再運行,雖然這行代碼只將運行時間延遲了兩秒,但一旦註釋便會引發錯誤,是什麼原因呢?

向管道傳遞數據時,先讀的進程會把數據取走。簡言之,數據進入管道後成為無主數據,也就是通過read函數先讀取數據的進程將得到數據,即使該進程將數據傳到了管道。因此,註釋第18行將產生問題,在第19行,子進程將讀回自己在第17行向管道發送的數據。結果父進程調用read函數後將無限期等待數據進入管道

從上述示例可以看到,只用一個管道進行雙向通信並非易事,為了簡化在進行雙向通信時,既然一個管道很難完成的任務,不如就讓兩個管道來一起完成?因此創建兩個管道,各自負責不同的數據流動即可。其過程如圖1-4所示

TCP/IP網絡編程之進程與間通信

圖1-4 雙向通信模型2

由圖1-4可知,使用兩個管道可以解決單單通過一個管道來進行雙向通信的麻煩,下面採用上述模型來改進pipe2.c

pipe3.c

#include <stdio.h>

#include <unistd.h>

#define BUF_SIZE 30

int main(int argc, char *argv[])

{

int fds1[2], fds2[2];

char str1[] = "Who are you?";

char str2[] = "Thank you for your message";

char buf[BUF_SIZE];

pid_t 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;

}  

  • 第13行:創建兩個管道
  • 第17、33行:子進程可以通過數組fds1指向的管道向父進程傳輸數據
  • 第18、25行:父進程可以通過數組fds2指向的管道向子進程傳輸數據
  • 第26行:沒有太大的意義,只是為了延遲父進程終止的插入的代碼

編譯pipe3.c並運行

# gcc pipe3.c -o pipe3

# ./pipe3

Parent proc output: Who are you?

Child proc output: Thank you for your message

  

運用進程間通信

上一節學習了基於管道的進程間通信方法,接下來將其運用到網絡代碼中。如前所述,進程間通信與創建服務端並沒有直接關聯,但有助於理解操作系統

保存消息的回聲服務端

擴展TCP/IP網絡編程之多進程服務端(二)這一章的echo_mpserv.c,添加將回聲客戶端傳輸的字符串按序保存到文件中。我們可以將這個任務交給另外的進程,換言之,另行創建進程,從向客戶端服務的進程字符串信息。當然,該過程需要創建用於接收數據的管道

下面給出示例,該示例可以與任意回聲客戶端配合運行,我們將用之前介紹過的echo_mpserv.c

echo_storeserv.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <signal.h>

#include

#include <arpa>

#include

#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, clnt_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]);/<port>

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_handling("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)

{

pid_t pid;

int status;

pid = waitpid(-1, &status, WNOHANG);

printf("removed proc id: %d \\n", pid);

}

void error_handling(char *message)

{

fputs(message, stderr);

fputc('\\n', stderr);

exit(1);

}   

  • 第47、48行:第47行創建管道,第48行創建負責保存文件的進程
  • 第49~62行:第49行創建的子進程運行區域,該區域從管道出口fds[0]讀取數據並保存到文件中。另外,上述服務端並不終止運行,而是不斷向客戶端提供服務。因此,數據在文件中累計到一定程序即關閉文件,該過程通過第55行的循環完成
  • 第80行:第73行通過fork函數創建的所有子進程將複製第47行創建的管道的文件描述符,因此,可以通過管道入口fds[1]傳遞字符串信息

編譯echo_storeserv.c並運行

# gcc echo_storeserv.c -o echo_storeserv

# ./echo_storeserv 8500

new client connected...

new client connected...

client disconnected...

removed proc id: 8647

removed proc id: 8633

client disconnected...

removed proc id: 8644

  

運行結果echo_mpclient ONE:

# ./echo_mpclient 127.0.0.1 8500

Hello world!

Message from server: Hello world!

Hello Amy!

Message from server: Hello Amy!

Hello Tom!

Message from server: Hello Tom!

Hello Jack!

Message from server: Hello Jack!

Hello Rose!

Message from server: Hello Rose!

q  

運行結果echo_mpclient TWO:

# ./echo_mpclient 127.0.0.1 8500

Hello Java!

Message from server: Hello Java!

Hello Python!

Message from server: Hello Python!

Hello Golang!

Message from server: Hello Golang!

Hello Spring!

Message from server: Hello Spring!

Hello Flask!

Message from server: Hello Flask!

q

打印echomsg.txt文件

# cat echomsg.txt

Hello world!

Hello Amy!

Hello Java!

Hello Python!

Hello Tom!

Hello Jack!

Hello Rose!

Hello Golang!

Hello Spring!

Hello Flask!

  

如上運行結果所示,啟動多個客戶端向服務端傳輸數據時,文件中累計一定數量的字符串後(共調用十次fwrite函數),可以打開echomsg.txt存入字符串

更多c/c++ linux免費視頻資料獲取 後臺私信【架構】

詳情請看課程大綱

TCP/IP網絡編程之進程與間通信

TCP/IP網絡編程之進程與間通信

TCP/IP網絡編程之進程與間通信

TCP/IP網絡編程之進程與間通信


分享到:


相關文章: