管道
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fcntl.h>
#include <sys/wait.h>
#include <errno.h>
#include <string.h>
/**
* 进程通信(IPC)
* 因为整个操作系统中,内核只有一块,所以采用内核共享缓冲区,实现进程通信
*
*
* 管道
* 作用与父子进程
* 本质是一个伪文件(内核缓冲区,大小4K,环形队列实现)
* 2个文件描述符引用,一个表示读端,一个表示写端
*
* 双向半双工
* 只能在有公共祖先进程间才能使用
*
* 读
* 管道有数据,返回数据
* 无数据
* 写端全部关闭,返回0,表示读到末尾
* 写端没关闭,则阻塞.
* 写
* 读端全部关闭,则进程异常终止(收到SIGPIPE信号)
* 没关闭
* 管道满了,则阻塞
* 没满,写入数据
*/
void test() {
int fd[2];
char *str = "hello World";
char buf[1024];
int ret = pipe(fd);
if (ret == 1) perror(" error");
pid_t pid = fork();
if (pid > 0) {
//关闭读端
close(fd[0]);
write(fd[1], str, strlen(str));
close(fd[1]);
} else if (pid == 0) {
//子进程关闭写端
close(fd[1]);
int size = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, size);
close(fd[0]);
}
}
void test1() {
int fd[2];
int ret = pipe(fd);
if (ret == 1) perror(" error");
pid_t pid = fork();
/* 这里之所以主进程负责读,然后执行命令
当主进程负责写,子进程负责读时
子进程会一直阻塞等待缓冲区数据.
这样就会造成父进程永远先于子进程结束,然后子进程会与bash进程争抢输出,从而乱序.
这里让父进程sleep也是无效的,因为exec写入数据是新开的一个新进程,无法控制它的同步输入缓冲区.
让父进程负责读操作就行了.*/
if (pid > 0) {
//关闭写端
close(fd[1]);
//将控制台输入数据指向缓冲区
//也就是当执行wc时,会读取控制台输入数据,这个时候控制台指向缓冲区
//因此读取的是缓冲区的数据
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
//这里不再执行close,因为缓冲区交由execlp处理了,只能期待内核隐式回收.
// close(fd[0]);
} else if (pid == 0) {
//子关闭读端
close(fd[0]);
//将控制台输出数据,写入缓冲区
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
}
}
void test2() {
int fd[2];
int ret = pipe(fd);
if (ret == 1) perror(" error");
int i;
for (i = 0; i < 2; i++) {
pid_t pid = fork();
if (pid == 0)break;
}
if (i == 0) {
//关闭写端
close(fd[0]);
dup2(fd[1], STDOUT_FILENO);
execlp("ls", "ls", NULL);
} else if (i == 1) {
close(fd[1]);
dup2(fd[0], STDIN_FILENO);
execlp("wc", "wc", "-l", NULL);
} else if (i == 2) {
//只能存在一个读端和写端
close(fd[0]);
close(fd[1]);
wait(NULL);
wait(NULL);
}
}
void test3() {
char *str1 = "hello World1";
char *str2 = "hello World2";
char buf[1024];
int fd[2];
int ret = pipe(fd);
if (ret == 1) perror(" error");
int i;
for (i = 0; i < 2; i++) {
pid_t pid = fork();
if (pid == 0)break;
}
if (i == 0) {
close(fd[0]);
write(fd[1], str1, strlen(str1));
close(fd[1]);
} else if (i == 1) {
close(fd[0]);
write(fd[1], str2, strlen(str2));
close(fd[1]);
} else if (i == 2) {
sleep(1);
close(fd[1]);
int size = read(fd[0], buf, sizeof(buf));
write(STDOUT_FILENO, buf, size);
close(fd[0]);
wait(NULL);
wait(NULL);
}
}
int main() {
/* 管道通信 */
// test();
//父子通信执行ls | wc -l
// test1();
//兄弟进程通信
// test2();
//多端写入,一端读取
// test3();
return 0;
}
有名管道
**file**
```c
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/fcntl.h>
/**
* 有名管道,可不同进程通信
* 数据只能读取一次
* linux下mkfifo命令
* @return
*/
int main() {
/* int fd = mkfifo("test2", 644); //创建一个有名管道
printf("111111111111 %d \n", fd);*/
char buf[1024];
int fd = open("E:\\workspace\\c\\cmake-build-debug\\test", O_WRONLY);
int i = 0;
while (1) {
sleep(3);
sprintf(buf, "hello %d \n", i++);
write(fd, buf, strlen(buf));
}
close(fd);
return 0;
}
file2
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/fcntl.h>
/**
*有名管道
* @return
*/
int main() {
char buf[1024];
int fd = open("E:\\workspace\\c\\cmake-build-debug\\test", O_RDONLY);
printf("11122222222222222211%d \n", fd);
while (1) {
sleep(3);
int size = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, size);
}
close(fd);
return 0;
}
文件通信
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/fcntl.h>
#include <string.h>
/**
* 文件通信
* @return
*/
int main() {
int te = 100;
int i;
pid_t pid;
for (i = 0; i < 2; i++) {
if ((pid = fork()) == 0)break;
}
if (i == 0) {
int fd = open("aa.txt", O_RDWR | O_TRUNC | O_CREAT,0664);
char *str = "hello \n";
write(fd, str, strlen(str));
sleep(4);
lseek(fd,0,SEEK_SET);
char buf[1024];
int ret = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
} else if (i == 1) {
sleep(2);
int fd = open("aa.txt", O_RDWR);
char buf[1024];
int ret = read(fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
char *str = "world \n";
write(fd, str, strlen(str));
} else {
sleep(10);
printf("我是父进程 %d \n", te);
}
return 0;
}
信号
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <sys/time.h>
#include <wait.h>
/**
* 信号
* 满足条件才能发送
* 每个进程收到的所有信号,都是内核负责发送的.
* 类似硬件中断
*
* 产生信号原因
* 按键产生,如CTRL+X
* 系统调用,如KILL,ABORT
* 软件产生,如定时器,sleep
* 硬件异常,1/0,内存对齐错误
* 命令产生,kill命令
*
* 递达:信号到达目标进程
* 未决:处于产生和递达中间状态,主要由于阻塞(屏蔽)导致
* 处理
* 1:执行默认动作
* 2:忽略
* 3:捕捉(调用用户处理函数)
*
* 阻塞信号集和未决信号集 位图形式 存储PCB中
* 阻塞信号集:将信号加入集和,对他们设置屏蔽,除非解除屏蔽才会执行此信号
* 未决信号集:信号产生后,未决信号集中描述该信号的位标记1,当信号处理后,重置0.
* 在信号产生后由于某些原因(如阻塞)不能抵达,则称之为未决信号.解除屏蔽前,一直处于未决状态.
*
* kill -l查看信号
* kill -信号编号 执行默认动作
* 信号不能忽略
* SIGSTOP
* SIGKILL
* 用户自定义信号
* SIGUSR1/USERR2
* 子进程状态变化通知父进程,默认动作忽略
* SIGCHLD信号产生条件
* 子进程终止时
* 子进程收到SIGSTOP信号停止时
* 子进程处于停止态,接收到SIGCONT后唤醒时
* 按键产生的信号
* SIGINT Ctrl+c 终止/中断
* SIGTSTP Ctrl+z 暂停/停止
* SIGQUIT Ctrl+\ 退出
* 硬件异常信号
* SIGFPE 浮点数错误,如除0
* SIGSEGV 段错误,非法访问内存
* SIGBUS 总线错误
*
*
*
* @return
*/
void killTest() {
pid_t pid = fork();
if (pid == 0) {
/*
给指定进程发送信号
pid>0,发送指定进程
=0,发送信号给与调用kill函数进程属于同一进程组的所有进程
<-1,取pid发送对应进程组
=-1,发送给进程有权限发送的系统中所有进程
*/
// kill(getppid(), SIGCHLD);
} else if (pid > 0) {
}
}
void myfunc(int sign) {
switch (sign) {
case SIGALRM:
printf("捕捉到了SIGALRM:%d \n", sign);
break;
}
}
/**
* 设置定时
* 以及捕捉信号,执行回调函数.
*/
void alarmTest() {
/* 一个进程只有一个定时器,存PCB中
* 延时多少秒发送SIGALRM信号,给当前执行进程
* 返回上次设置的延时到如今,还剩下多少秒数
*
* time 命令可查看程序运行时间
* 实际时间=用户时间+内核时间+等待时间
*/
alarm(1);
//捕捉信号,以及执行方法
// signal(SIGALRM, myfunc);
struct sigaction act, old;
//设置捕捉后回调方法
act.sa_handler = myfunc;
/** 捕捉函数执行期间,本信号自动屏蔽.
sa_mask跟程序阻塞信号集二者不可同日而语,samask生命周期只在回调函数执行期间,执行完后.会恢复阻塞信号集.
阻塞的常规信号不支持排队,当捕捉函数执行结束,恢复阻塞信号集后,多次执行的信号只会执行一次.(linux kill -l 手册中后32信号支持排队)*/
//设置捕捉函数执行期间,屏蔽的信号集
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGQUIT);
/**
* 中断系统调用:
* 系统调用分为2种,慢速系统和其他系统调用
* 慢系统调用
* 可能造成永远阻塞的情况,如:wait()或者write()read()...
* 这个时候执行中断后,当中断回调函数执行结束,应该返回到阻塞函数.
* 可设置flags=SA_RESTART
* sigaction:
* sa_flags:
* SA_RESTART:系统中断后重启
* SA_NODEFER:不屏蔽当前信号
*
* 其他系统调用:
* getPid,fork
*/
//设置0,表示回调执行期间,屏蔽捕捉的当前信号
//之所有这个参数,是为了避免,回调函数执行期间,捕捉了当前信号,然后递归.内存溢出.
act.sa_flags = 0;
int ret = sigaction(SIGALRM, &act, &old);
if (ret == -1) {
perror("error");
}
for (int i = 0;; ++i) {
sleep(1);
printf("%d \n", i);
}
}
int setitimerTest() {
struct itimerval it, oldit;
signal(SIGALRM, myfunc);
//第一次执行定时2秒钟,只有每次间隔5S执行一次定时器
//定时时长
it.it_value.tv_sec = 2;
it.it_value.tv_usec = 0;
//2次定时任务之间间隔时间
it.it_interval.tv_sec = 5;
it.it_interval.tv_usec = 0;
/*
* ITIMER_REAL 发送SIGLARM信号 计算自然时间
* ITIMER_VIRTUAL SIGVTALRM 计算进程占用CPU时间
ITIMER_PROF SIGPROF 计算占用CPU时间+系统调用时间
it:设置的定时时间
oldit:传出参数,记录上次剩余时间
*/
if (setitimer(ITIMER_REAL, &it, &oldit) == -1) {
perror("error");
return -1;
}
while (1);
}
/**
* 设置未决信号和阻塞信号集
*/
void sigset() {
sigset_t set, oldset, pedset;
//清空信号集
sigemptyset(&set);
//将一个信号添加到集合中
sigaddset(&set, SIGINT);
//设置信号屏蔽字
/*SIG_BLOCK:设置阻塞
/*SIG_UNBLOCK:设置不阻塞
/*SIG_SETMASK:自定义set替换mask
oldset:旧的mask
*/
int ret = sigprocmask(SIG_BLOCK, &set, &oldset);
if (ret == -1) {
perror("error");
}
while (1) {
sleep(1);
//查看未决信号集
ret = sigpending(&pedset);
if (ret == -1) {
perror("error");
}
//遍历打印
for (int i = 0; i < 32; i++) {
//判断一个信号是否在未决信号集合中
if (sigismember(&pedset, i)) {
putchar('1');
} else {
putchar('0');
}
}
printf("\n");
}
}
void myfunc2(int sign) {
pid_t pid;
//当同一个时间点,多个进程死了,发送信号.当回调函数执行结束后,只会执行一个.
// pid = wait(NULL);
//每次执行回收多个子进程
int status;
pid_t ip;
// while ((pid = wait(NULL)) != -1)
while ((ip = waitpid(-1, &status, WNOHANG)) != -1) {
if (WIFEXITED(status)) {
printf("子进程正常终止%d 返回的值为:%d \n", ip, WEXITSTATUS(status));
}
}
}
int clearChild() {
//设置阻塞
sigset_t set, oldset;
sigemptyset(&set);
sigaddset(&set, SIGCHLD);
int ret = sigprocmask(SIG_BLOCK, &set, &oldset);
if (ret == -1) {
perror("error");
}
pid_t pid;
int i;
for (i = 0; i < 5; i++) {
if ((pid = fork()) == 0)break;
}
if (5 == i) {
sleep(4);
struct sigaction act;
act.sa_handler = myfunc2;
sigemptyset(&act.sa_mask);
/**
*
*/
act.sa_flags = 0;
sigaction(SIGCHLD, &act, NULL);
//恢复阻塞
/* int ret = sigprocmask(SIG_SETMASK, &oldset, NULL);
if (ret == -1) {
perror("error");
}*/
sigprocmask(SIG_UNBLOCK, &set, NULL);
printf("我是父进程 \n");
//父进程先结束,注册的捕捉信号就清空了,这里让父进程不要结束.
while (1);
} else {
// sleep(1);
printf("我是子进程:%d \n", getpid());
return i;
}
}
int main() {
//操作kill函数
// killTest();
/* 设置定时
以及捕捉信号,执行回调函数.*/
// alarmTest();
//操作重复定时
// setitimerTest();
//设置未决信号和阻塞信号集
// sigset();
//回收子进程
return clearChild();
return 0;
}
MMAP
本地套接字
server.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/un.h>
#define PATH "serv.socket"
/**
* 本地套接字
* @return
*/
int main() {
int lfd, cfd;
int ret, len;
char buf[BUFSIZ];
struct sockaddr_un serv_addr, cliaddr;
/**
声明本地套接字 AF_UNIX/AF_LOCAL
*/
lfd = socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, PATH);
//算出serv_addr的具体占用长度
len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
unlink(PATH);
/**
* 这里的长度,传递的不是结构体的大小,而是结构体内容占用空间大小.
*/
bind(lfd, (struct sockaddr *) &serv_addr, len);
listen(lfd, 20);
printf("accept...\n");
while (1) {
//监听并处理连接
len = sizeof(cliaddr);
cfd = accept(lfd, (struct sockaddr *) &cliaddr, (socklen_t *) &len);
len -= offsetof(struct sockaddr_un, sun_path);
cliaddr.sun_path[len] = '\0';
printf("fileName %s \n", cliaddr.sun_path);
while ((ret = read(cfd, buf, sizeof(buf))) > 0) {
write(STDOUT_FILENO, buf, ret);
for (int i = 0; i < ret; ++i) {
buf[i] = toupper(buf[i]);
}
write(cfd, buf, ret);
}
close(cfd);
}
close(lfd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/un.h>
#define PATH "cli.socket"
#define SER_PATH "serv.socket"
int main() {
int cfd;
int ret, len;
char buf[BUFSIZ];
struct sockaddr_un clit_addr, serv_addr;
cfd = socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&clit_addr, sizeof(clit_addr));
clit_addr.sun_family = AF_UNIX;
strcpy(clit_addr.sun_path, PATH);
len = offsetof(struct sockaddr_un, sun_path) + strlen(clit_addr.sun_path);
//确保bind之前,套接字文件不存在.
unlink(PATH);
//这里不能依赖于隐式绑定,需要显示绑定,从而创建套接字文件
bind(cfd, (struct sockaddr *) &clit_addr, len);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SER_PATH);
len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
connect(cfd, (struct sockaddr *) &serv_addr, len);
while (1) {
write(cfd, "hello\n", 6);
sleep(1);
ret = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
}
close(cfd);
return 0;
}
如何选择?
1.管道:速度慢,容量有限,半双工,只有父子进程能通讯 ,FIFO,不能重复消费.
2.FIFO(命名管道):基本同上,但任何进程间都能通讯
3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题
4.信号量:不能传递复杂消息,只能用来同步
5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
6.socket:稳定,可靠,全双工,任何进程都可通信,不可重复消费
7.文件通信(舍弃):慢,可使用偏移量重复消费,需要处理同步问题.