Linux 常见的六大 IPC 通信方式

栏目: 服务器 · 发布时间: 6年前

内容简介:Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。今天我们来看一下,Linux下常见的六大IPC通信方式。信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执

Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcessCommunication)。今天我们来看一下,Linux下常见的六大IPC通信方式。

1、信号

信号是Unix/Linux系统在一定条件下生成的事件。信号是一种异步通信机制,进程不需要执行任何操作来等待信号的到达。信号异步通知接收信号的进程发生了某个事件,然后操作系统将会中断接收到信号的进程的执行,转而去执行相应的信号处理程序。

  • (1)注册信号处理函数
#include<signal.h>
     /*typedefvoid (*sighandler_t)(int);  sighandler_t signal(int signum,sighandler_thandler);*/
    
    void (*signal(intsignum, void (*handler)(int)))(int);  //SIG_IGN && SIG_DFL
    
    int sigaction(int signum, const struct sigaction*act,struct sigaction *oldact);
复制代码
  • (2)发送信号
#include<signal.h>
    intkill(pid_t pid,int sig); //#include<sys/types.h> 
    intraise(intsig);            //kill(getpid(),sig);
    unsignedint alarm(unsigned int seconds); //(#include<unistd.h>) seconds秒后,向进程本身发送SIGALRM信号。
复制代码
  • (3)信号集 信号集被定义为:
typedef struct {unsigned long sig[_NSIG_WORDS];}sigset_t;
    * intsigaddset(sigset_t *set,int sig);
    * intsigemptyset(sigset_t *set);
复制代码

2、管道(Pipe)

管道用来连接不同进程之间的数据流。

  • (1)在两个程序之间传递数据的最简单的方法是使用popen()和pclose()函数:
#include<stdio.h>
    FILE *popen(const char *command,const char *open_mode);
    int pclose(FILE *stream);
复制代码

popen()函数首先调用一个shell,然后把command作为参数传递给shell。这样每次调用popen()函数都需要启动两个进程;但是由于在 Linux 中,所有的参数扩展(parameter expansion)都是由 shell 执行的,这样command中包含的所有参数扩展都可以在command程序启动之前完成。

  • (2)pipe()函数:
int pipe(int pipefd[2]);
复制代码

popen()函数只能返回一个管道描述符,并且返回的是文件流(filestream),可以使用函数fread()和fwrite()来访问。pipe()函数可以返回两个管道描述符:pipefd[0]和pipefd[1],任何写入pipefd[1]的数据都可以从pipefd[0]读回;pipe()函数返回的是文件描述符(file descriptor),因此只能使用底层的read()和write()系统调用来访问。pipe()函数通常用来实现父子进程之间的通信。

  • (3)命名管道:FIFO
intmkfifo(const char *fifo_name, mode_t mode);
复制代码

前面两种管道只能用在相关的程序之间,使用命名管道可以解决这个问题。在使用open()打开FIFO时,mode中不能包含O_RDWR。mode最常用的是O_RDONLY,O_WRONLY与O_NONBLOCK的组合。O_NONBLOCK影响了read()和write()在FIFO上的执行方式。

3、信号量(Semaphores)

System V的信号量集表示的是一个或多个信号量的集合。内核为每个信号量集维护一个semid_ds数据结构,而信号量集中的每个信号量使用一个无名结构体表示,这个结构体至少包含以下成员:

struct{
        unsigned short semval;//信号量值,总是>=0
        pid_t sempid;  //上一次操作的pid
       …
    };
复制代码
  • (1)创建或访问信号量
intsemget(key_t key,int nsems,int flag); 
复制代码

nsems指定信号量集中信号量的个数,如果只是获取信号量集的标识符(而非新建),那么nsems可以为0。flag的低9位作为信号量的访问权限位,类似于文件的访问权限;如果flag中同时指定了IPC_CREAT和IPC_EXCL,那么如果key已与现存IPC对象想关联的话,函数将会返回EEXIST错误。例如,flag可以为IPC_CREAT|0666。

  • (2)控制信号量集
int semctl(intsemid,int semnum,int cmd,union semun arg);
复制代码

对semid信号量集合执行cmd操作;cmd常用的两个值是:SETVAL初始化第semnum个信号量的值为arg.val;IPC_RMID删除信号量。

  • (3)对一个或多个信号量进行操作
int semop(intsemid,struct sembuf *sops,unsigned nsops);
    struct sembuf{
            unsignedshort sem_num;  //信号量索引
            short   sem_op;     //对信号量进行的操作,常用的两个值为-1和+1,分别代表P、V操作
            short  sem_flag;   //比较重要的值是SEM_UNDO:当进程结束时,相应的操作将被取消;同时,如果进程结束时没有释放资源的话,系统会自动释放
    };
复制代码

4、共享内存

共享内存允许两个或多个进程共享一定的存储区,因为不需要拷贝数据,所以这是最快的一种IPC。

  • (1)创建或访问共享内存
intshmget(key_t key,size_t size,int shmflg);
复制代码
  • (2)附加共享内存到进程的地址空间
void*shmat(int shmid,const void *shmaddr,int shmflg);// shmaddr通常为NULL,由系统选择共享内存附加的地址;shmflg可以为SHM_RDONLY
复制代码
  • (3)从进程的地址空间分离共享内存
* intshmdt(const void *shmaddr); //shmaddr是shmat()函数的返回值
复制代码
  • (4)控制共享内存
intshmctl(int shmid,int cmd,struct shmid_ds *buf);
        struct shmid_ds{
          structipc_perm shm_perm;
          …
      }; 
复制代码

cmd的常用取值有:

(a)IPC_STAT获取当前共享内存的shmid_ds结构并保存在buf中

(b)IPC_SET使用buf中的值设置当前共享内存的shmid_ds结构

(c)IPC_RMID删除当前共享内存

5、消息队列

消息队列保存在内核中,是一个由消息组成的链表。

  • (1)创建或访问消息队列
int msgget(key_t key,int msgflg);
复制代码
  • (2)操作消息队列
intmsgsnd(int msqid,const void *msg,size_t nbytes,int msgflg);
复制代码

msg指向的结构体必须以一个longint成员开头,作为msgrcv()的消息类型,必须大于0。nbytes指的是msg指向结构体的大小,但不包括longint部分的大小 ssize_t msgrcv(intmsqid,void *msg,size_t nbytes,long msgtype,int msgflg); 如果msgtype是0,就返回消息队列中的第一个消息;如果是正整数,就返回队列中的第一个该类型的消息;如果是负数,就返回队列中具有最小值的第一个消息,并且该最小值要小于等于msgtype的绝对值。

  • (3)控制消息队列
int msgctl(intmsqid,int cmd,struct msqid_ds *buf);
        struct msqid_ds{
        struct ipc_permmsg_perm;
        …
       };

复制代码

6、Socket

套接字(Socket)是由Berkeley在BSD系统中引入的一种基于连接的IPC,是对网络接口(硬件)和网络协议(软件)的抽象。它既解决了无名管道只能在相关进程间单向通信的问题,又解决了网络上不同主机之间无法通信的问题。

  套接字有三个属性:域(domain)、类型(type)和协议(protocol),对应于不同的域,套接字还有一个地址(address)来作为它的名字。

  域(domain)指定了套接字通信所用到的协议族,最常用的域是AF_INET,代表网络套接字,底层协议是IP协议。对于网络套接字,由于服务器端有可能会提供多种服务,客户端需要使用IP端口号来指定特定的服务。AF_UNIX代表本地套接字,使用Unix/Linux文件系统实现。

  IP协议提供了两种通信手段:流(streams)和数据报(datagrams),对应的套接字类型(type)分别为流式套接字和数据报套接字。流式套接字(SOCK_STREAM)用于提供面向连接、可靠的数据传输服务。该服务保证数据能够实现无差错、无重复发送,并按顺序接收。流式套接字使用TCP协议。数据报套接字(SOCK_DGRAM)提供了一种无连接的服务。该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP协议。

  一种类型的套接字可能可以使用多于一种的协议来实现,套接字的协议(protocol)属性用于指定一种特定的协议。   

  • (1)创建套接字
int socket(int  domain,int  type,int  protocol);
复制代码

对于SOCK_STREAM和SOCK_DGRAM而言,分别只有一种协议支持这种类型的套接字。因此protocol可以为0,表示默认的协议。

  • (2) 绑定套接字
int bind(int  sockfd,const  struct sockaddr *addr,socklen_t addrlen);//将无名套接字sockfd与addr绑定(bind)
复制代码
  • (3)监听套接字
int listen(int  sockfd,int  backlog);//backlog限定了等待服务的队列的最大长度
复制代码
  • (4)等待接受连接
int accept(int  sockfd,struct  sockaddr *addr,socklen_t  *addrlen);
复制代码

当客户端程序尝试连接sockfd套接字时,accept返回一个新的套接字与客户端进行通信。如果addr不是NULL,那么客户端的地址将会保存在addr所指向的结构体中;调用accept()前必须先将addrlen初始化为addr所指向结构体的大小,accept()返回以后,addrlen将会被设置成客户端套接字地址结构体的实际大小。然后,通过对accept()返回的套接字执行read()和write()操作即可实现与客户端的简单的通信。   

  • (5)建立连接(客户端)
int connect(int  sockfd,const  struct sockaddr *addr,socklen_t addrlen);
复制代码

connect()在无名套接字sockfd和addr之间建立连接。addr指向的结构体中可以包含服务器的IP地址和端口号等信息。

  • (6)数据传输
ssize_t send(int sockfd,const void *buf,size_t  len,int  flags);
  
ssize_t recv(int sockfd, void *buf, size_t len,int flags);
复制代码
  • (7)关闭套接字
int close(int  fd);
复制代码
  • (8)主机字节序和网络字节序的转换
#include <netinet/in.h>
  
unsigned long int  htonl(unsigned  long int  hostlong);  //host to  network,long
  
unsigned short int  htons(unsigned  short int  hostshort);
  
unsigned long  int ntohl(unsigned long int  netlong);
  
unsigned short int  ntohs(unsigned  short int  netshort);
  
// long型函数用来转换sockaddr_in.in_addr.s_addr;
// short型函数用来转换sockaddr_in.sin_port。
复制代码

以上所述就是小编给大家介绍的《Linux 常见的六大 IPC 通信方式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Spring in Action

Spring in Action

Craig Walls / Manning Publications / 2011-6-29 / USD 49.99

Spring in Action, Third Edition has been completely revised to reflect the latest features, tools, practices Spring offers to java developers. It begins by introducing the core concepts of Spring and......一起来看看 《Spring in Action》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具