进程间通信(IPC)
1 管道
- 通过在内存中创建管道文件实现进程间的通信
- 大小为0,磁盘不存储内容
- 文件操作符,使用和普通文件操作一样
进程打开:0标准输入,1标准输出,2标准错误输出 - 创建管道文件命令
mkfifo 管道文件名
- 有名管道:任意进程通信
- 无名管道:只能父子进程通信
- 半双工:某一时刻只能进行单向操作
单工:只能单向操作
全双工:同时进行双方操作
#include <unistd.h>
int pipe(int pipefd[2]);
//pipe()创建一个管道
//数组pipefd用于返回两个指向管道末端的文件描述符
//pipefd[0]:读端
//pipefd[1]:写端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
int fd[2];
pipe(fd);
pid_t pid = fork();
assert(pid!=-1);
if(pid == 0)
{
close(fd[1]);//关闭子进程的写操作
char buf[128] = {};
read(fd[0],buf,127);
printf("%s\n",buf);
close(fd[0]);
}
else
{
close(fd[0]);//关闭父进程的读操作
write(fd[1],"hello",5);
close(fd[1]);
}
}
管道必须是双端操作
读端关闭,写端接收到SIGPIPE信号,终止写入。
2 信号量
- 临界资源:某一时刻只允许一个进程进行访问的资源
- 临界区:访问临界资源的代码段
- 特殊变量
- 原子增值,p:等待,获取资源(当信号量为0时,进程堵塞)
- 原子减值,v:发送信号,释放资源
原子内存操作,底层CPU支持,硬件加速
- 二值信号量:0,1(可访问 / 不可访问)
- 计数信号量:可获取资源计数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建一个信号量或取得信号量的key
int semget(key_t key, int nsems, int semflg);
//key: 唯一key值,不同进程通过key值访问同一个信号量
//nsems: 指定创建信号量的个数
//semflg: 标志位,信号量的权限,联合使用IPC_CREAT和IPC_EXCL确保创建唯一的新信号量
//返回值:-1,创建失败;>0,成功,返回信号量标识符semid
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//改变信号量的值,实现pv操作
int semop(int semid, struct sembuf *sops, size_t nsops);
//semid: semget返回的信号量标识符
//struct sembuf *sops
//{
// unsigned short sem_num;//信号量编号
// short sem_op;//操作信号量的数值;-1,p操作;+1,v操作
// short sem_flg;//标志,SEM_UNDO当进程终止时,该操作将自动撤销,用于释放资源
//}
//nsops: sops结构体个数
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//控制信号量信息
int semctl(int semid, int semnum, int cmd, ...);
//semid: semget返回的信号量标识符
//semnum: 信号量编号,唯一一个信号量时为0,
//cmd: 命令参数,SETVAL设置信号量初始值,IPC_RMID删除信号量
//...:SETVAL需要第四个参数,union semun结构体
//union semun//需要外部自己定义联合体
//{
// int val;//SETVAL使用该成员,将其设置为信号量的初始值
// struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
// unsigned short *array; /* Array for GETALL, SETALL */
// struct seminfo *__buf; /* Buffer for IPC_INFO
// (Linux-specific) */
//};
ipcs #查看消息队列、共享内存、信号量通信情况
------ Message Queues --------//消息队列
key msqid owner perms used-bytes messages
------ Shared Memory Segments --------//共享内存
key shmid owner perms bytes nattch status
------ Semaphore Arrays --------//信号量
key semid owner perms nsems
0x000002c4 458752 root 600 1
ipcs -s #查看信号量
ipcrm -s semid #删除信号量
ipcs -q #查看消息队列
ipcs -m #查看共享内存
gcc -o PrinterA PrinterA.c sem.c
gcc -c sem.c
gcc PrinterA.c sem.o -o PrinterA
1 0 0
A B C
p1 p2 p3
v2 v3 v1
3 共享内存
- 不同进程共享同一个物理内存块
#include <sys/ipc.h>
#include <sys/shm.h>
//创建或获取共享内存
int shmget(key_t key, size_t size, int shmflg);
//key: 唯一key值,不同进程通过key值访问同一个内存
//size: 指定创建共享内存的大小
//shmflg: 标志位,共享内存的权限,IPC_CREAT获取共享内存,不存在则创建
//返回值:-1,创建失败;>0,成功,返回共享内存的ID,shmid
#include <sys/types.h>
#include <sys/shm.h>
//将共享内存映射到进程的虚拟地址空间
void *shmat(int shmid, const void *shmaddr, int shmflg);
//shmid: shmget返回的共享内存的ID
//shmaddr: 映射的虚拟地址,一般为NULL,有系统自动选择
//shmflg: 标志位;0,默认可读可写;SHM_EDONLY设置为只读模式
//返回值:共享内存的首地址,或失败返回NULL
#include <sys/types.h>
#include <sys/shm.h>
//断开共享内存和进程虚拟地址空间之间的映射
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
//shmaddr:映射的虚拟地址
//返回值:0,成功;-1,失败
#include <sys/ipc.h>
#include <sys/shm.h>
//控制共享内存
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//shmid: shmget返回的共享内存的ID
//cmd: 命令参数,IPC_RMID销毁共享内存
//struct shmid_ds *buf
//返回值:0,成功;-1,失败
4 消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//创建或获取一个消息队列
int msgget(key_t key, int msgflg);
//唯一key值,不同进程通过key值访问同一个消息队列
//msgflg: 标志位,消息队列的权限,IPC_CREAT获取共享内存,不存在则创建
//返回值:-1,创建失败;>0,成功,返回消息队列的ID,msqid
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
//发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
//msqid: msgget返回的消息队列的ID
//msgp:消息结构体指针
struct msgbuf
{
long mtype;//消息类型,必须 > 0
char mtext[1];//消息数据
};
//msgsz:消息数据的大小
//msgtyp:接收消息的类型,0无类型,默认接收所有类型的消息
//msgflg:标志位,默认0
5 套接字
套接字地址:IP+Port
#include <sys/socket.h>
//通用套接字地址结构
struct sockaddr
{
sa_family_t sa_family;//地址族,IPV4(AF_INET),IPV6(AF_INET6)
char sa_data[]; /* Socket address */
};
//专用,IPV4
struct sockaddr_in
{
sa_family_t sa_family;//地址族,AF_INET
u_int16_t sin_port;//端口号
struct in_addr sin_addr;//IP地址
//struct in_addr
//{
//u_int32_t s_addr;
//}
};
//专用,IPV6
struct sockaddr_in6
{
sa_family_t sa_family;//地址族,AF_INET6
u_inet16_t sin6_port;//端口号
u_int32_t sin6_flowinfo;//流信息
struct in6_addr sin6_addr;//IP地址
//struct in6_addr
//{
//unsigned char sa_addr[16];
//}
};
网络字节序:大端(htons)
1024(root) ~ 4096(保留)~ 临时端口
#include <arpa/inet.h>
//长整型,主机转网络
uint32_t htonl(uint32_t hostlong);
//短整型,主机转网络
uint16_t htons(uint16_t hostshort);
//长整型,网络转主机
uint32_t ntohl(uint32_t netlong);
//短整型,网络转主机
uint16_t ntohs(uint16_t netshort);
IP地址转换函数
十进制字符串表示IPV4
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
//字符串转网络字节序
in_addr_t inet_addr(const char *cp);
//网络字节序转字符串
char *inet_ntoa(struct in_addr in);
typedef uint32_t in_addr_t;//无符号整型
struct in_addr
{
in_addr_t s_addr;
};