进程间通信意味着两个不同进程间可以交换数据,为了完成这一点,操作系统中应提供两个进程可以同时访问的内存空间。
只要有两个进程可以同时访问的内存空间,就可以通过此空间交换数据。
但是,进程具有完全独立的内存结构。就连通过fork函数创建的子进程也不会与父进程共享内存空间。此时,进程间通信只能通过其他特殊方法完成。
为了完成进程间通信,需要创建管道。
管道并非属于进程的资源,而是和套接字一样,属于操作系统(也就不是fork函数的复制对象),所以,两个进程通过操作系统提供的内存空间进行通信。
父进程调用该函数时将创建管道,同时获取对应于出入口的文件描述符,此时父进程可以读写同一管道。
但父进程的目的是与子进程进行数据交换,因此需要将入口或出口中的一个文件描述符利用fork函数传递给子进程。
fds[0]:通过管道接收数据时使用的文件描述符,即管道出口
fds[1]:通过管道传输数据时使用的文件描述符,即管道入口
#include
#include
#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数组中保存用于IO的文件描述符
pipe(fds);
// 调用fork函数
// 子进程将同时拥有通过pipe函数调用获取的2个文件描述符
// 注意:复制的并非管道,而是用于管道IO的文件描述符
// 至此,父子进程同时拥有IO文件描述符
pid = fork();
if(pid == 0) {
// 子进程通过write向管道传递字符串
// 传输数据,使用fds[1]
write(fds[1], str, sizeof(str));
} else {
// 父进程通过read从管道接收字符串
// 接收数据,使用fds[0]
read(fds[0], buf, BUF_SIZE);
puts(buf);
}
return 0;
}
#include
#include
#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传输数据
write(fds[1], str1, sizeof(str1));
sleep(2);
// 子进程通过read接收数据
read(fds[0], buf, BUF_SIZE);
printf("child proc output: %s \n", buf);
} else {
// 父进程通过read接收子进程通过write传输的数据
read(fds[0], buf, BUF_SIZE);
printf("parent proc output: %s \n", buf);
// 父进程通过write传输数据,这些数据被子进程用read接收
write(fds[1], str2, sizeof(str2));
sleep(3);
}
return 0;
}
运行结果
模型如图所示
如果注释掉子进程运行区域中的sleep(2),会发生错误,原因是:
向管道传递数据时,先读的进程会把数据取走。即数据进入管道后称为无主数据。
就是说,注释掉sleep以后,子进程调用read函数先读取数据,即使该进程将数据传到了管道。即子进程将读回自己向管道发送的数据。结果,父进程调用read函数后将无限期等待数据进入管道。
#include
#include
#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) {
// 子进程通过数组fds1指向的管道向父进程传输数据
write(fds1[1], str1, sizeof(str1));
// 子进程通过数组fds2指向的管道从父进程接收数据
read(fds2[0], buf, BUF_SIZE);
printf("child proc output: %s \n", buf);
} else {
// 父进程通过read接收子进程通过write传输的数据
read(fds1[0], buf, BUF_SIZE);
printf("parent proc output: %s \n", buf);
// 父进程通过write传输数据,这些数据被子进程用read接收
write(fds2[1], str2, sizeof(str2));
// 没啥意义,只是为了延迟父进程终止
sleep(3);
}
return 0;
}
在学了基于管道的进程间通信后,将其运用到网络代码中。
另行创建进程,从向客户端提供服务的进程读取字符串消息。当然,该过程中需要创建用于接收数据的管道。
#include
#include
#include
#include
#include
#include
#include
#include
#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;
struct sockaddr_in clnt_adr;
int fds[2];
pid_t pid;
struct sigaction act;
socklen_t adr_sz;
char buf[BUF_SIZE];
int str_len, state;
if(argc != 2) {
printf("Usage : %s \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);
if(serv_sock == -1) {
error_handling("socket error");
}
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) {
// 该区域从管道出口fds[0]读取数据并保存到文件中
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 {
printf("new client connected...\n");
}
// accept调用之后。父子进程分别带有1个accept处生成的套接字(受理客户端连接请求时创建的)文件描述符
// 通过fork函数创建的所有子进程将复制之前创建的管道的文件描述符
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 {
// 由于通过accept创建的套接字文件描述符已经复制给了子进程
// 因此,服务器端需要销毁自己拥有的文件描述符
close(clnt_sock);
}
}
close(serv_sock);
return 0;
}
void error_handling(char *message){
fputs(message, stderr);
fputs("\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);
}