TCP/IP网络编程 第十一章:进程间通信

进程间通信的基本概念

在上一章中我们讲到,进程自身有独立的内存空间,进程之间是相互独立的存在。因此在没有任何机制的支持下,我们可以将进程看作相互孤立的存在。

但是进程之间在某种程度上也是要"交流"的,下面正式介绍进程间通信的方法.

通过管道实现进程间通信

为了完成进程间通信,需要创建管道。管道并非属于进程的资源,而是和套接字一样,属于操作系统(也就不是fork函数的复制对象)。所以,两个进程通过操作系统提供的内存空间进行通信。下面介绍创建管道的函数。

cpp 复制代码
#include<unistd.h>
int pipe(int filedes[2]);//成功时返回0,失败时返回-1。
    filedes[0]  //通过管道接收数据时使用的文件描述符,即管道出口。
    filedes[1]  //通过管道传输数据时使用的文件描述符,即管道入口。

以2个元素的int数组地址值作为参数调用上述函数时,数组中存有两个文件描述符,它们将被用作管道的出口和入口。父进程调用该函数时将创建管道,同时获取对应于出入口的文件描述符,此时父进程可以读写同一管道(相信大家也做过这样的实验)。但父进程的目的是与子选程进行数据交换,因此需要将入口或出口中的1个文件描述符传递给子进程。如何完成传递呢?答案就是调用fork函数。通过下列示例进行演示。

cpp 复制代码
#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路径,但子进程仅用输入路径,父进程仅用输出路径。

通过管道进行进程间双向通信

先给出示例代码稍作讨论

cpp 复制代码
#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秒,但已引发运行错误。产生原因是什么呢?

"向管道传递数据时,先读的进程会把数据取走。"

这里管程是操作系统分配的一个地址空间,它不属于那两个进程。那么两个程序在运行时,一个进程将内容放入这个"无主之地"。如果放入的这一方不等待几秒而是直接往下运行,那么反而它自己放入的内容被自己取走了。时间片耗尽后,轮到另一个进程时,它便会被无限期的堵塞在读取的函数中,因此产生错误。

从上述示例中可以看到,只用1个管道进行双向通信并非易事。为了实现这一点,程序需要预测并控制运行流程,这在每种系统中都不同,可以视为不可能完成的任务。既然如此,该如何进行双向通信呢?

"创建2个管道。"

非常简单,1个管道无法完成双向通信任务,因此需要创建2个管道,各自负责不同的数据流

动即可。

下面是示例代码

cpp 复制代码
#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;
}

运用进程间通信

保存消息的回声服务器端

下面扩展第10章的回声服务器端,添加如下功能:"将回声客户端传输的字符串按序保存到文件中。"

我希望将该任务委托给另外的进程。换言之,另行创建进程,从向客户端提供服务的进程读

取字符串信息。当然,该过程中需要创建用于接收数据的管道。

下面给出示例。该示例可以与任意回声客户端配合运行。

cpp 复制代码
#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){
//与上一章示例一致,故省略。
}
相关推荐
骑上单车去旅行5 小时前
MD5校验对比脚本
linux·服务器·windows
平生幻5 小时前
Linux 常用命令
linux
期待着20135 小时前
docker 安装 ,在centos7.9
运维·docker·容器
踏月的造梦星球5 小时前
DM8 DSC 单机双实例部署
运维·开发语言·数据库
MXsoft6186 小时前
平台化是智能运维发展的必然趋势吗?
运维
ShirleyWang0126 小时前
让headlamp控制台能访问
linux·服务器·python·k8s·k3s
猿的天空6 小时前
机器人双手迎来全栈训练系统:灵初智能EgoSteer让灵巧手无所不能
网络·人工智能·计算机·ai·程序员·机器人·编程
Xzaveir_7777 小时前
OPPO、vivo、荣耀号码认证:多终端拨测矩阵与异常复现
大数据·网络·科技·矩阵·产品经理·ai-native
liuccn7 小时前
Linux 存储系统:LVM 与直接分区
linux·运维
捷配链7 小时前
六层服务器板行业材料与工艺标准化选型指南
服务器·制造·pcb