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){
//与上一章示例一致,故省略。
}
相关推荐
JosieBook1 小时前
【远程运维】Linux 远程连接 Windows 好用的软件:MobaXterm 实战指南
linux·运维·windows
文档搬运工1 小时前
Linux MInt启动速度的优化
linux
Broken Arrows1 小时前
Linux学习——管理网络安全(二十一)
linux·学习·web安全
索迪迈科技2 小时前
网络请求库——Axios库深度解析
前端·网络·vue.js·北京百思可瑞教育·百思可瑞教育
Light602 小时前
领码方案|Linux 下 PLT → PDF 转换服务超级完整版:异步、权限、进度
linux·pdf·可观测性·异步队列·plt转pdf·权限治理·进度查询
鳄鱼杆2 小时前
服务器 | Docker应用开发与部署的实践以及阿里云镜像加速配置
服务器·阿里云·docker
羚羊角uou3 小时前
【Linux】命名管道
linux·运维·服务器
IT 小阿姨(数据库)3 小时前
PgSQL监控死元组和自动清理状态的SQL语句执行报错ERROR: division by zero原因分析和解决方法
linux·运维·数据库·sql·postgresql·centos
THMAIL3 小时前
量化股票从贫穷到财务自由之路 - 零基础搭建Python量化环境:Anaconda、Jupyter实战指南
linux·人工智能·python·深度学习·机器学习·金融
曾经的三心草3 小时前
Python2-工具安装使用-anaconda-jupyter-PyCharm-Matplotlib
android·java·服务器