第11章 进程间的通信

11.1 进程间通信的基本概念

为了进程间可以通信,操作系统提供两个进程可以同时访问的内存空间。

1.对进程间通信的基本理解

假设有一个变量bread = 1,A进程用掉后就是bread = 0,B进程再对这个bread访问的时候就是0,这样B进程就知道A进程用掉了bread,换而言之就是知道了对方的状态。 那么怎么才能获得一个通信的空间?

2.通过管道实现进程通信

管道不属于进程资源,和套接字一样属于操作系统资源。

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

以两个元素的int数组的地址值作为参数调用上述函数后,数组中存在两个文件描述符,他们分别作为管道出口和入口。父进程调用完之后,会获得这两个文件描述符。如果是父子进程想要进行数据交换,只需把出口或入口文件描述符给子进程。

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

父子进程都可以直接访问管道的I/O,但是父进程只用输出路径,子进程只用输入路径

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

可以看出通过一个管道完全可以双向通信

#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);//休眠2秒确保父进程能够先读完,防止自己的read读了信息,父进程read进入无限等待。
		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);//父进程终止会弹出命令提示符,所以要sleep3秒确保子进程可以在父进程弹出终止命令提示符前输出内容。
	}
	return 0;
}

可以看出用一个管道进行通信,会考虑许多细节,程序越复杂需要考虑的细节越多,而且程序不可能预测和控制运行流程,所以用一个管道通信几乎不可能,所以双向通信一般用两个管道。

#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));//子进程可以用fds1[1]写
		read(fds2[0], buf, BUF_SIZE);//子进程可以用fds2[0]读
		printf("Child proc output: %s \n",  buf);
	}
	else
	{
		read(fds1[0], buf, BUF_SIZE);//父进程可以用fds[1]读
		printf("Parent proc output: %s \n", buf);
		write(fds2[1], str2, sizeof(str2));//父进程可以用fds[2]写
		sleep(3);
	}
	return 0;
}

11.2运用进程间通信

1.保存消息的回声服务端

#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 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]);
		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(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

2.想要构建更大型的程序

仅仅想用进程和管道构建复杂功能的服务端,需要很高的水平,初学者并非易事,但是后面还有两种更强大的模型。也许有人觉得那前面讲的内容那还算什么?没啥用啊。并不是,前面是对基础的铺垫,为了更好地掌握更高阶的内容。

即使开始时只想学习必要部分,最后也会需要掌握所有内容


本文章使用limfx的vscode插件快速发布