进程间通信

无名管道

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

在父进程中创建无名管道后,又创建子进程,则父子进程中分别拥有独立的读端和写端。所以要把父进程的写端 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 32700
int 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 2048

int 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 1024

struct 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;
}

 

posted @ 2011-01-23 17:21  高性能golang  阅读(522)  评论(0编辑  收藏  举报