《TCP IP网络编程》第十一章

news2024/11/16 22:32:12

第 11 章 进程间通信

11.1 进程间通信的基本概念

通过管道实现进程间通信:

        进程间通信,意味着两个不同的进程中可以交换数据。下图是基于管道(PIPE)的进程间通信的模型:

 

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

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

        父进程调用函数时将创建管道,同时获取对应于出入口的文件描述符,此时父进程可以读写同一管道。但父进程的目的是与子进程进行数据交换,因此需要将入口或出口中的 1 个文件描述符传递给子进程。下面的例子是关于该函数的使用方法:

#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
    int fds[2]; // 用于存储管道的文件描述符,fds[0] 用于读取,fds[1] 用于写入
    char str[] = "Who are you?"; // 待写入管道的字符串
    char buf[BUF_SIZE]; // 用于存储从管道读取的数据
    pid_t pid;

    // 调用 pipe 函数创建管道,fds 数组中保存用于 I/O 的文件描述符
    pipe(fds);

    pid = fork(); // 创建子进程,子进程将同时拥有创建管道获取的2个文件描述符,复制的并非管道,而是文件描述符

    if (pid == 0) // 子进程
    {
        write(fds[1], str, sizeof(str)); // 将字符串写入管道的写入端
    }
    else // 父进程
    {
        read(fds[0], buf, BUF_SIZE); // 从管道的读取端读取数据
        puts(buf); // 将读取的数据打印到屏幕上
    }

    return 0;
}

 运行结果:

        可以从程序中看出,首先创建了一个管道,子进程通过 fds[1] 把数据写入管道,父进程从 fds[0] 再把数据读出来。可以从下图看出: 

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

        下图可以看出双向通信模型:

        下面是双向通信的代码示例: 

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

运行结果:

         红色结果是正常运行的结果,蓝色是注释掉sleep(2)这行代码之后的结果。

         向管道传递数据时,先读的进程会把数据取走。数据在进入管道之后成为无主数据,也就是先通过read函数先读到数据的进程将得到数据,即使是 该进程将此数据传到了管道中。因此,若注释掉那行代码,子进程将读会自己之前向管道放入的数据,结果就是,父进程掉用read函数后无限期等待数据放入管道。

        当一个管道不满足需求时,就需要创建两个管道,各自负责不同的数据流动,过程如下图所示:

        使用两个管道可以避免程序流程的预测和控制。用上述模型改进的代码示例:

#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));
    }
    return 0;
}

        运行结果:

         上面通过创建两个管道实现了功能,此时,不需要额外再使用 sleep 函数。运行结果和上面一样。

11.2 运用进程间通信

保存消息的回声服务器:

下面对第 10 章的服务器端代码进行改进,添加一个功能:

将回声客户端传输的字符串按序保存到文件中。

实现该任务将创建一个新进程,从向客户端提供服务的进程读取字符串信息,下面是代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

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]);
        exit(1);
    }

    act.sa_handler = read_childproc; // 防止僵尸进程
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0); // 注册信号处理器,将成功的返回值给 state

    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) // 分配IP地址和端口号
        error_handling("bind() error");

    if (listen(serv_sock, 5) == -1) // 进入等待连接请求状态
        error_handling("listen() error");

    pipe(fds); // 创建管道,fds[0] 用于从子进程读取数据,fds[1] 用于向子进程写入数据

    pid = fork();
    if (pid == 0)
    {
        // 子进程运行区域,此部分从客户端接收数据并写入文件
        FILE *fp = fopen("echomsg.txt", "wt");
        char msgbuf[BUF_SIZE];
        int i, len;
        for (int 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, BUFSIZ)) != 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); // 通过 accept 函数创建的套接字文件描述符已经复制给子进程,因为服务器端要销毁自己拥有的
    }

    close(serv_sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid(-1, &status, WNOHANG);
    printf("removed proc id: %d \n", pid);
}

        该代码创建了一个服务器,并使用多进程处理客户端连接。每当有新的客户端连接到服务器时,会创建一个新的子进程来处理与该客户端的通信。子进程将接收来自客户端的数据,并将数据回送给客户端。同时,子进程还会将收到的数据写入到名为"echomsg.txt"的文件中。父进程用于接受客户端的连接,并通过管道将客户端发送的数据传递给子进程进行处理。

         配合第十章的客户端代码运行结果:

服务器端:

客户端: 

 生成的txt文件:

        从图上可以看出,服务端已经生成了文件,把客户端的消息保存下来。


习题 :

1、什么是进程间通信?分别从概念和内存的角度进行说明。

        从概念上讲,进程间通信是指两个或多个进程之间交换数据、共享资源或进行通信的机制。它允许不同进程之间进行数据传递和信息交换,从而实现协作和资源共享。

        从内存的角度来看,进程间通信意味着不同进程之间需要访问彼此的内存空间。由于每个进程有独立的虚拟内存空间,进程间不能直接访问对方的内存。因此,需要通过特定的IPC机制,如管道、共享内存、消息队列、信号量等,来实现数据的传递和共享。

2、进程间通信需要特殊的 IPC 机制,这是由于操作系统提供的。进程间通信时为何需要操作系统的帮助?

        为了进行进程间通信,需要管道的帮助,但是管道不是进程的资源,它属于从操作系统,所以,两个进程通过操作系统提供的内存空间进行通信。

        操作系统作为进程的管理者和协调者,提供了特殊的IPC机制。这样,进程间可以协同工作,共享信息和资源,实现复杂的计算和任务。同时,操作系统对进程间通信进行控制和保护,确保系统的稳定性和安全性。

3、「管道」是典型的 IPC 技法。关于管道,请回答以下问题:

i、管道是进程间交换数据的路径。如何创建此路径?由谁创建?

        使用 pipe 函数进行创建,由操作系统创建。父进程调用该函数时将创建管道。+

ii、为了完成进程间通信。2 个进程要同时连接管道。那2 个进程如何连接到同一管道?

        数组中有两个文件描述符,父子进程调用相关函数时,通过 fork 函数,把 1 个文件描述符传递给子进程。      

iii、  管道允许 2 个进程间的双向通信。双向通信中需要注意哪些内容?

        向管道传输数据时,先读的进程会把数据取走。简言之,就是数据进入管道后会变成无主数据,会被先调用read函数的进程读取,这个进程可能是自己。所以有时候为了防止错误,需要多个管道来进程通信。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/800606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React入门学习笔记1

前言 React是一个用来动态构0建用户界面的JavaScript库&#xff08;只关注于视图&#xff09;。它可以帮助开发人员轻松地创建复杂的交互式界面&#xff0c;以及管理与用户交互的状态。相比于其他的JavaScript框架&#xff0c;React采用了一种不同的编程模型&#xff0c;称为“…

单片机中的通用LED驱动

前言 项目中需要用到很多的LED灯&#xff0c;存在不同的闪烁方式&#xff0c;比如单闪&#xff0c;双闪&#xff0c;快闪&#xff0c;慢闪等等&#xff0c;我需要一个有如下特性的LED驱动 方便的增加不同闪烁模式可以切换闪烁模式增加LED数目不会有太多的改动方便移植&#x…

《焊接点云处理》-V型焊缝处理

焊接点云处理-V型焊缝处理 前言一、代码二、处理步骤前言 处理效果 一、代码 主函数 #include "CGALRECONSTRUCT.h" #include"PCLFUNCTION.h"int main(int argc

vue+leaflet笔记之地图聚合

vueleaflet笔记之地图聚合 文章目录 vueleaflet笔记之地图聚合开发环境代码简介插件简介与安装使用简介 详细源码(Vue3) 本文介绍了Web端使用Leaflet开发库进行地图聚合查询的一种方法 (底图来源:中科星图)&#xff0c;结合Leaflet.markercluster插件能够快速的实现地图聚合查询…

【C++】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动

[导读]本系列博文内容链接如下&#xff1a; 【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值 【C】做一个飞机空战小游戏(二)——利用getch()函数实现键盘控制单个字符移动 在【C】做一个飞机空战小游戏(一)——使用getch()函数获得键盘码值一文中介绍了如何利用…

4.4 if选择结构

4.4 if选择结构 if单选择结构 我们很多时候需要去判断一个东西是否可行&#xff0c;然后我们才去执行&#xff0c;这样的一个过程在程序中用if语句来表示 语法 if(布尔表达式){//如果布尔表达式为true将执行的语句 }if单选择结构流程图 package com.baidu.www.struct;import …

JVM源码剖析之JIT工作流程

版本信息&#xff1a; jdk版本&#xff1a;jdk8u40思想至上 Hotspot中执行引擎分为解释器、JIT及时编译器&#xff0c;上篇文章描述到解释器过度到JIT的条件。JVM源码剖析之达到什么条件进行JIT优化 这篇文章大致讲述JIT的编译过程。在JDK中javac和JIT两部分跟编译原理挂钩&a…

MySQL 服务器的调优策略

点击上方↑“追梦 Java”关注&#xff0c;一起追梦&#xff01; 在工作中&#xff0c;我们发现慢查询一般有2个途径&#xff0c;一个是被动的&#xff0c;一个是主动的。被动的是当业务人员反馈某个查询界面响应的时间特别长&#xff0c;你才去处理。主动的是通过通过分析慢查询…

原生html—摆脱ps、excel 在线绘制财务表格加水印(html绘制表格js加水印)

文章目录 ⭐前言⭐html标签&#x1f496;table表格的属性&#x1f496;实现财务报表 ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享原生html——绘制表格报表加水印。 背景&#xff1a;解决没有ps的情况下使用前端html制作表格报表。 html介绍 HTML&#xf…

【雕爷学编程】Arduino动手做(93)--- 0.96寸OLED液晶屏模块8

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

C++ inline 内联函数

1.什么是内联函数 在函数声明或定义中&#xff0c;在函数的返回类型前加上C关键字inline&#xff0c;即将函数指定为内联函数。这样可以**解决一些频繁调用的简单函数大量消耗栈空间&#xff08;栈内存&#xff09;**的问题。关键字inline必须与函数定义放在一起才能使函数成为…

C++模拟实现queue

1.前言 queue 遵循的原则是先进先出&#xff0c;那到底是用list 还是 vector呢&#xff1f;其实都可以&#xff0c;但是严格来讲vector是不可以的&#xff0c;因为他头删的效率太低了。所以vs官方是不允许用vector的&#xff1a; 因为底层的pop用的是pop_front(), vector是没有…

【C刷题】矩阵相等判断与序列中删除指定的数字

目录 BC105-矩阵相等判断 方法1:两矩阵输入完毕后&#xff0c;进行比较 方法2:在接收过程中直接比较 BC98 - 序列中删除指定的数字 方法1:把要删除的元素改为0 方法2:打印不用删除的元素 方法3:定义两个下标 i 和 j(动图演示) 此篇文章是关于牛客网刷题的做题思路和代码…

Java版知识付费平台免费搭建 前后端分离实现知识付费平台

提供职业教育、企业培训、知识付费系统搭建服务。系统功能包含&#xff1a;录播课、直播课、题库、营销、公司组织架构、员工入职培训等。 提供私有化部署&#xff0c;免费售后&#xff0c;专业技术指导&#xff0c;支持PC、APP、H5、小程序多终端同步&#xff0c;支持二次开发…

大模型开发(十一):Chat Completions模型的Function calling功能详解

全文共5000余字&#xff0c;预计阅读时间约15~25分钟 | 满满干货(附代码案例)&#xff0c;建议收藏&#xff01; 本文目标&#xff1a;介绍Chat Completions模型的Function calling参数和使用方法&#xff0c;并完整的实现一个Chat模型的Function calling功能案例。 代码下载地…

字节跳动 EB 级 Iceberg 数据湖的机器学习应用与优化

深度学习的模型规模越来越庞大&#xff0c;其训练数据量级也成倍增长&#xff0c;这对海量训练数据的存储方案也提出了更高的要求&#xff1a;怎样更高性能地读取训练样本、不使数据读取成为模型训练的瓶颈&#xff0c;怎样更高效地支持特征工程、更便捷地增删和回填特征。本文…

Java IO,BIO、NIO、AIO

操作系统中的 I/O 以上是 Java 对操作系统的各种 IO 模型的封装&#xff0c;【文件的输入、输出】在文件处理时&#xff0c;其实依赖操作系统层面的 IO 操作实现的。【把磁盘的数据读到内存种】操作系统中的 IO 有 5 种&#xff1a; 阻塞、 非阻塞、【轮询】 异步、 IO复…

STM32的SDIO功能框图及SDIO结构体

目录 STM32的SDIO功能框图及SDIO结构体 STM32的SDIO功能框图 SDIO适配器 命令路径 CPSM状态机 数据路径 DPSM状态机 数据FIFO 适配器寄存器 SDIO相关结构体 SDIO初始化结构体 SDIO命令初始化结构体 SDIO数据初始化结构体 STM32的SDIO功能框图及SDIO结构体 STM32的…

3d软件动物生活习性仿真互动教学有哪些优势

软体动物是一类广泛存在于海洋和淡水环境中的生物&#xff0c;其独特的形态和生活习性给学生带来了新奇和有趣的学习主题&#xff0c;为了方便相关专业学科日常授课教学&#xff0c;web3d开发公司深圳华锐视点基于真实的软体动物&#xff0c;制作软体动物3D虚拟展示系统&#x…

发点实用的快捷键(mac

切换输入法&#xff1a;ctrlspace /ctrloptionspace&#xff08;更快捷 切换网页&#xff1a; shifttab 切换应用界面&#xff1a;alttab 关闭页面&#xff1a;altw 搜索&#xff1a;altspace 展示mac隐藏文件&#xff1a; Commangshift . (点) 以下是一些浏览器快捷键&am…