进程间通信

来源:互联网 发布:如何看八字排盘 知乎 编辑:程序博客网 时间:2024/06/17 14:10

无名管道

无名管道用于在在亲缘关系的进程间通信(父子进程或兄弟进程),它是一种半双工的工作模式。

在父进程中创建无名管道后,又创建子进程,则父子进程中分别拥有独立的读端和写端。所以要把父进程的写端 fd[1]和子进程的读端 fd[0]关闭。这时,父子进程之间就建立起了一条“子进程写入父进程读”的通道。

数据保存在无名管道中,而无名管道在内核中。

无名管道、消息队列、信号量都位于内核,共享内在位于用户空间。 

#include<unistd.h>#include<string.h>#include<sys/types.h>#include<error.h>#include<stdio.h>#include<stdlib.h>int main(){    int pipe_fd[2];    pid_t pid;    char buff1[100];    int len;    memset(buff1,0x00,sizeof(buff1));    //创建管道    if(pipe(pipe_fd)<0){        perror("pipe");        exit(1);    }    //创建子进程。父进程中打开的文件、IPC对象等在子进程中也可以使用    if((pid=fork())==0){        //在子进程中        close(pipe_fd[1]);        //关闭写端        sleep(1);        //等待父进程写入数据        if((len=read(pipe_fd[0],buff1,sizeof(buff1)))>0)            printf("%d numbers read from the pipe is %s\n",len,buff1);        close(pipe_fd[0]);        //关闭读端        exit(0);    }    else if(pid>0){        //在父进程中        close(pipe_fd[0]);        //关闭读端        if((write(pipe_fd[1],"hello",5))!=-1)            printf("parent write success!\n");        if((write(pipe_fd[1],"pipe",4))!=-1)            printf("parent write success!\n");        close(pipe_fd[1]);    //关闭写端        sleep(1);        waitpid(pid,NULL,2);        exit(0);    }    //int main()函数可以没有返回值}

 输出:

parent write success!
parent write success!
9 numbers read from the pipe is hellopipe

#include <stdio.h>FILE *popen(const char *command, const char *type)

type--“r”:文件指针连接到 command 的标准输出,即该命令的结果产生输出;“w”:文件指针连接到 command 的标准输入,即该命令的结果产生输入

int pclose(FILE *stream)

用 popen 创建的管道必须使用标准 I/O 函数进行操作。比如fopen、fputs、fwrite等函数,它们都是带缓冲的。
与标准I/O库对应的原始文件编程模式,这是不带缓冲的(用户空间没有设置缓冲区),比如read、write、open等函数。
#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#define MAX 32700int main(){    FILE *f;    char buff[MAX];    const char *cmd = "ps -ef";    if ((f = popen(cmd, "r")) == NULL) {        perror("popen");        exit(1);    }    while (fgets(buff, MAX, f)) {        printf("%s", buff);    }    pclose(f);}

有名管道

有名管道依托于文件,严格遵从先进先出,不能使用lseek()等随机读写函数。

由于普通文件的读写时不会出现阻塞问题,而在管道的读写中却有阻塞的可能,这里的非阻塞标志可以在 open 函数中设定为 O_NONBLOCK。

当管道已满时,对于写进程

• 若该管道是阻塞打开,则写进程而言将一直阻塞直到有读进程读出数据。

• 若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行写操作。

#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

创建了有名管道pathname,就相当于创建了一个文件pathname。对有名管道的读写就使用读写普通文件的那几个函数就可以了。

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<fcntl.h>#include<sys/types.h>#include<sys/stat.h>#include<error.h>#define oops(m,x){    \    perror(m);    \    exit(x);    \}int main(){    char *file="/home/orisun/today";    if((mkfifo(file,0644))==-1)        //创建有名管道        oops("mkfifo",1);    int fd;    if((fd=open(file,O_RDWR|O_NONBLOCK,0644))==-1)        //打开有名管道        oops("open",1);    char *str="hust";    char buf[10]={0};    if((write(fd,str,strlen(str)))<0)            //向管道中写入数据        oops("write",1);    if((read(fd,buf,sizeof(buf)))<0)        //从管道中读取数据        oops("read",1);    printf("read:%s\n",buf);    unlink(file);            //删除有名管道}

共享内存

1.创建共享内存--shmget。即从内存中获得一片共享区域

2.映射共享内存--shmat。把共享内存映射到具体的进程空间中去

(此时就可以使用不带缓冲的I/O读写命令对其进行操作了)

3.撤销映射--shmdt

 

1.#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

int shmget(key_t key,int size,int shmflg)

Key:IPC_PRIVATE

Size:共享内存区大小

Shmflg:同 open 函数的权限位,也可以用八进制表示法

函数返回值

成功:共享内存段标识符

出错:−1

 

2.char *shmat(int shmid,const void *shmaddr,int shmflg)

shmid:要映射的共享内存区标识符

shmaddr:将共享内存映射到指定位置(若为 0 则表示把该段共享内存映射到调用进程的地址空间)

Shmflg:

SHM_RDONLY:共享内存只读

默认 0:共享内存可读写

函数返回值

成功:被映射的段地址

出错:−1

3.int shmdt(const void *shmaddr)
Shmaddr:被映射的共享内存段地址
函数返回值
成功:0
出错:−1
#include<sys/shm.h>#include<sys/ipc.h>#include<sys/types.h>#include<stdio.h>#include<stdlib.h>#define BUFSZ 2048int main(){    int shmid;    char *shmadd;    /*建立共享内存 */    if ((shmid = shmget(IPC_PRIVATE, BUFSZ, 0600)) == -1) {        perror("shmget");        exit(1);    } else {        printf("create shmid=%d\n", shmid);        system("ipcs -m");    //system()函数可以在程序中执行shell命令,"ipcs -m"表示查看共享内存段的使用情况    }    /*映射共享内存 */    if ((shmadd = shmat(shmid, 0, 0)) < (char*)0) {        perror("shmat");        exit(1);    } else {        printf("share memory attached\n");        system("ipcs -m");    }    /*撤销共享内存 */    if (-1 == (shmdt(shmadd))) {        perror("shmdt");        exit(1);    }    printf("share memory detached\n");    system("ipcs -m");    /*删除共享内存*/    if(shmctl(shmid,IPC_RMID,NULL)<0){        perror("shmctl");        exit(1);    }    printf("delete share memory\n");    system("ipcs -m");    return 0;} 

输出:

create shmid=55672849

------ Shared Memory Segments --------
key      shmid    owner perms  bytes  nattch status

0x00000000 55869463   orisun     600        10         0        

share memory attached

0x00000000 55869463   orisun     600        10         1              

share memory detached

0x00000000 55869463   orisun     600        10         0        

delete share memory

 

nattch表示IPC对象的访问次数         

消息队列

 消息队列具有一定的FIFO性,向队列中添加一条消息总是添加到队尾,但又可以实现消息的随机查询。
消息队列在内核中。
struct msgbuf{
  long mtype;//消息类型
  char mtext[1];//消息正文
}
系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型如下:
key_t ftok( char * fname, int id )
fname就时你指定的文件名,id是子序号。
#include<sys/types.h>#include<sys/ipc.h>#include<sys/shm.h>#include<string.h>#include<stdio.h>#include<stdlib.h>#define MSGLEN 1024struct message {    long msg_type;    char msg_cont[MSGLEN];};int main(){    struct message msg, msg2;    int queueid;    int len;    key_t key;    if ((key = ftok(".", 'a')) == -1) {        perror("tfok");        exit(1);    }    /*创建消息队列 */    if ((queueid = msgget(key, IPC_CREAT | 0666)) == -1) {        perror("msgget");        exit(1);    }    printf("message queue created.\n");    puts("please input the message you add to the queue:");    //puts和fgets都是行读写函数。puts会在行后自动加上换行符'\n'    if ((fgets((&msg)->msg_cont, MSGLEN, stdin)) == NULL) {    //fgets会自动在读取的消息后面加一个'\n'然后才写入缓冲区。具体情况看运行结果截图        perror("fgets");        exit(1);    }    (&msg)->msg_type = getpid();    len = strlen(msg.msg_cont);    //strlen得到字符串的真实长度,不包括末尾的'\0'    /*将消息添加到队尾 */    if ((msgsnd(queueid, (&msg), len, 0)) < 0) {    //第3个参数是消息的长度,要求不能以NULL结尾        perror("message posted");        exit(1);    }    /*从队列中接收消息 */    if ((msgrcv(queueid, (&msg2), MSGLEN, msg.msg_type, 0)) == -1) {        perror("msgrcv");        exit(1);    }    printf("received message is %s\n", msg2.msg_cont);    /*从内核中移除消息队列 */    if ((msgctl(queueid, IPC_RMID, NULL)) == -1) {        perror("msgctl");        exit(1);    }    return 0;}