Linux 环境编程 day06 管道通信、XSI进程间通信、消息队列、共享内存、信号量、本地套接字
进程通信基本概念
1、什么是进程间通信
进程间通信(Interprocess communication 也叫IPC):指两个或多个进程之间数据交互的过程。
为什么进程之间需要通信:进程之间是相互独立的,当它们合作完成一些任务时就需要交互数据。
2、进程间通信的分类
1、简单的进程间通信
命令行参数、环境变量、信号、文件
2、传统的进程间通信:管道
3、XSI进程间通信:共享内存、消息队列、信号量
4、基于socket文件的套接字
传统的进程间通信方式-管道
管道是UNIX系统最古老的一种基于文件通信方式,现在已经很少使用,是一种半双工的通信方式。
与普通文件相比的优势就是,当文件内容不存在时,会自动阻塞,不需要调整位置指针。
管道中的数据是流式的,可能出现"粘包"问题,解决方法:
1、每条数据包中有一个结束标志,常用的标志位有'\n'或'\0',接收者每次只接收1个字节,当遇到结束标志时一个完整的数据包才接收完成,缺点:用户态和内核态之间频繁切换浪费了大量的时间。
2、规定数据包的大小,发送者与接收者之间约定好数据包的大小,缺点:数据包的大小由最大的数据包决定,如果数据包之间数据量差异比较大,可能会发送大量的垃圾数据。
3、设置一个包头:
bytes:123412
asdfsdflqwerkjlwekrj
有名管道:
进程A 进程B
创建管道文件(mkfifo) ...
打开管道文件(open) 打开管道文件
写(read/write) 读
关闭(close)
删除(remove)
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个管道文件
pathname:管道文件的路径
mode:管道文件的权限
匿名管道:
原理也是基于管道文件,但它没有文件暴露在文件系统中,因此不需要创建也不需要删除,只适合用于父子进程之间。
int pipe(int pipefd[2]);
功能:打开一个匿名的管道文件
pipefd:存储管道文件描述的数组
注意:下标为0的用于读,下标为1用于写。
父进程读 子进程写
获取管道文件描述符pipe ...
创建子进程 会自动拷贝两个文件描述符
关闭1 关闭0
read 0 write 1
关闭 关闭
XSI进程间通信
XSI X/Open System Interface
1、IPC标识符(代表一个用于进程间通信的内核对象)
使用XSI方式进行进程间通信时,系统会在内核中创建一个内核对象用于通信,然后这个对象会以整数的形式提供给各个进程,类似于文件描述符,是一个非负整数。
与文件描述符不同的是,从小往大使用,当达到最大值时,向0回转。
2、IPC键值
用于创建XSI-IPC内核对象的凭证,相当于IPC对象的名字,类似于文件名。
自定义(类似于文件取名),但是有重名的风险。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:自动生成一个IPC键值
pathname:当前项目的路径,必须一个真实存在的路径,它靠的路径的位置,而不是字符串。
proj_id:当前项目的编号
注意:只要提供相同的路径和项目编号,就可以获得相同的IPC键值。
消息队列
1、基本原理
是由系统内核维护的一个链式队列,每个一条消息由类型、数据、数据长度组成。
和管道类似,但它可以双向通信,还可以按类型接收消息,而不是顺序。
2、常用函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:创建/获取消息队列
msgflg:
0 用于获取消息队列
IPC_CREAT 创建消息队列
IPC_EXCL 消息队列存在则创建失败
mode 权限
返回值:成功返回消息队列标识符,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送数据
msgp:结构指针
struct msgbuf {
long mtype;
char mtext[n];
};
msgsz:数据的长度,注意不包括消息类型。
msgflg:
0 当消息队列满时,阻塞
IPC_NOWAIT 当消息队列满时,不阻塞。
返回值:成功返回0,失败返回-1
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:从消息队列接收数据
msgp:存储数据的空间
msgsz:空间的字节数
msgtyp:要接收的消息类型
> 0 只接收消息类型等于msgtyp的数据包
= 0 收消息队列中排名第一的数据包
< 0 接收第一个消息类型等于<=abs(msgtyp)的数据的包
msgflg:
0 当消息不存在,阻塞。
MSG_EXCEPT 接收消息队列中第个类型不是msgtyp的消息
IPC_NOWAIT 如果接收的消息不存在,立即返回。
返回值:返回收到的字节数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:销毁/控制消息队列
cmd:
IPC_STAT 获取消息队列的属性
IPC_SET 设置消息队列的属性
IPC_RMID 删除消息队列
buf:消息队列属性结构指针
编程模型:
进程A 进程B
获取key 获取key
创建消息队列 获取消息队列
发送数据 接收数据
接收数据 发送数据
删除
共享内存
1、基本原理
在内核中开辟一内存,可以让其它进程的虚拟地址与它进行映射,这样就达到多个进程共享同一块内存的目的,当一个进程向这块内存写数据时,其它进了也都可以看到,这样就达到了通信的目的。
注意:这种通信方式不存在数据的复制,是最快进程间通信方式。
2、常用的函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:创建/获取一块内核中共享内存
key:IPC键值(文件名)
size:内存的大小,字节数
shmflg:0表示该参数无效,获取共享内存,size的值也无效。
IPC_CREAT 创建
IPC_EXCL 如果存在则创建失败
mode 权限
返回值:IPC标识符
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:加载共享内存(把进程中的虚拟地址与内核中的共享内存建立映射关系)
shmid:IPC标识符
shmaddr:虚拟地址,也可以为NULL,操作系统会自动分配。
shmflg:
SHM_RDONLY 只读
SHM_RND 当shmaddr为空时,向下整的内存页的整数倍。
返回值:成功返回映射后的虚拟地址,返回返回-1。
int shmdt(const void *shmaddr);
功能:卸载共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:销毁/控制共享内存
shmid:IPC标识符
cmd:
IPC_STAT 获取共享内存的属性
IPC_SET 设置共享内存的属性,uid,gid,mode可以设置。
IPC_RMID 删除共享内存
struct shmid_ds {
struct ipc_perm shm_perm; 所有都及权限
size_t shm_segsz; 共享内存的大小,以字节为单位
time_t shm_atime; 最后加载时间
time_t shm_dtime; 最后卸载时间
time_t shm_ctime; 最后修改时间
pid_t shm_cpid; 创建者的PID
pid_t shm_lpid; 最后加载/卸载的者PID
shmatt_t shm_nattch; 加载计数器
};
3、编程模型
进程A 进程B
创建 获取
加载 加载
使用 使用
卸载 卸载
销毁
作业:使用共享内存让进程之间可以对话。
信号量
1、基本原理
存在于内核中的一个整型变量,相当于计数器,用于限制多个进程对于共享资源的访问。
当一个资源需要被多个进程共享访问,第一次应该初始化一个该资源有多个份(定义一个信号量并初始化)。
当有一个进程占用资源时,信号量减1,如果不够减则阻塞。
当一个进程使用资源完毕后,归还资源,信号量加1,此时之前被阻塞状态的进程将被唤醒。
2、常用函数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:创建/获取信号量
nsems:信号的数量,一般为1
semflg:
0 获取信号量
IPC_CREAT 创建信号量
IPC_EXCL 如果存在则出错
semflg 权限
返回值:成功则返回信号量标识符,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对信号进程加1或减1操作,如果不能减1则阻塞
sops: 数组
unsigned short sem_num; 信号量的编号,一般为0
short sem_op; 对信号量的操作,
short sem_flg;
0 阻塞
IPC_NOWAIT 当信号量不能操作时,立即返回,不阻塞。
nsops:表示sops的数量,数组的长度。
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout);
功能:对信号进程加1或减1操作,带倒计时的阻塞
timeout:超时时间
int semctl(int semid, int semnum, int cmd, 1...);
功能:销毁/控制信号量
semnum:信号量的编号
cmd:
IPC_STAT 获取信号属性
IPC_SET 设置信号量属性
IPC_RMID 删除信号量
SETVAL 设置信号量的值
GETVAL 获取信号量的值
编程模型:
进程A 进程B
获取key 获取key
创建信号量 获取信号量
初始化信号量的值 ...
操作信号量-1 操作信号量-1
使用资源... 使用资源...
操作信号量+1 操作信号量+1
销毁信号量
本地套接字
1、基本原理
把网络服务抽象成文件(套接字文件),按照网络通信的格式,进行进程间通信。
2、常用函数
int socket(int domain, int type, int protocol);
功能:创建socket对象
domain:通信方式
AF_UNIX 本地套按字
AF_INET 网络通信
type:数据的发送方式
SOCK_STREAM 数据流
SOCK_DGRAM 报文
protocol:特殊协议,写0即可
返回值:
成功返回socket对象描述符
失败返回-1
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:把socket对象与socket文件绑定
sockfd:socket对象描述符
addr:socket文件的地址
addrlen:addr 实际类型的字节数
struct sockaddr_un
{
sa_family_t sun_family; // 与socket函数的domain参数相同即可
char sun_path[108]; // socket文件的路径
};
int listen(int sockfd, int backlog);
sockfd:socket对象描述符
功能:开户监听,并设置最大排队数量
backlog:最大排队数量
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:等待对方连接
sockfd:socket对象描述符
addr:用于存储对方的通信地址
addrlen:
既是输入又是输出,注意。
返回值:连接成功的socket描述符,可以用它进行读写数据。
read/write/close函数还可以继续使用。
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
功能:连接到socket对象
addr:对方和地址
addr 实际类型的字节数
编程模型:
进程A 进程B
1、创建socket对象 1、创建socket对象
2、准备地址(文件的路径) 2、准备地址(文件的路径)
3、绑定socket对象和socket文件 ...
4、开启监听 ...
5、等待连接 3、连接
6、读/写数据 4、写读数据
7、关闭socket对象 5、关闭socket对象
查看ipc对象: ipcs -a 显示所有ipc对象 ipcs -q 显示消息队列 ipcs -m 显示共享内存 ipcs -s 显示信号量
删除ipc对象: ipcrm -q 删除消息队列 ipcrm -m 删除共享内存 ipcrm -s 删除信号量
|