为了进程间可以通信,操作系统提供两个进程可以同时访问的内存空间。
假设有一个变量bread = 1,A进程用掉后就是bread = 0,B进程再对这个bread访问的时候就是0,这样B进程就知道A进程用掉了bread,换而言之就是知道了对方的状态。 那么怎么才能获得一个通信的空间?
管道不属于进程资源,和套接字一样属于操作系统资源。
#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,但是父进程只用输出路径,子进程只用输入路径
可以看出通过一个管道完全可以双向通信
#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;
}
#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);
}
仅仅想用进程和管道构建复杂功能的服务端,需要很高的水平,初学者并非易事,但是后面还有两种更强大的模型。也许有人觉得那前面讲的内容那还算什么?没啥用啊。并不是,前面是对基础的铺垫,为了更好地掌握更高阶的内容。
本文章使用limfx的vscode插件快速发布