专业建网站价格,昆明网站制作代理,云平台网站叫什么,西部数码网站源码第 11 章 进程间通信
11.1 进程间通信的基本概念
通过管道实现进程间通信#xff1a; 进程间通信#xff0c;意味着两个不同的进程中可以交换数据。下图是基于管道#xff08;PIPE#xff09;的进程间通信的模型#xff1a; 可以看出#xff0c;为了完成进程间通信 进程间通信意味着两个不同的进程中可以交换数据。下图是基于管道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 30int 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 30int 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 30int 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 30void 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); // 注册信号处理器将成功的返回值给 stateserv_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;elseputs(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;}elseclose(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函数的进程读取这个进程可能是自己。所以有时候为了防止错误需要多个管道来进程通信。