进程间通信
1.管道
1.方法
#include <unistd.h>
int pipe(int pipefd[2]);
管道是半双工的,只能在具有公共祖先的进程间使用。
pipefd[2]:传入大小为2的int数组,返回两个文件描述符,pipefd[0]为读端,pipefd[1]为写端。
2.实例
1.创建一个管道,用于父子进程间数据传输。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <string.h>
#define BUF_SIZE 20
int main(int argc, char const *argv[])
{
int pipefd[2];
pid_t pid;
int err;
char buf[BUF_SIZE];
err = pipe(pipefd);
if(err < 0){
perror("pipe()");
exit(1);
}
pid = fork();
if(pid < 0){
perror("fork");
exit(1);
}
if(pid > 0){
close(pipefd[0]);
write(pipefd[1], "Hello\n", 7);
waitpid(pid, NULL, 0);
}else{
close(pipefd[1]);
read(pipefd[0], buf, BUF_SIZE);
puts(buf);
}
exit(0);
}
2.使用管道实现父子进程间同步,如顺序输出abababab。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <wait.h>
#include <string.h>
static int fd_c[2], fd_p[2];
static int tell_wait()
{
if (pipe(fd_c) < 0 || pipe(fd_p) < 0)
return -1;
return 0;
}
static void wait_child()
{
char c;
if (read(fd_c[0], &c, 1) != 1)
{
perror("read fd_c");
exit(1);
}
if (c != 'c')
{
fprintf(stderr, "wait child: incorrect data.\n");
}
}
static void tell_parent()
{
if (write(fd_c[1], "c", 1) != 1)
{
perror("write fd_c");
exit(1);
}
}
static void wait_parent()
{
char c;
if (read(fd_p[0], &c, 1) != 1)
{
perror("read fd_p");
exit(1);
}
if (c != 'p')
{
fprintf(stderr, "wait parent: incorrect data.\n");
}
}
static void tell_child()
{
if (write(fd_p[1], "p", 1) != 1)
{
perror("write fd_p");
exit(1);
}
}
int main(int argc, char const *argv[])
{
int err;
pid_t pid;
err = tell_wait();
int i = 0;
if (err < 0)
{
fprintf(stderr, "tell_wait()\n");
exit(err);
}
pid = fork();
if (pid < 0)
{
perror("fork()");
exit(1);
}
if (pid > 0)
{
while (i < 10)
{
putchar('a');
fflush(stdout);
sleep(1);
tell_child();
wait_child();
i++;
}
wait(NULL);
puts("");
}
else
{
while (i < 10)
{
wait_parent();
putchar('b');
fflush(stdout);
sleep(1);
tell_parent();
i++;
}
}
return 0;
}
运行结果:
shbj@ubuntu:~/develop/workspace/c/ipc$ ./pipe2
abababababababababab
2.FIFO
通FIFO文件,不相关的进程也能交换数据。
1.方法
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname : 指定FIFO文件的创建路径。
mode : FIFO文件权限。
2.实例
1.通过FIFO,将实现文件的复制,并将复制文件中的小写字母转换为大写字母。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <wait.h>
#define PATH "/tmp/fifo1"
#define BUF_SIZE 1024
static int dest_fd1, sou_fd, fifo_fd;
static int i;
static char buf[BUF_SIZE];
static void write_fun(const char *path)
{
char c;
fifo_fd = open(PATH, O_WRONLY);
int rang;
if (fifo_fd < 0)
{
perror("open fifo");
exit(1);
}
sou_fd = open(path, O_RDONLY);
if (sou_fd < 0)
{
perror("open source");
exit(1);
}
rang = 'z' - 'Z';
while ((i = read(sou_fd, &c, 1)) > 0)
{
if (c < 'z' && c > 'a')
c -= rang;
write(fifo_fd, &c, i);
}
close(fifo_fd);
close(sou_fd);
}
static void read_fun(const char *path)
{
fifo_fd = open(PATH, O_RDONLY);
if (fifo_fd < 0)
{
perror("open fifo");
exit(1);
}
dest_fd1 = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0660);
if (dest_fd1 < 0)
{
perror("open dest");
exit(1);
}
while ((i = read(fifo_fd, buf, BUF_SIZE)) > 0)
{
write(dest_fd1, buf, i);
}
close(fifo_fd);
close(dest_fd1);
}
int main(int argc, char const *argv[])
{
pid_t pid;
int err, i;
if (access(PATH, F_OK) < 0)
{
err = mkfifo(PATH, 0660);
if (err < 0)
{
perror("mkfifo()");
exit(1);
}
}
for (i = 0; i < 2; i++)
{
if ((pid = fork()) < 0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
if (i == 0){
sleep(2);
write_fun(argv[1]);
exit(0);
}
else if (i == 1){
sleep(2);
read_fun(argv[2]);
exit(0);
}
}
}
return 0;
}
3.消息队列
1.方法
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
path:文件路径。
proj_id:相当于一个hash的混淆值,一般传入一个字符。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key:ftok返回的key;如果是有亲缘关系进程间可以使用IPC_PRIVATE。
msgflg:接收端,使用和文件一样的权限位(如0660),如果消息队列以前不存在需要或上IPC_CREATE(0660 |IPC_CREATE);发送端,一般设为0就好。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
msgp:传输数据的指针。
msgsz:数据的大小,需要减去mtype的大小,sizeof(buf) - sizeof(long);
msgflg:
0:接收端,msgrcv将会阻塞,直到队列中有msgtyp对应的消息;
? 发送端,当消息队列满时,msgsnd将会阻塞,直到有消息被取走。
IPC_NOWAIT:接收端,没有对应消息,msgrcv不等待,立即返回;
? 发送端,当消息队列满时,msgsnd不等待,立即返回。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
cmd:IPC_STAT、IPC_SET、IPC_RMID。
buf:如果不关心,可以传NULL。
2.实例
1.两个不相关进程间数据传输。
#ifndef MSG1_H__
#define MSG1_H__
#define PATH "/etc/services"
#define PROJ_ID 'a'
struct stu_st
{
char name[20];
int math;
int chinese;
};
struct msg_stu
{
long mtype;
struct stu_st st;
};
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#include "msg1.h"
int main(int argc, char const *argv[])
{
key_t key;
int id;
int err;
struct msg_stu *stp;
key = ftok(PATH, PROJ_ID);
if (key < 0)
{
perror("ftok()");
exit(1);
}
id = msgget(key, 0660 | IPC_CREAT);
if (id < 0)
{
perror("msgget()");
exit(1);
}
stp = malloc(sizeof(*stp));
while((err = msgrcv(id, stp, sizeof(*stp) - sizeof(long), 1, 0)) < 0){
if(err != EINTR){
perror("msgrcv()");
msgctl(id, IPC_RMID, NULL);
exit(1);
}
}
msgctl(id, IPC_RMID, NULL);
printf("name: %s, chinese : %d, math : %d\n", \
stp->st.name, stp->st.chinese, stp->st.math);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <errno.h>
#include "msg1.h"
int main(int argc, char const *argv[])
{
key_t key;
int id;
int err;
struct msg_stu *stp;
key = ftok(PATH, PROJ_ID);
if (key < 0)
{
perror("ftok()");
exit(1);
}
id = msgget(key, 0);
if (id < 0)
{
perror("msgget()");
exit(1);
}
stp = malloc(sizeof(*stp));
stp->mtype = 1;
strcpy(stp->st.name, "zhangsan");
stp->st.math = 90;
stp->st.chinese = 80;
while ((err = msgsnd(id, stp, sizeof(*stp) - sizeof(long), 0)) < 0)
{
if (err != EINTR)
{
perror("msgrcv()");
exit(1);
}
}
return 0;
}
4.信号量
1.方法
1.获取信号量列表。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
nsems:信号量列表中信号量的个数。
semflg:相当于文件的权限位;亲缘关系的进程(如父子进程)key可为IPC_PRIVATE,semflg可以不或上IPC_CREATE,其余需要,如0660|IPC_CREATE。
2.控制信号量列表。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
semid:信号量id。
semnum:哪一个信号量,信号量下标。
cmd:命令,IPC_STAT、IPC_SET、IPC_RMID、GETALL、GETVAL、SETALL、SETVAL。
返回值:根据命令的不同返回值不同。
3.操作信号量列表。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
sops:有如下几个成员
? sem_num,信号量编号; ? sem_op,操作,正数+,负数-; ? sem_flg,选项;
nsops:sops的大小。
2.实例
1.多进程间的顺序累加。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <fcntl.h>
#include <wait.h>
#include <errno.h>
#define BUFFSIZE 10
#define PROCNUM 20
static int sem_id;
static void P()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = 0;
while (semop(sem_id, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop()");
exit(1);
}
}
static void V()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = 0;
while (semop(sem_id, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop()");
exit(1);
}
}
int main(int argc, char const *argv[])
{
FILE *fp;
char buf[BUFFSIZE];
int num;
int i;
pid_t pid;
sem_id = semget(IPC_PRIVATE, 1, 0660);
if (sem_id < 0)
{
perror("semget()");
exit(1);
}
if (semctl(sem_id, 0, SETVAL, 1) < 0)
{
perror("semctl()");
exit(1);
}
for (i = 0; i < PROCNUM; i++)
{
pid = fork();
if (pid < 0)
{
perror("fork()");
exit(1);
}
if (pid == 0)
{
fp = fopen("/tmp/out", "r+");
if (fp == NULL)
{
perror("fopen()");
exit(1);
}
P();
fgets(buf, BUFFSIZE, fp);
fseek(fp, 0, SEEK_SET);
num = atoi(buf);
fprintf(fp, "%d\n", ++num);
fflush(fp);
sleep(1);
V();
fclose(fp);
exit(0);
}
}
for (i = 0; i < PROCNUM; i++)
wait(NULL);
semctl(sem_id, 0, IPC_RMID);
return 0;
}
执行结果:
hbj@ubuntu:~/develop/workspace/c/ipc/sem$ echo 10 > /tmp/out
shbj@ubuntu:~/develop/workspace/c/ipc/sem$ ./reciver
shbj@ubuntu:~/develop/workspace/c/ipc/sem$ cat /tmp/out
30
shbj@ubuntu:~/develop/workspace/c/ipc/sem$
5.共享存储
共享存储允许两个或多个进程共享一个给定的存储区。因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。
共享存储与内存映射的区别,共享存储没有相关的文件,共享存储段是内存的匿名段。
1.方法
1.创建共享存储。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
size:存储区大小。
shmflg:权限位。
返回值:成功返回共享存储id,失败-1。
2.操作共享存储。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd:操作命令;IPC_STAT、IPC_SET、IPC_RMID
buf:cmd为IPC_STAT,将对应共享存储的shmid_ds结构,存储在buf指向的结构中;
? cmd为IPC_SET,按照buf指向结构中的值,设置共享存储对应的shmid_ds结构。
3.共享存储连接到调用进程的哪个地址上。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
shmid:共享存储的id。
shmaddr:指定的地址,一般设为0,由系统自动分配。
shmflg:一般也设为0。
进程中,当共享存储段的操作结束时,调用shmdt与该段分离。注意,调用shmdt并不删除共享存储,只有当某个进程(一般是服务进程)调用了带IPC_RMID命令的shm_ctl特地删除它为止。
2.实例
1.使用共享存储实现不同进程间的数据传输。
#ifndef PROTO_H__
#define PROTO_H__
#define PATH "/etc/services"
#define PROJ_ID 'a'
struct stu_st
{
char name[20];
int math;
int chinese;
};
struct msg_stu
{
long mtype;
struct stu_st st;
};
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "proto.h"
#define PATH "/etc/services"
#define SHM_SIZE 1024
int main(int argc, char const *argv[])
{
key_t key;
int shmid, err;
struct stu_st *stp;
key = ftok(PATH, 'a');
if (key < 0)
{
perror("ftok()");
exit(1);
}
shmid = shmget(key, SHM_SIZE, 0660 | IPC_CREAT);
if (shmid < 0)
{
perror("shmget()");
exit(1);
}
stp = shmat(shmid, 0, 0);
if (stp < 0)
{
perror("shmat()");
exit(1);
}
sleep(10);
printf("%s, %d, %d\n", stp->name, stp->chinese, stp->math);
strcpy(stp->name, "lisi");
sleep(10);
err = shmdt(stp);
if (err < 0)
{
perror("shmdt()");
exit(1);
}
err = shmctl(shmid, IPC_RMID, NULL);
if (err < 0)
{
perror("shmctl()");
exit(1);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "proto.h"
#define PATH "/etc/services"
#define SHM_SIZE 1024
int main(int argc, char const *argv[])
{
key_t key;
int shmid, err;
struct stu_st *stp;
key = ftok(PATH, 'a');
shmid = shmget(key, SHM_SIZE, 0660);
stp = (struct stu_st *)shmat(shmid, 0, 0);
strcpy(stp->name, "zhangsan");
stp->chinese = 90;
stp->math = 95;
sleep(10);
printf("%s, %d, %d\n", stp->name, stp->chinese, stp->math);
err = shmdt(stp);
return 0;
}
2.改进1程序,实现两个进程间的数据同步,即同时只能有一个进程操作共享存储区。
采用信号量实现同步。
#ifndef PROTO_H__
#define PROTO_H__
#define PATH "/etc/services"
#define PROJ_ID 'a'
struct stu_st
{
char name[20];
int math;
int chinese;
};
struct msg_stu
{
long mtype;
struct stu_st st;
};
void sem_init();
void sem_destroy();
void P1();
void V1();
void P2();
void V2();
#endif
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include "proto.h"
static int semid;
void sem_init()
{
key_t key;
key = ftok(PATH, PROJ_ID);
if (key < 0)
{
perror("ftok()");
exit(1);
}
if ((semid = semget(key, 2, 0660)) < 0)
{
semid = semget(key, 2, 0660 | IPC_CREAT);
}
if (semid < 0)
{
perror("semget()");
exit(1);
}
if (semctl(semid, 0, SETVAL, 0) < 0)
{
perror("semctl()");
exit(1);
}
if (semctl(semid, 1, SETVAL, 0) < 0)
{
perror("semctl()");
exit(1);
}
}
void sem_destroy(){
semctl(semid, 0, IPC_RMID);
}
void P1()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
while (semop(semid, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop_p1()");
exit(1);
}
}
void V1()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
while (semop(semid, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop_v1()");
exit(1);
}
}
void P2()
{
struct sembuf buf;
buf.sem_num = 1;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
while (semop(semid, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop_p2()");
exit(1);
}
}
void V2()
{
struct sembuf buf;
buf.sem_num = 1;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
while (semop(semid, &buf, 1) < 0)
{
if (errno == EINTR || errno == EAGAIN)
continue;
perror("semop_v2()");
exit(1);
}
}
接收端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <errno.h>
#include "proto.h"
#define SHM_SIZE 1024
int main(int argc, char const *argv[])
{
key_t key;
int shmid, err;
struct stu_st *stp;
key = ftok(PATH, 'a');
if (key < 0)
{
perror("ftok()");
exit(1);
}
shmid = shmget(key, SHM_SIZE, 0660 | IPC_CREAT);
if (shmid < 0)
{
perror("shmget()");
exit(1);
}
sem_init();
stp = shmat(shmid, 0, 0);
if (stp < 0)
{
perror("shmat()");
exit(1);
}
P1();
printf("%s, %d, %d\n", stp->name, stp->chinese, stp->math);
strcpy(stp->name, "lisi");
sleep(5);
V2();
err = shmdt(stp);
if (err < 0)
{
perror("shmdt()");
exit(1);
}
P1();
err = shmctl(shmid, IPC_RMID, NULL);
if (err < 0)
{
perror("shmctl()");
exit(1);
}
sem_destroy();
return 0;
}
发送端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include "proto.h"
#define SHM_SIZE 1024
int main(int argc, char const *argv[])
{
key_t key;
int shmid, err;
struct stu_st *stp;
key = ftok(PATH, 'a');
shmid = shmget(key, SHM_SIZE, 0660);
stp = (struct stu_st *)shmat(shmid, 0, 0);
sem_init();
strcpy(stp->name, "zhangsan");
stp->chinese = 90;
stp->math = 95;
sleep(5);
V1();
P2();
printf("%s, %d, %d\n", stp->name, stp->chinese, stp->math);
err = shmdt(stp);
V1();
return 0;
}
|