Java教程

进程间通信

本文主要是介绍进程间通信,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

进程间通信(Interprocess Communication, IPC):管道、FIFO、消息队列、信号量、共享存储、套接字

1. 管道

1.1 创建:

#include <unistd.h>
 
int pipe(int fd[2]);

// 返回值:若成功,返回0,若出错,返回-1.
// 函数调用成功返回r/w两个文件描述符。无需open,但需手动close。
// 规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样
// 向管道文件读写数据其实是在读写内核缓冲区。

1.2 特点:

  • 其本质是一个伪文件(实为内核缓冲区)
  • 只适用于有血缘关系之间的进程
  • 数据一旦被读走,便不在管道中存在(不可反复读取)

1.3 局限性:

  • 半双工,数据只能在一个方向流动,只能单向通信
  • 管道只能在具有公共祖先之间的两个进程之间使用;

1.4 通信过程

管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。父子进程间通信的步骤:
在这里插入图片描述

  1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。

  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

2. 命名管道(FIFO):

1.1 创建:

#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);

//返回值:若成功,返回0;若出错,返回-1。
// 参数pathname为路径名,创建管道的名字
// mode为创建fifo的读写权限。

1.2 特点:

  • 命名管道不同于匿名管道:它提供一个路径名与之关联,以FIFO的文件形式存储于文件系统中,即第一个被写入的数据将首先从管道中读出
  • 命名管道是一个设备文件,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。

1.3 与匿名管道对比

  • mkfifo函数的作用是在文件系统中创建一个真实存在文件,该文件用于提供FIFO功能,即命名管道。前边讲的那些管道都没有名字,因此它们被称为匿名管道,或简称管道。
  • 对文件系统来说,匿名管道是不可见的,它的作用仅限于在父进程和子进程两个进程间进行通信。而命名管道是一个可见的文件,因此,它可以用于任何两个进程之间的通信,不管这两个进程是不是父子进程,也不管这两个进程之间有没有关系。

3. 消息队列

1.1 创建:

第一步:获得key值。因为消息队列独立于进程而存在,所以为了区别不同的消息队列,需要以key值标记消息队列,这样两个不相关的进程可以通过事先约定的key值通过消息队列进行消息收发。

#include <sys/types.h>
#include <sys/ips.h>
 
key_t futon(char *pathname, int projid)

// pathname:文件名(含路径),通常设置为当前目录“.” 
// projid:子序号,虽然为int类型,但只使用了低8位,通常用一个字母表示
// 返回值:成功返回key值,失败返回-1;

第二步:创建或打开消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflag)

/*
功能:用于创建一个新的或打开一个已经存在的消息队列,此消息队列与key相对应。
参数:
    key:函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时表示创建自己的消息队列,这样就只能用于和自己有亲缘关系的进程间通信。
    msgflag:
        IPC_CREAT:创建新的消息队列。
        IPC_EXCL:与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
        IPC_NOWAIT:读写消息队列要求无法满足时,不阻塞。
返回值:
    调用成功返回队列标识符,否则返回-1.
*/

第三步:将消息添加到消息队列

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgsnd(int msqid,  struct msgbuf *msgp,  size_t msgsz,  int msgflag)
/*
功能:
    将新消息添加到队列尾端,即向消息队列中发送一条消息。
参数:
    msqid:消息队列的标识符。
    msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下

struct msgbuf {
    long mtype;     /* 消息类型,必须 > 0 */
    char mtext[1];  /* 消息文本 */
};
    msgsz:消息的大小,消息的总大小不能超过8192字节,在<linux/msg.h>有个宏控制,#define MSGMAX 8192。
    msgflag:
         IPC_NOWAIT: 指明在消息队列没有足够空间容纳要发送的消息时,msgsnd立即返回。
         0:msgsnd调用阻塞直到条件满足为止.(一般选这个)
*/

第四步:从消息队列读取消息:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid,  struct msgbuf *msgp,  size_t msgsz,  long msgtype,  int msgflag)

/*
功能:
    从队列中接收消息
参数:
    msqid:已打开的消息队列id
    msgp:存放消息的结构体指针。msgp->mtype与第四个参数是相同的。
    msgsz:消息的字节数,指定mtext的大小。
    msgtype:消息类型,消息类型 mtype的值。如果为0,则接受该队列中的第一条信息,如果小于0,则接受小于该值的绝对值的消息类型,如果大于0,接受指定类型的消息,即该值消息。
    msgflag:函数的控制属性。
    msgflag:
        MSG_NOERROR:若返回的消息比msgsz字节多,则消息就会截短到msgsz字节,且不通知消息发送进程.
        IPC_NOWAIT:调用进程会立即返回.若没有收到消息则返回-1.
        0:msgrcv调用阻塞直到条件满足为止.
在成功地读取了一条消息以后,队列中的这条消息将被删除。
返回值:成功执行时,msgrcv()返回0, 失败返回-1
*/

1.2 特点:

  • 消息队列是消息的链表,具有特定的格式,存放在内存中,并由消息队列标识符标识.
  • 消息队列允许一个或多个进程向它写入与读取消息.
  • 管道和命名管道都是通信数据都是先进先出的原则,而消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取;比FIFO更有优势

4. 共享存储

共享存储允许一个或多个进程共享一个给定的存储区,因为数据不需要在客户端进程和服务器进程之间进行复制,所以这是最快的一种IPC,对于像管道和消息队列等通信方式,则需要在内核和用户空间进行四次的数据拷贝,而共享内存则只拷贝两次数据:一次从输入文件到共享内存区,另一次从共享内存区到输出文件。

不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新,反之亦然。由于多个进程共享同一块内存区域,必然需要某种同步机制——互斥锁和信号量都可以。

4.1 相关函数

第一步:创建/打开共享内存

#include <sys/ipc.h>
#include <sys/shm.h>
 
int shmget(key_t key, size_t size, int shmflg);
 
/*
    功能:分配一块共享内存
  
    参数:
         key: 函数ftok的返回值或IPC_PRIVATE,为IPC_PRIVATE时创建的共享内存只能用于有亲缘关系的进程通信;
         size:建立共享内存的大小(单位字节),通常取系统页长的整数倍,若size值不是系统页的整数倍,则最后一页剩余部分是不可用的;
         shmflg:
             0:取共享内存标识符,若不存在则函数会报错
             IPC_CREAT:如果内核中不存在键值与key相等的共享内存,则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
             IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;如果存在这样的共享内存则报错  16    返回值:成功,返回共享内存标识符,失败返回-1;      
*/

第二步:映射共享内存

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg)

/*
    功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问

    参数:
          msqid:共享内存标识符
          shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置
          shmflg:SHM_RDONLY:为只读模式,否则为读写模式

    返回值:
            成功,返回共享内存地址,出错返回-1
*/

4.2 共享存储的特点:

  • 速度快,效率高;

  • 需要有同步机制;

4.3 共享存储操作流程

  1. 创建/打开共享内存

  2. 映射共享内存,即把即把指定的共享内存映射到进程的地址空间用于访问

  3. 撤销共享内存映射

  4. 删除共享内存

5. UNIX域套接字

  • UNIX域套接字用于在同一台机器上运行的进程之间的通信,虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。
  • UNIX域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不要产生顺序号,无需发送确认报文。
  • UNIX域套接字有两种类型的套接字:字节流套接字和数据包套接字,字节流套接字类似于TCP,数据包套接字类似于UDP。
这篇关于进程间通信的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!