学习C高级(二十三)
IPC — Inter-Process Communication
两个进程间通信的方式:
- 共享文件
- 信号
- 管道(匿名、命名)
- 消息队列
- 共享内存 ---- 效率最高
处理通信规则的方式: - 信号量集
- 锁文件
- RPC—远程调用
信号
信号:某种事情发生的标识
< 32 : 不可靠信号,几乎不携带任何附属信息
SIGINT 2 ---- ctrl-c
SIGQUIT 3 ---- ctrl-
SIGKILL 9
SIGALRM 14
SIGUSR1 10
SIGUSR2 12
SIGCHLD 17 子进程退出时发给父进程
SIGCONT 18 进程继续
SIGSTOP 19 进程暂停
>= 32: 可靠信号,可以携带一些附属信息
命令行发信号:
kill:
kill -数字 pid
kill -信号名 pid
信号名去掉前面的SIG,用后面的字母组合
kill -9 2345 ----- kill -KILL 2345
killall
killall -数字 进程名(可执行文件名)
killall -信号名 进程名(可执行文件名)
发信号的函数:
kill
int kill(pid_t pid,int sig)
功能:给指定的进程发指定的信号
参数:
pid:收信号的进程ID
sig:发送的是哪个信号(信号的编号)
返回值:成功为0,失败-1
接收到信号的处理方式:
- 异常终止进程
- 进程暂停
- 忽略,不做任何处理
- 自定义收到信号的处理方法
指定信号处理方式
指定信号处理方式的函数:
sigaction
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact)
功能:设置对指定信号的处理方式
参数:
signum:信号编号
act:指向空间中存放着一个struct sigaction类型的元素
该元素用来设置新的信号处理方式
oldact:用来存放原来的信号处理方式
返回值:成功为0,失败-1
辅助函数:sigemptyset
int sigemptyset(sigset_t *set)
功能:将指定信号集合置空
深度睡眠与浅度睡眠的区别
浅度睡眠----能被信号唤醒
深度睡眠----不能被信号唤醒
struct sigaction
{
void (*sa_handler)(int);//当指定信号收到时,其指向函数将被调用
//SIG_DFL 指采用相同默认处理方式
//SIG_IGN 指收到信号不做任何处理,即忽略该信号
sigset_t sa_mask;//信号屏蔽字
int sa_flags;//固定填0
//其它成员
};
/*
设计一个死循环进程,当进程收到Ctrl+C---2号信号时不退出进程,而是打印字符串
*/
#include <stdio.h>
#include <string.h>
#include <signal.h>
void MySigFunc(int no);
int main()
{
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_handler = MySigFunc;//当指定信号收到时,其指向函数将被调用
sigemptyset(&act.sa_mask);//用来将参数act.sa_mask信号集初始化并清空。
act.sa_flags = 0;//固定填0
//当收到2号信号时设置新的信号处理方式,原来的信号置空
sigaction(2,&act,NULL);
while(1)
{
}
return 0;
}
void MySigFunc(int no)
{
printf("sig number is %d\n",no);
}
用户输入Ctrl+C时进程没退,而是打印字符串,用户输入另外一个退出信号Ctrl+
时退出
EINTR
while(阻塞函数调用出错判断)
{
if(errno == EINTR)
{//本次醒来不是资源就绪,而是信号引起,这是可忽略的错误
continue;
}
}
例如:
while((ret = read(fd,buf,4)) < 0)
{
if(errno == EINTR)
{
//信号引起的错误
continue;
}
else
{
//真正的出错
}
}
alarm
unsigned int alarm(unsigned int seconds)
功能:设置每隔多长时间给调用进程发送信号SIGALRM
参数:
seconds:秒数
返回值:返回前一次设置的间隔时间
pause
int pause()
功能:让调用任务进入永久浅度睡眠,只有进程收到信号本函数才返回
返回值:-1
sleep
unsigned int sleep(unsigned int seconds)
功能:
让调用任务进入浅度睡眠指定的秒数,
在收到信号本函数或指定时间到达才返回
参数:
seconds:秒数
返回值:
指定时间到达返回0,
收到信号时,返回剩余的描述
匿名管道 — 单工
pipe
int pipe(int pipefd[2])
功能:给调用进程创建一个匿名管道,并将管道两端的描述符反馈给调用进程
参数:
pipefd: 指向空间至少能够存放两个描述符
返回值:
成功0,失败-1
备注:运用在父子进程间(更准确的描述时具有亲缘关系的进程间)
创建一个管道:
创建一个子进程,这时子进程就可以和父进程通过管道交流,但有点乱,得改进
改进后,父进程读端关闭,子进程写端关闭。父进程留下写端,子进程留下读端,这时父进程通过管道写入数据,子进程从管道读出数据
/*
在父进程写入hello,在子进程读出hello
*/
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int arrfd[2] = {-1,-1};//管道的读端、写端
pid_t pid = 0;
pipe(arrfd);//创建匿名管道
pid = fork();//创建子进程
//创建子进程失败后关闭管道
if(pid < 0)
{
perror("fork error");
close(arrfd[0]);
close(arrfd[1]);
return 1;
}
if(pid == 0)
{//子进程,先关闭管道写端,读取数据后再关闭读端
char buf[8] = "";
close(arrfd[1]);
read(arrfd[0],buf,8);
printf("In son-process,buf=%s\n",buf);
close(arrfd[0]);
}
else
{//父进程,先关闭管道读端,写入数据后再关闭写端
close(arrfd[0]);
write(arrfd[1],"hello",6);
wait(NULL);//对子进程做善后处理
close(arrfd[1]);
}
return 0;
}
命名管道 ----- 单工
mkfifo
int mkfifo(const char *pathname,mode_t mode)
功能:创建一个管道,同时创建一个特殊文件来给其命名
参数:
pathname:带路径的文件名,用来给管道命名
mode:给管道文件指定操作权限,用法同open、mkdir的mode参数一样
返回值:
成功0,失败-1
备注:
- 其它操作函数open close read write
- 命名管道open时会阻塞等待到对应端打开
- read函数返回0表示对端已关闭
- 与普通文件不一样,管道中如果有M个字节的数据,读走N字节后,
剩余M-N个字节
用mkfifo创建命名管道,write.c写入数据,read.c读出数据
/*
write.c
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int ret = 0;
int wfd = -1;//用于存放管道描述符
int x = 11;
ret = access("/tmp/myfifo",F_OK);//检查文件(命名管道)是否存在
if(ret < 0)//不存在就创建命名管道
{
mkfifo("/tmp/myfifo",0666);
}
wfd = open("/tmp/myfifo",O_WRONLY);//只写形式打开,命名管道open时会阻塞等待到对应端打开
if(wfd < 0)
{
perror("open fifo failed");
return 1;
}
printf("*************\n");//注意:当另一个管道端口打开时这代码才运行
write(wfd,&x,sizeof(int));
close(wfd);//关闭管道
wfd = -1;
return 0;
}
/*
reak.c
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
int ret = 0;
int rfd = -1;//用于存放打开文件(命名管道)描述符
int len = 0;
ret = access("/tmp/myfifo",F_OK);//检查文件(命名管道)是否存在
if(ret < 0)//不存在时创建管道
{
mkfifo("/tmp/myfifo",0666);
}
rfd = open("/tmp/myfifo",O_RDONLY);//只读形式打开命名管道
if(rfd < 0)
{
perror("open fifo failed");
return 1;
}
read(rfd,&len,sizeof(int));//读取命名管道里的数据
printf("len = %d\n",len);
close(rfd);
rfd = -1;
return 0;
}
先打开写端
再打开读端
通信:
所谓通信就是双方收发数据
通信成功的基本前提是通信双方使用同一套数据组织形式,
这样的数据组织形式被称为通信协议
协议数据(PDU:Protocol Data Unit)组织的两种方式:
- 纯文本形式
- 二进制形式
变长结构体:
结构体配合类型转换的一种灵活的使用方式
不定义这种类型的变量,只用这种类型的指针变量
struct ChatData//变长结构体的定义
{
int len;
char buf[1];
};
struct ChatData *pst1 = (struct ChatData *)malloc(24);//变长结构体的使用
pst1->len 前4个字节
pst1->buf 一维的字符数组名
表达式中单独出现等价于下标为0的元素地址,
地址类型为char型地址(char *)
对于指针类型的形参,按其在函数中作用分为:
- 函数调用过程中只读其指向空间内容 ----- 值参数
- 函数调用过程中只向其指向空间填写新内容,而不用老的内容 ---- 结果参数
- 函数调用过程中既要用其指向空间的老内容又要修改其指向空间的内容 ----- 值-结果参数
写进程完成,接收用户命令行的输入,并将输入内容发送给读进程,
如果输入是"quit"则退出,否则就继续输入
读进程接收并显示写进程发送的内容,如果内容为"quit"也退出,
否则继续接收并显示
/*
strpdu.h
*/
#ifndef STRING_PDU_H
#define STRING_PDU_H
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define STRING_FIFO "/tmp/strfifo"
struct StringPDU//定义变长结构体
{
int len;
char buf[1];
};
int MyGetString(char arr[],int size);
#endif
/*
write.c
*/
#include "strpdu.h"
int MyGetString(char arr[],int size);//接收用户输入的size个字符
int main()
{
int ret = 0;
int fd = -1;
char buf[64] = "";
int len = 0;
struct StringPDU *pstPDU = NULL;
//检查管道文件是否存在,不存在时创建管道
ret = access(STRING_FIFO,F_OK);
if(ret < 0)
{
mkfifo(STRING_FIFO,0666);
}
//打开管道文件,打开失败后退出
fd = open(STRING_FIFO,O_WRONLY);
if(fd < 0)
{
perror("open fifo failed");
return 1;
}
//打开管道文件成功后继续操作
while(1)
{
//用户输入字符串数据
printf("Please input your content:\n");
MyGetString(buf,64);
//计算字符串长度
len = strlen(buf) + 1;
//申请存放变长结构体数据的堆空间
pstPDU = malloc(len + sizeof(int));
if(NULL == pstPDU)
{
perror("Malloc Failed");
break;
}
memset(pstPDU,0,len + sizeof(int));
//存放数据进变长结构体
pstPDU->len = len;
strcpy(pstPDU->buf,buf);
//把变长结构体的数据写入管道后释放结构体
ret = write(fd,pstPDU,len+sizeof(int));
free(pstPDU);
pstPDU = NULL;
if(ret != len + sizeof(int))
{
perror("write pdu error");
break;
}
//用户输入quit结束循环
if(strcmp(buf,"quit") == 0)
{
break;
}
}
//关闭管道
close(fd);
fd = -1;
return 0;
}
int MyGetString(char arr[],int size)
{
int len = 0;
fgets(arr,size,stdin);
len = strlen(arr);
if(arr[len-1] == '\n')
{
arr[len-1] = '\0';
}
else
{
while(getchar() != '\n')
{
}
}
return 0;
}
/*
read.c
*/
#include "strpdu.h"
struct StringPDU *RecviveStringPDU(int fd);
int main()
{
int ret = 0;
int fd = -1;//管道文件描述符
struct StringPDU *pstPDU = NULL;
//检查管道文件是否存在,不存在时创建管道文件
ret = access(STRING_FIFO,F_OK);
if(ret < 0)
{
mkfifo(STRING_FIFO,0666);
}
//打开管道文件,打开失败时退出
fd = open(STRING_FIFO,O_RDONLY);
if(fd < 0)
{
perror("open fifo failed");
return 1;
}
//打开管道文件成功时读取管道文件
while(1)
{
pstPDU = RecviveStringPDU(fd);
if(pstPDU == NULL)
{
break;
}
printf("The string is %s\n",pstPDU->buf);
if(strcmp(pstPDU->buf,"quit") == 0)
{
free(pstPDU);//释放结构体堆空间
pstPDU = NULL;
break;
}
free(pstPDU);
pstPDU = NULL;
}
//关闭管道
close(fd);
fd = -1;
return 0;
}
struct StringPDU *RecviveStringPDU(int fd)
{
int ret = 0;
int len = 0;
struct StringPDU *pstPDU = NULL;
//从管道读取4个字节的数据,即读取管道中的结构体的len
ret = read(fd,&len,sizeof(len));
if(ret <= 0)//读取数据失败
{
perror("read len error");
return NULL;
}
if(len <= 0)
{
return NULL;
}
//申请对空间存放结构体
pstPDU = (struct StringPDU *)malloc(len + sizeof(int));
if(NULL == pstPDU)//申请堆空间失败
{
perror("Malloc Failed");
return NULL;
}
memset(pstPDU,0,len + sizeof(int));
//向堆空间存放数据
pstPDU->len = len;
ret = read(fd,pstPDU->buf,len);
if(ret != len)//读取的数据有误
{
perror("read content error");
free(pstPDU);
return NULL;
}
return pstPDU;//返回堆空间的首地址
}
用户从write这进程写入字符串,quit退出
从read这进程显示用户输入的数据
写进程完成,接收用户命令行输入的整数count,
然后产生count个随机数(0~99),将count个随机数发送给读进程
如果count <= 0则退出,要求可重复操作
读进程接收写进程发送的随机数,用qsort将这些数字从小到大排序后输出
要求可重复操作,除非写进程关闭,必须使用变长结构体
//randpdu.h
#ifndef RAND_PDU_H
#define RAND_PDU_H
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct RandPDU
{
int count;
char buf[1];
};
#define RAND_FIFO "/tmp/randfifo"
#endif
//write.c
#include "randpdu.h"
struct RandPDU *CreateRandPDU(int count);
int InputCount();
int main()
{
int ret = 0;
int fd = -1;
int count = 0;
struct RandPDU *pstPDU = NULL;
srand(time(NULL));
//检查管道文件是否存在,不存在则创建管道文件
ret = access(RAND_FIFO,F_OK);
if(ret != 0)
{
ret = mkfifo(RAND_FIFO,0666);
if(ret < 0)
{
perror("mkfifo error");
return 1;
}
}
//打开管道文件,获得管道描述符
fd = open(RAND_FIFO,O_WRONLY);
if(fd < 0)
{
perror("open fifo error");
return 2;
}
//因为要求为可重复操作,用while(1)
while(1)
{ //用户输入数据
count = InputCount();
if(count <= 0)
{
printf("Your input error\n");
break;
}
//创建PDU
pstPDU = CreateRandPDU(count);
if(NULL == pstPDU)
{
break;
}
//发送PDU
ret = write(fd,pstPDU,(pstPDU->count+1)*sizeof(int));
//销毁PDU
free(pstPDU);
pstPDU = NULL;
if(ret != (count + 1) * sizeof(int))
{
perror("Send Rand Failed");
break;
}
}
//关闭管道描述符
close(fd);
fd = -1;
return 0;
}
int InputCount()
{
int n = 0;
printf("Please input a number:\n");
scanf("%d",&n);
while(getchar() != '\n')
{
}
return n;
}
struct RandPDU *CreateRandPDU(int count)
{
struct RandPDU *pstPDU = NULL;
int i = 0;
if(count <= 0)
{
printf("Count is invalid\n");
return NULL;
}
pstPDU = (struct RandPDU *)malloc((count + 1) * sizeof(int));
if(NULL == pstPDU)
{
perror("Malloc Failed");
return NULL;
}
memset(pstPDU,0,(count + 1) * sizeof(int));
pstPDU->count = count;
for(i = 0;i < count;i++)
{
*((int *)pstPDU->buf + i) = rand() % 1000;
}
return pstPDU;
}
//read.c
#include "randpdu.h"
int IntCmp(const void *pv1,const void *pv2);
struct RandPDU *ReceiveRandPDU(int fd);
int DisplayRands(int *pi,int count);
int main()
{
int ret = 0;
int fd = -1;
struct RandPDU *pstPDU = NULL;
srand(time(NULL));
//检查管道文件是否存在,不存在则创建管道文件
ret = access(RAND_FIFO,F_OK);
if(ret != 0)
{
ret = mkfifo(RAND_FIFO,0666);
if(ret < 0)
{
perror("mkfifo error");
return 1;
}
}
//打开管道文件,获得管道描述符
fd = open(RAND_FIFO,O_RDONLY);
if(fd < 0)
{
perror("open fifo error");
return 2;
}
while(1)
{ //接收PDU
pstPDU = ReceiveRandPDU(fd);
if(NULL == pstPDU)
{
break;
}
//排序
qsort(pstPDU->buf,pstPDU->count,sizeof(int),IntCmp);
//打印
DisplayRands((int *)pstPDU->buf,pstPDU->count);
//销毁PDU
free(pstPDU);
pstPDU = NULL;
}
//关闭管道描述符
close(fd);
fd = -1;
return 0;
}
int DisplayRands(int *pi,int count)
{
int i = 0;
for(i = 0;i < count;i++)
{
printf("%d ",*(pi+i));
}
printf("\n");
return 0;
}
struct RandPDU *ReceiveRandPDU(int fd)
{
struct RandPDU * pstPDU = NULL;
int count = 0;
int ret = 0;
//读取管道信息
ret = read(fd,&count,sizeof(int));
if(ret != sizeof(int))
{
perror("Receive Rand Count Failed");
return NULL;
}
if(count <= 0)
{
printf("The Rand Count %d is invalid\n",count);
return NULL;
}
pstPDU = (struct RandPDU *)malloc((count + 1) *sizeof(int));
if(NULL == pstPDU)
{
perror("Malloc Failed");
return NULL;
}
memset(pstPDU,0,(count + 1) * sizeof(int));
pstPDU->count = count;
ret = read(fd,pstPDU->buf,count * sizeof(int));
if(ret != count * sizeof(int))
{
perror("Receive Rand Data Failed");
free(pstPDU);
pstPDU = NULL;
return NULL;
}
return pstPDU;
}
int IntCmp(const void *pv1,const void *pv2)
{
return *((int *)pv1) - *((int *)pv2);
}
管道连通后从write端写入数字,在read端输出结果