进程间通信

本文参考《Linux程序设计 第4版》、《高性能服务器编程》


进程间通信(IPC,InterProcess Communication),不同进程之间传播或交换信息。
实现方法包括管道(无名和命名)、信号量、共享内存、消息队列、socket等。

  1. 管道
    把一个进程的输出通过管道连接到另一个进程的输入。在内存中分配空间。
    (1)、无名管道pipe
pipe函数的原型如下:
#include<unistd.h>

int pipe(int fd[2])

形参:拥有两个整形数的数组指针
返回值:函数在数组中填上新的文件描述符后成功返回0,失败返回1

两个返回的文件描述符以特殊的方式连接在一起,写到fd[1]的所有数据可以从fd[0]中读取。数据基于先进先出的原则(FIFO)进行处理,类似于队列,比如写到fd[1]中为 1 2 3,从fd[0]中读出来也是1 2 3。
在网上看到这两个图片很形象,就荡过来了~
这里写图片描述
上个程序练练手
这里写图片描述
首先pipe产生两个新的文件描述符,fork之后又多出两个一模一样的描述符,一个用于读,一个用于写,一共有4个文件描述符。
pipe是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

(2)、命名管道FIFO
FIFO可以在无关的进程之间交换数据,与无名管道不同。在磁盘上创建,打开时在内存中写入。
它在文件系统中以文件名的形式存在。
可以用命令行创建命名管道,也可以用程序创建。
这里写图片描述
mkfifo命令,可以创建出名为FIFO的命名管道。

用程序创建原型:

#include<sys/types.h>
#inculde<sys/stat.h>
int mkfifo(const char* filename,mode_t mode);
形参:filename为将要在文件系统中创建的一个专用文件,mode为该文件的权限
返回值:成功返回0,失败返回-1

可以用下面的命令行来查找命名管道
这里写图片描述
注意输出的第一个字符为p,表示这是一个管道,最后的 | 也表示管道的意思,是ls命令的-F选项添加的。

fifor.c
这里写图片描述

fifow.c
这里写图片描述
运行结果(分别为两个终端)
这里写图片描述

2、信号量
用于管理对资源的访问

相关概念
(1)、竞态条件:是指两个或多个进程对共享的数据进行读或写的操作 时,最终的结果取决于这些进程的执行顺序。
(2)、临界区:访问临界资源的代码段。
(3)、临界资源:同一时段只允许一个进程访问的资源。
(4)、原子操作:不会被线程调度机制打断的操作

信号量是一个特殊的变量,只能取正整数值,并且程序对其访问都是原子操作。只允许对它进行等待(wait)和发送信号(signal)操作。
p(信号量变量)用于等待
v(信号量变量)用于发送信号
信号量分两种:
二进制信号量,值取0和1两种。
通用信号量,可以取多个值。

工作原理:当两个进程都要进行独占式访问临界区时,两个进程共享同一个信号量(sv),当临界区可以访问时,其中一个进程得到信号量,并进行p操作(既原子量-1),表示资源正在占用。此时另一个进程若执行p操作,将会阻塞,被挂起等待资源被释放。当这个资源用完后,进行v操作(原子量+1),释放资源。
这里写图片描述

信号量函数原型
#include<sys/sem.h>
int semget(key_t key,int num_sems,int flags);   
//创建信号量,key为非零整数值,num_sems为信号量的数量,flags为权限,一般写IPC_CREATE|IPC_EXCL
返回信号量标识符,失败返回-1
int semop(int sem_id,struct sembuf* em_ops,size_t num_sem_ops);
//操作信号量,sem_id为semget返回的标识符,em_ops为sembuf结构体指针,num_sem_ops为
int semctl(sem_id,sem_num,int command);
//控制信号量信息,sem_num 信号量编号,command一般取值SETVAL或IPC_RMID

这里写图片描述

这里写图片描述

这里写图片描述

3、共享内存
共享内存可以在已经运行中的进程之间传递消息,通过IPC机制创建的一段特殊的物理地址空间,其他进程可以将同一段共享内存连接在自己的内存地址中,共享内存中的内容被修改,立刻就会被其他进程发现,可以是说很高效的通信的通信方式。
但是对于共享内存的同步的需要程序员自己进行,一般配合上信号量一起使用。

int shmget(key_t key,size_t size,int shmflg);
  //创建共享内存,size为共享内存的大小,shmflg设置为IPC_CREATE
void * shmat(int shm_id,const void *shm_addr,int shm_flg);
//连接共享内存进入进程内存空间,共享内存连接进入进程中的地址位置,一般有系统自己给定,设置为NULL,shm_flg为标志位SHM_RND(用来控制共享内存连接的地址)和SHM_RDONLY(将共享内存设置为只读)
返回指向共享内存第一个字节的指针,失败返回-1
int shmdt(const void *shm_addr);
//将内存地址从进程空间剥离出来,参数为shmat返回的第一个指针,并未删除共享内存,而是是进程对其不可用。
int shmctl(int shm_id,int command,struct shmid_ds *buff)
//command表示是要采取的动作,buff是一个指向共享内存模式和访问权限的结构

4、消息队列
提供了一种从一个进程到另一个进程发送数据块的方法

函数原型
#include<sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
int msgget(key_t key,int msgflg);//创建一个消息队列
int msgrcv(int msqid,void *msg_ptr,size_t msg_sz,long int msgtype,int msgflg);//从一个消息队列中获取消息
int msgsnd(int msqid,const void*msg_ptr,size_t msg_sz,int msgflg);//将消息添加到消息队列中

消息队列及共享队列在后期会继续更实现,这里不详细说明~~~

5、套接字socket
socket本质就是可读、可写、可操作、可关闭的文件描述符
这是网络编程的一块肥肉
使用TCP套接字编程可以实现基于TCP/IP协议的面向连接的通信,它分为服务器端和客户端两部分。
这是socket编程的基本流程
这里写图片描述
(1)、创建socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int family,int type,int protocol);

第一个参数指明了协议簇,目前支持5种协议簇,最常用的有AF_INET(IPv4协议)和AF_INET6(IPv6协议);第二个参数指明套接口类型,有三种类型可选:SOCK_STREAM(字节流套接口)、SOCK_DGRAM(数据报套接口)和SOCK_RAW(原始套接口);如果套接口类型不是原始套接口,那么第三个参数就为0。
(2)、命名套接字bind
bind函数:为套接口分配一个本地IP和协议端口,对于网际协议,协议地址是32位IPv4地址或128位IPv6地址与16位的TCP或UDP端口号的组合;如指定端口为0,调用bind时内核将选择一个临时端口,如果指定一个通配IP地址,则要等到建立连接后内核才选择一个本地IP地址。

#include <sys/socket.h>  
int bind(int sockfd, const struct sockaddr * server, socklen_t addrlen);
返回:0---成功   -1---失败 

 第一个参数是socket函数返回的套接口描述字;第二和第三个参数分别是一个指向特定于协议的地址结构的指针和该地址结构的长度。
(3)、listen

#include <sys/socket.h>
int listen(int sockfd,int backlog);   
返回:0---成功   -1---失败

第二个参数规定了内核为此套接口排队的最大连接个数。由于listen函数第二个参数的原因,内核要维护两个队列:以完成连接队列和未完成连接队列。未完成队列中存放的是TCP连接的三次握手未完成的连接,accept函数是从以连接队列中取连接返回给进程;当以连接队列为空时,进程将进入睡眠状态。
(4)、accept
由TCP服务器调用,从已完成连接队列头返回一个已完成连接,如果完成连接队列为空,则进程进入睡眠状态

#include <sys/socket.h>         
int accept(int listenfd, struct sockaddr *client, socklen_t * addrlen);  
返回:非负描述字---成功   -1---失败

第一个参数是socket函数返回的套接口描述字;第二个和第三个参数分别是一个指向连接方的套接口地址结构和该地址结构的长度;该函数返回的是一个全新的套接口描述字;如果对客户端的信息不感兴趣,可以将第二和第三个参数置为空。
(5)、connect

#include <sys/socket.h>      
int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);  
 返回:0---成功   -1---失败

服务器端:
这里写图片描述
这里写图片描述

客户端:
这里写图片描述
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值