公众号:CppCoding
匿名管道
1.单工管道(程序进程与shell命令行进程单项通信)
打开管道
FILE* popen(const char* command,const char* open_mode)
参数 | 含义 |
---|
command | 命令行字符串 |
open_mode | “r"或"w”//只读或只写 |
读取
size_t fread(void *buffer,size_t size,size_t count,FILE *stream)
参数 | 含义 |
---|
buffer | 用于接收数据的内存地址 |
size | 读取每个数据项的字节数 |
count | 数据项个数 |
stream | 输入流 |
返回值 | 含义 |
---|
>count | 出错 |
正数 | 真实读取的数据项个数 |
写入
size_t fwrite(const void* buffer,size_t size,size_t count,FILE* stream)
参数 | 含义 |
---|
buffer | 用于接收数据的内存地址 |
size | 写入每个数据项的字节数 |
count | 数据项个数 |
stream | 目标文件指针 |
返回值 | 含义 |
---|
>count | 出错 |
正数 | 真实写入的数据项个数 |
关闭管道
int pclose(FILE* stream)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("wc","w");
char str[] = "123 456\n";
size_t n = fwrite(str,sizeof(char),sizeof(str),fd);
if(n > sizeof(str)){
fprintf(stderr,"FILE:%d,LINE:%d-fwrite error",__FILE__,__LINE__);
exit(EXIT_FAILURE);
}
pclose(fd);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fd = popen("ps -ef","r");
char buf[BUFSIZ];
size_t count = 0;
printf("read data:\n");
do{
memset(buf,'\0',BUFSIZ);
size_t n = fread(buf,sizeof(char),BUFSIZ-1,fd);
if( n > BUFSIZ - 1 ){
perror("fread error");
exit(EXIT_FAILURE);
}
count += n;
printf("\n%d:\n%s",n,buf);
}while(!feof(fd));
printf("total size:%ld\n",count);
pclose(fd);
}
半双工管道
创建管道
int pipe(int filedes[2])
参数 | 含义 |
---|
filedes[0] | 读 |
filedes[1] | 写 |
ssize_t write(int fd,const void * buf,size_t nbyte)
参数 | 含义 |
---|
fd | 文件描述符 |
buf | 写入数据的内存单元 |
nbyte | 写入文件制定的字节数 |
ssize_t read(int fd,void* buf,size_t count)
int fcntl(int fd,int cmd,long arg)
如果管道是空的,read()默认是阻塞
参数 | 含义 |
---|
fd | 文件描述符 |
cmd | F_GETFL:获取文件描述符状态;F_SETFL:设置文件描述符状态 |
arg | O_NONBLOCK:非阻塞;O_BLOCK:阻塞 |
把文件描述符设置为非阻塞的
fcntl(filedes,F_SETFL,O_NONBLOCK)
close(fd)
#include<stdio.h>
#include<stdlib.h>
#include<stdring.h>
int main(){
int fd[2];
pipe(fd);
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("write:%s\n",in);
char out[sizeof(in)]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("read:%s\n",out);
close(fd[0]);
close(fd[1]);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){
char in[] = "Hello pipe";
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
}else{
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
}
close(fd[0]);
close(fd[1]);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
int main(){
int fd[2];
pipe(fd);
if(!fork()){
close(fd[0]);
char in[] = "Hello pipe";
sleep(3);
write(fd[1],in,sizeof(in));
printf("child %d write:%s\n",getpid(),in);
close(fd[1]);
}else{
close(fd[1]);
fcntl(fd[0],F_SETFL,O_NONBLOCK);
char out[BUFSIZ]={0};
ssize_t n = read(fd[0],out,sizeof(out));
if(-1 == n){
perror("read error");
return -1;
}
printf("parent %d read:%s\n",getpid(),out);
close(fd[0]);
}
}
管道复制
分类 | 文件描述符 | 文件号 |
---|
标准输入 | STDIN_FILENO | 0 |
标准输出 | STDOUT_FILENO | 1 |
标准出错信息 | STDERR_FILENO | 2 |
内核为每个进程创建文件描述符,每复制一次,文件号一次递加
int dup(int oldfd)
int dup2(int oldfd,int newfd)
参数 | 含义 |
---|
oldfd | 旧文件描述符 |
newfd | 新文件描述符 |
新文件描述符与旧文件描述符不同,但是具备旧文件描述符功能
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
char str[]="Hello dup\n";
write(fd,str,sizeof(str));
int cp_fd = dup(fd);
printf("copy %d to %d",fd,cp_fd);
write(cp_fd,str,sizeof(str));
close(fd);
}
- 把文件描述符重定向(复制)到标准输出,并且输出后还原
注意把标准输出流从文件重定向(复制)回终端,需要清除缓冲区。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#define FILE_MODE (S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH)
int main(){
int save_fd = dup(STDOUT_FILENO);
int fd = open("./test",O_CREAT|O_RDWR,FILE_MODE);
if(-1 == dup2(fd,STDOUT_FILENO)){
perror("dup2 error0");
return 1;
}
close(fd);
printf("%d printf:Hello dup\n",fd);
fflush(stdout);
if(-1 == dup2(save_fd,STDOUT_FILENO)){
perror("dup2 error");
return 1;
}
close(save_fd);
printf("%d printf:this is save\n",save_fd);
}
FIFO管道/命名管道
FIFO和管道的区别:
管道只能在亲缘进程之间,FIFO可以在无亲缘关系的进程之间进行IPC
FIFO用mkfifo创建,之后用open打开。
int mkfifo(pathname,mode)
参数 | 含义 |
---|
pathname | 文件路径,文件必须不存在 |
mode | 模式 |
int open(const char *path,int mode)
模式 | 含义 |
---|
O_RDONLY | 阻塞只读 |
O_RDONLY | O_NONBLOCK | 非阻塞只读 |
O_WRONLY | 阻塞只写 |
O_WRONLY\NONBLOCK | 非阻塞只写 |
特点
在进行全双工的时候,在父进程首先打开(open)1管道的RDONLY时,然后打开2管道的WRONLY,在子进程打开(open)1管道的WRONLY和打开2管道的RDONLY,在此过程中,父进程和子进程的RD不能同时打开,否则该程序将一直阻塞。原因是目前没有任何进程打开FIFO来写,即打开该FIFO来读的进程将阻塞。这种现象称为死锁。
设置描述符的两种方式:
open(FIFO,O_RDONLY|O_NONBLOCK,0);
fcntl(fd,F_SETFL,O_NONBLOCK);
- 对管道或者FIFO进行close时,该管道或者FIFO的任何数据都会被丢弃
迭代服务器和并发服务器
迭代服务器是完全处理每个客户的请求后,在接待下一个客户。
并发服务器是每当有一个客户请求到达时,服务器就让主进程fork一个新的子进程。
拒绝服务型攻击
客户发送一个请求后,但是从来不打开自己的FIFO来读,这是拒绝服务型攻击。
解决办法:将服务器程序编写为并发型,这样上述的问题就只影响一个子进程,而不会影响主服务器。
OPEN_MAX:一个进程打开的最大文件描述符
PIPE_BUF:往管道或者FIFO写的最大数据量
可以使用ulimit查看