进程间通信是指在不同进程之间传播或交换信息。
IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams等。其中Socket和Streams支持不同主机上的两个进程IPC。
管道,通常指无名管道,是UNIX系统IPC最古老的形式。
#include
int pipe(int pipefd[2]);
pipefd[2] 用于存放读和写的文件描述符 大小为2的数组
返回值:成功返回 0 失败返回-1 ,errno被设置
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开(巧记:我们说话时说01和读写,0对应读,1对应写)。如下图:
思路:
#include
#include
#include
#include
int main()
{
int pid;
int fd[2];
char r_buf[128] = {0};
if ((pipe(fd)) == -1) { //创建管道
perror("pipe fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
}
if (pid > 0) { //父进程
close(fd[0]); //关闭读端
write(fd[1], "hallo world!", strlen("hallo world!")); //往管道里写
close(fd[1]); //关闭管道
} else if (pid == 0) {
close(fd[1]); //关闭写端
read(fd[0], r_buf, 128); //读取管道中的数据
printf("%s\n", r_buf); //打印数据
close(fd[0]); //关闭管道
}
return 0;
}
运行结果:
FIFO,也称为命名管道,它是一种文件类型。
#include
#include
int mkfifo(const char *pathname, mode_t mode);
pathname 创建一个名字为"pathname"的FIFO特殊文件
mode 文件权限
返回值:成功返回 0 失败返回 -1 ,errno被设置
其中的mode参数与open函数中的mode相同。一旦创建了一个FIFO,就可以用一般的文件I/O函数操作它。当open一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:
FIFO的通信方式类似于在进程中使用文件来传输数据,只不过FIFO类型文件同时具有管道的特性。在数据读出时,FIFO管道中同时清除数据,并且“先进先出”。下面的例子演示了使用FIFO进行IPC的过程。
思路:
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd;
int pid;
int sum = 3;
char r_buf[128] = {0};
if (((mkfifo("file", 0600)) == -1) && (errno != EEXIST)) { //创建FIFO,如果FIFO存在条件为假
perror("mkfifo fail");
exit(-1);
}
if ((pid = fork()) == -1) { //创建进程
perror("fork fail");
exit(-1);
} else if (pid > 0) { //父进程
fd = open("file", O_WRONLY); //打开FIFO
while(sum) //间隔一秒往FIFO里写入数据
{
write(fd, "message form fifo", strlen("message from fifo"));
sleep(1);
sum--;
}
close(fd); //关闭FIFO
} else if (pid == 0) { //子进程
fd = open("file", O_RDONLY); //打开FIFO
while (sum) //间隔一秒读取一次FIFO里的数据并打印
{
read(fd, r_buf, 128);
puts(r_buf);
sleep(1);
sum--;
}
close(fd); //关闭FIFO
}
return 0;
}
运行结果:
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
//头文件
#include
#include
#include
int msgget(key_t key, int msgflg);
//创建或打开队列,成功返回队列ID,失败返回-1,errno被设置
int msgsnd(int msqid, const void *msgp, size_t msgsz, int flag);
//写入数据,成功返回0,失败返回-1, errno被设置
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int flag);
//读取数据,成功返回数据的字节数,失败返回-1, errno被设置
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//根据cmd(删除队列、设置数据元素、读取数据结构)命令执行操作。失败返回-1,errno被设置
key | 由ftok()生成,key为队列ID的组成部分 |
msgflg | 标志位(IPC_CREAT、IPC_EXCL、 IPC_NOWAIT)还应加上队列权限(读、写、执行) |
msqid | 队列ID |
msgp | 消息缓冲区指针(结构体指针) |
flag | 为0表示阻塞方式(队列空间不够就会堵塞),设置IPC_NOWAIT 表示非阻塞方式 |
msgsz | 消息数据长度,不含数据类型长度4个字节 |
msgtyp | 数据类型 type == 0 返回队列中的第一个消息。type > 0 返回队列中消息类型为type的第一个消息。type < 0返回队列中消息类型值小于或等于type绝对值,而且在这种消息中,其类型值又最小的消息 |
cmd | 控制命令(IPC_STAT(读取数据结构)、IPC_SET(设置数据元素)、IPC_RMID(删除队列)) |
buf | 读取和设置操作的缓冲区指针,不使用设置为NULL |
思路:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf {
long mtype;
char mtext[128];
};
int main()
{
int key;
int pid;
int msgid;
struct msgbuf wbuf;
struct msgbuf rbuf;
pid = fork(); //创建进程
if (pid == -1) {
perror("fork fail");
exit(1);
}
key = ftok(".", 1); //创建key
if (key == -1) {
perror("ftok fail");
exit(2);
}
if (pid > 0) { //父进程
msgid = msgget(key, IPC_CREAT | 0777); //创建消息队列
if (msgid == -1) {
perror("msgget fail");
exit(3);
}
wbuf.mtype = 1; //创建数据
memset(wbuf.mtext, '0', sizeof(wbuf.mtext));
strcpy(wbuf.mtext, "message from message queuing");
msgsnd(msgid, &wbuf, sizeof(wbuf) - sizeof(long), 0); //发送数据,空间不够就会堵塞
} else if (pid == 0) { //子进程
msgid = msgget(key, IPC_CREAT | 0777); //打开消息队列
if (msgid == -1) {
perror("msgget fail");
exit(4);
}
msgrcv(msgid, &rbuf, sizeof(rbuf) - sizeof(long), 1, 0); //读取数据,无数据就会堵塞
printf("type = %ld msg: %s\n", rbuf.mtype, rbuf.mtext);
msgctl(msgid, IPC_RMID, NULL); //删除消息队列
}
return 0;
}
运行结果:
#include
#include
//创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
//连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
//断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(const void *addr);
//控制共享内存的相关消息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
key | 由ftok()生成的key标识,标识系统的唯一IPC资源。 |
size | 需要申请共享内存的大小。在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。 |
只获取共享内存时指定为0 | |
flag | 如果是已经存在的,可以使用IPC_CREAT或直接传0。 |
如果要创建新的共享内存,需要使用IPC_CREAT,IPC_EXCL。 | |
若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段。 | |
shm_id | 共享存储段的标识符。 |
*addr | addr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用),连接以后返回的地址。 |
cmd | IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中。 |
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 | |
IPC_RMID:删除这片共享内存 | |
*buf | 设置为NULL即可。 |
思路:
#include
#include
#include
#include
#include
#include
#include
int main()
{
int pid;
int key;
int shmid;
char *shmaddr;
pid = fork(); //创建进程
if (pid == -1) {
perror("fork fail");
exit(-1);
}
key = ftok(".", 2); //获取key
if (key == -1) {
perror("ftok fail");
exit(-1);
}
if (pid > 0) { //父进程
shmid = shmget(key, 1024*4, IPC_CREAT | 0666); //创建共享内存
if (shmid == -1) {
perror("shmget fail");
exit(-1);
}
shmaddr = shmat(shmid, NULL, 0); //挂载内存到进程空间中
strcpy(shmaddr, "message from share memory"); //写入数据
shmdt(shmaddr); //卸载共享内存
} else if (pid == 0) { //子进程
shmid = shmget(key, 0, IPC_CREAT); //获取共享内存ID
if (shmid == -1) {
perror("shmget fail");
exit(-1);
}
shmaddr = shmat(shmid, NULL, 0); //挂载内存到进程空间中
printf("%s\n", shmaddr); //打印共享内存中的数据
shmdt(shmaddr); //卸载共享内存
shmctl(shmid, IPC_RMID, NULL); //删除共享内存
}
return 0;
}
运行结果:
对于Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为Linux提供了一种处理异步事件的方法。比如,终端用户输入了ctrl+c来中断程序,会通过信号机制停止一个程序。
信号的名字和编号:每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO”、“SIGCHLD”等等。信号定义在 signal.h 头文件中,信号名都定义为正整数。具体的信号名称可以使用 kill -l 来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0有特殊的应用。
CLC@Embed_Learn:~/IPC$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
信号的处理方式有三种方法,分别是:忽略、捕捉和默认动作。
其实对于常用的 kill 命令就是一个发送信号的工具, kill 9 PID 来杀死进程。比如,我在后台运行了一个 a.out 的程序,通过ps命令可以查看它的 PID ,通过 kill 9 来发送了一个终止的信号来结束了 a.out 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL ,正是杀死该进程的信号。而以下执行过程实际也就是执行了9号的默认动作——杀死进程。
对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通信的手段。
信号处理函数的注册不只一种方法,分为入门版和高级版
信号发送函数也不止一种,同样分为入门版和高级版
signal函数
#include
typedef void (*sighandler_t)(int);//void型函数,里面一个int型的信号参数
sighandler_t signal(int signum, sighandler_t handler);
/*
根据函数原型可以看出由两部分组成,一个是真实处理信号的函数,另一个是注册函数了。
对于 sighandler_t signal(int signum, sighandler_t handler) 函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
同样,typedef void (*sighandler_t)(int) 中断函数的原型中,有一个参数是 int 类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
*/
#include
#include
int kill(pid_t pid, int sig);
//pid:要发送信号的进程pid号。 sig:发送的信号。
/*
pid > 0:将发送该 pid 的进程。
pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值,并且发送进程具有权限向其发送信号的所有进程。
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)。
*/
demo5.c
#include
#include
void handler(int signum)
{
printf("get signum = %d\n", signum);
switch(signum)
{
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
}
printf("never quit!\n");
}
int main()
{
signal(SIGINT, handler);
signal(SIGKILL, handler);
signal(SIGUSR1, SIG_IGN);
while(1);
return 0;
}
demo6.c
#include
#include
#include
#include
int main(int argc, char **argv)
{
int signum;
int pid;
char cmd[20];
if (argc != 3) {
printf("sum false!\n");
exit(-1);
}
//方式一
pid = atoi(argv[2]);
signum = atoi(argv[1]);
kill(pid, signum);
/*方式二
sprintf(cmd, "kill -%s %s", argv[1], argv[2]);
system(cmd);
*/
return 0;
}
运行结果:
简单的总结一下,我们通过 signal 函数注册一个信号处理函数,分别注册了三个信号(SIGINT,SIGKILL和 SIGUSER1),随后主程序就一直“长眠”了。通过 kill 命令发送信号之前,我们需要先查看到接收者,通过 ps 命令查看了之前所写的程序的 PID,通过 kill 函数来发送。
对于已注册的信号,使用 kill 发送都可以正常接收到,并执行对应的命令。
在此还有两个问题需要说明一下:
sigaction函数
#include
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
struct sigaction {
void (*sa_handler)(int); //信号处理不接受数据,SIG_IGN 为忽略,SIG_DFL 为默认动作
void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理接受数据和sigqueue配合使用,void *为空无数据,不为空有数据
sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先设置。
int sa_flags;//设置为 SA_SIGINFO 表示接受数据
};
//回调函数句柄sa_handler、sa_sigaction只能任选其一
siginfo_t {
int si_signo; /* 信号的编号 */
int si_errno; /* 一个errno值 */
int si_code; /* 信号码 */
int si_trapno; /* 导致硬件生成信号(在大多数体系结构上未使用)*/
pid_t si_pid; /* 发送进程ID */
uid_t si_uid; /* 发送进程的实际用户ID */
int si_status; /* 退出值或信号 */
clock_t si_utime; /* 消耗的用户时间 */
clock_t si_stime; /* 消耗的系统时间 */
sigval_t si_value; /* 存放信号携带的信息,si_value为共用体与发送信号时的value共用体一致 */
int si_int; /* POSIX.1b信号 */
void *si_ptr; /* POSIX.1b信号 */
int si_overrun; /* 计时器溢出计数;POSIX.1b计时器*/
int si_timerid; /* 计时器ID;POSIX.1b计时器 */
void *si_addr; /* 导致故障的内存位置*/
long si_band; /* band事件(was int in)glibc 2.3.2及更早版本)*/
int si_fd; /* 文件描述符 */
short si_addr_lsb; /* 地址的最低有效位(从内核2.6.32开始*/
}
sigaction 是一个系统调用,根据这个函数原型,我们不难看出,在函数原型中,第一个参数 signum是注册信号的编号;第二个参数 *act 如果不为空说明需要对该信号有新的配置;第三个参数 *oldact 如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
sigqueue函数
#include
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
int sival_int;
void *sival_ptr;
};
demo思路:
receive.c
#include
#include
#include
#include
#include
void handler(int signum, siginfo_t *info, void *content)
{
int shmid;
int key;
char *shmaddr;
//创建共享内存
key = ftok(".", 10);
shmid = shmget(key, 0, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
printf("get signum = %d\n", signum);
//打印信号携带的信息
if (content != NULL) { //判断信号是否有携带信息
printf("from pid = %d\n", info->si_pid);
printf("get message character string = %s int data = %d\n",shmaddr, info->si_value.sival_int);
}
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, NULL);
}
int main()
{
struct sigaction act;
struct sigaction oldact;
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
printf("my pid = %d\n", getpid());
sigaction(10, &act, &oldact);
while(1);
return 0;
}
send.c
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
int pid;
int key;
int shmid;
int signum;
char *shmaddr;
union sigval value;
if (argc != 3) {
printf("sum false!\n");
exit(-1);
}
//创建共享内存
key = ftok(".", 10);
shmid = shmget(key, 1024, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
strcpy(shmaddr, "hallo");
shmdt(shmaddr);
//发送信号
pid = atoi(argv[2]);
signum = atoi(argv[1]);
printf("my pid = %d\n", getpid());
value.sival_int = 100;
sigqueue(pid, signum, value);
return 0;
}
运行结果:
信号量与IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥同步,而不是用于存储进程间通信数据。
信号量(semaphore)的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。信号量的值与相应资源的使用情况有关。
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。
注意,信号量的值仅能由PV操作来改变。
Linux下信号量函数都是在通用信号量数组上进行操作的,不是一个单一的二值信号量进行操作。
#include
#include
#include
创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
/*
当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1;
如果是引用一个现有的集合,则将num_sems指定为0。
*/
对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
/*
semid:信号量的id
numops:配置信号量个数,如果是1个,做一个struct sembuf semoparray[]
struct sembuf
{
short sem_num; // 信号量组中对应的序号,0~sem_nums-1
short sem_op; // 信号量值在一次操作中的改变量
short sem_flg; // IPC_NOWAIT, SEM_UNDO
};
如果信号量集有两个或以上,需要做数组,man 手册例子:
struct sembuf sops[2];
int semid;
sops[0].sem_num = 0; //Operate on semaphore 0
sops[0].sem_op = 0; //Wait for value to equal 0
sops[0].sem_flg = 0;
sops[1].sem_num = 0; // Operate on semaphore 0
sops[1].sem_op = 1; // Increment value by one
sops[1].sem_flg = 0;
if (semop(semid, sops, 2) == -1) {
perror("semop");
exit(EXIT_FAILURE);
}
*/
控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);
/*
在semctl函数中的命令(cmd)有多种,这里就说两个常用的:
1、SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
2、IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。
联合semun需要用户自行定义,通常只使用第一个元素
union semun {
int val; // Value for SETVAL (锁的个数)
struct semid_ds *buf; // Buffer for IPC_STAT, IPC_SET
unsigned short *array; // Array for GETALL, SETALL
struct seminfo *__buf; // Buffer for IPC_INFO (Linux-specific)
};
*/
功能说明:使用信号量配合共享内存、消息队列进行进程间通信。服务端根据用户的输入进行发送操作或者退出操作。
服务端:
接收端:
服务端:
#include
#include
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtext[128];
};
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void seminit(int semid, int num)
{
union semun sem;
sem.val = 1;
semctl(semid, num, SETVAL, sem);
}
void sem_p(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void sem_v(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = 1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
int main()
{
int key;
int shmid,msgid,semid;
char *shmaddr;
char *buf;
struct msgbuf r_buf;
key = ftok(".", 10);
msgid = msgget(key, IPC_CREAT | 0777);
shmid = shmget(key, 1024, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
semid = semget(key, 1, IPC_CREAT | 0666);
seminit(semid, 0);
while(1)
{
msgrcv(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 666, 0); //读取队列里的数据
if (strcmp(r_buf.mtext, "r") == 0) {
sem_p(semid, 0);
printf("%s\n", shmaddr); //读取共享内存中的内容
sem_v(semid, 0);
} else if (strcmp(r_buf.mtext, "q") == 0) {
shmdt(shmaddr);
union semun un;
shmctl(shmid, IPC_RMID, NULL);
msgctl(msgid, IPC_RMID, NULL);
semctl(semid, 0, IPC_RMID, un);
exit(-1);
break;
}
}
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
struct msgbuf
{
long mtype;
char mtext[128];
};
union semun{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
void seminit(int semid, int num)
{
union semun sem;
sem.val = 1;
semctl(semid, num, SETVAL, sem);
}
void sem_p(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = -1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void sem_v(int semid, int num)
{
struct sembuf sbuf;
sbuf.sem_num = num;
sbuf.sem_op = 1;
sbuf.sem_flg = SEM_UNDO;
semop(semid, &sbuf, 1);
}
void welcome()
{
printf("----------------------------------------\n");
printf("--------------welcome to ipc------------\n\n");
printf("------------input r send datas----------\n");
printf("---------------input q quit------------\n\n");
printf("----------------------------------------\n");
}
int main()
{
int key;
int shmid,msgid,semid;
char *shmaddr;
struct msgbuf r_buf;
key = ftok(".", 10);
msgid = msgget(key, IPC_CREAT | 0777);
shmid = shmget(key, 0, IPC_CREAT | 0666);
shmaddr = shmat(shmid, NULL, 0);
semid = semget(key, 1, IPC_CREAT | 0666);
welcome();
while(1)
{
printf("please input your operation\n");
memset(r_buf.mtext, '0', sizeof(r_buf.mtext));
scanf("%s", r_buf.mtext); //往队列写入数据,用于判断是r还是q
if (strcmp(r_buf.mtext, "r") == 0) {
sem_p(semid, 0);
printf("plase input you want send datas\n");
scanf("%s", shmaddr); //往共享内存写入数据
r_buf.mtype = 666;
msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
sem_v(semid, 0);
} else if (strcmp(r_buf.mtext, "q") == 0) {
r_buf.mtype = 666;
msgsnd(msgid, &r_buf, sizeof(r_buf) - sizeof(long), 0);
shmdt(shmaddr);
break;
}
}
return 0;
}
运行结果:
客户端:
----------------------------------------
--------------welcome to ipc------------------------input r send datas----------
---------------input q quit----------------------------------------------------
please input your operation
服务端:
r客户端:
plase input you want send datas服务端:
hallo客户端:
hallo
please input your operation服务端:
q
1、无名管道:速度慢,容量有限,只有父子进程能通讯。
2、有名管道:任何进程间都能通讯,但速度慢。
3、消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题。
4、共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存。
5、信号: 传递字符串消息只能在同一程序下的进程间。
6、信号量:不能传递复杂消息,只能用来同步 ,通常配合消息队列、共享内存使用。