在看过高级的popen函数之后,我们再来看看底层的pipe函数。通过这个函数在两个程序之间传递数据不需要启动一个shell来解释请求的命令。它同时还提供了对读写数据的更多控制。pipe函数的原型如下所示:
#include <unistd.h>
int pipe(int pipefd[2]);
pipe函数的参数是一个由两个整数类型的文件描述符组成的数组的指针。该函数在数组中填上两个新的文件描述符后返回0,如果失败则返回-1并设置errno来表明失败的原因。在Linux手册页(手册的第二部分)中定义了下面一些错误。
❑ EMFILE:进程使用的文件描述符过多。
❑ ENFILE:系统的文件表已满。
❑ EFAULT:文件描述符无效。
两个返回的文件描述符以一种特殊的方式连接起来。写到file_descriptor[1]的所有数据都可以从file_descriptor[0]读回来。数据基于先进先出的原则(通常简写为FIFO)进行处理,这意味着如果你把字节1,2,3写到file_descriptor[1],从file_descriptor[0]读取到的数据也会是1, 2,3。这与栈的处理方式不同,栈采用后进先出的原则,通常简写为LIFO。
特别要注意,这里使用的是文件描述符而不是文件流,所以我们必须用底层的read和write调用来访问数据,而不是用文件流库函数fread和fwrite。
下面的程序pipe1.c用pipe函数来创建一个管道。
实验pipe函数
注意file_pipes数组的用法,它的地址被当作参数传递给pipe函数。
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
void test_01(void){
int len = 0;
int fds_pipes[2];
const char some_data[] = "123";
const char some_data2[] = "456";
char buf[BUFSIZ + 1];
memset(buf,0,sizeof(buf));
if(pipe(fds_pipes) == 0){
len = write(fds_pipes[1],some_data,strlen(some_data));
DEBUG_INFO("write len = %d",len);
len = read(fds_pipes[0],buf,BUFSIZ);
DEBUG_INFO("read len = %d,buf = %s",len,buf);
memset(buf,0,sizeof(buf));
len = write(fds_pipes[0],some_data2,strlen(some_data2));
DEBUG_INFO("write len = %d",len);
len = read(fds_pipes[1],buf,BUFSIZ);
DEBUG_INFO("read len = %d,buf = %s",len,buf);
}else{
perror("pipe:");
}
}
int main(int argc, char**argv){
test_01();
DEBUG_INFO("hello world");
return 0;
}
测试结果:
实验解析
这个程序用数组file_pipes[]中的两个文件描述符创建一个管道。然后它用文件描述符file_pipes[1]向管道中写数据,再从file_pipes[0]读回数据。注意,管道有一些内置的缓存区,它在write和read调用之间保存数据。
如果你尝试用file_descriptor[0]写数据或用file_descriptor[1]读数据,其后果并未在文档中明确定义,所以其行为可能会非常奇怪,并且随着系统的不同,其行为可能会发生变化。在我的系统上,这样的调用将失败并返回-1,这至少能够说明这种错误比较容易发现。
乍看起来,这个使用管道的例子并无特别之处,它做的工作也可以用一个简单的文件完成。管道的真正优势体现在,当你想在两个进程之间传递数据的时候。当程序用fork调用创建新进程时,原先打开的文件描述符仍将保持打开状态。如果在原先的进程中创建一个管道,然后再调用fork创建新进程,我们即可通过管道在两个进程之间传递数据。
实验 跨越fork调用的管道
(1)下面这个程序pipe2.c的开始部分(在调用fork之前的部分)和第一个例子非常相似。
测试代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define DEBUG_INFO(format, ...) printf("%s - %d - %s :: "format"\n",__FILE__,__LINE__,__func__ ,##__VA_ARGS__)
void test_01(void){
int len = 0;
int fds_pipes[2];
const char some_data[] = "123";
const char some_data2[] = "456";
pid_t pid;
int res;
char buf[BUFSIZ + 1];
memset(buf,0,sizeof(buf));
res = pipe(fds_pipes);
if(res != 0){
perror("pipe:");
return;
}
pid = fork();
if(pid < 0){
perror("fork:");
return ;
}
if(pid == 0){
len = read(fds_pipes[0],buf,BUFSIZ);
DEBUG_INFO("CHILD:pid = %lu,read -- len = %d,buf = %s\n",getpid(),len,buf);
len = write(fds_pipes[1],some_data2,BUFSIZ);
DEBUG_INFO("CHILD:pid = %lu,write -- len = %d\n",getpid(),len);
sleep(1);
return;
}
len = write(fds_pipes[1],some_data,BUFSIZ);
DEBUG_INFO("PARENT:pid = %lu,write -- len = %d\n",getpid(),len);
len = read(fds_pipes[0],buf,BUFSIZ);
DEBUG_INFO("PARENT:pid = %lu,read -- len = %d,buf = %s\n",getpid(),len,buf);
sleep(1);
}
int main(int argc, char**argv){
test_01();
DEBUG_INFO("hello world");
return 0;
}
测试结果:
在本测试代码中实现了双向通信。
实验解析
这个程序首先用pipe调用创建一个管道,接着用fork调用创建一个新进程。如果fork调用成功,父进程就写数据到管道中,然后父进程开发等待从管道中读数据,子进程从管道中读取数据后,向管道中写数据,然后父进程读到数据。父进程和子进程写数据的时候都使用的文件描述符fds_pipes[1],读数据的时候都使用的文件描述符fds_pipes[0]
CMakeLists.tx 、编译脚本和sftp.json
cmake_minimum_required(VERSION 3.8)
project(myapp)
# add_compile_options("-std=c++11")
add_executable(popen popen.c)
add_executable(popen2 popen2.c)
add_executable(popen3 popen3.c)
add_executable(popen4 popen4.c)
add_executable(pipe1 pipe1.c)
add_executable(pipe2 pipe2.c)
rm -rf _build_
mkdir _build_ -p
cmake -S ./ -B _build_
make -C _build_
# ./_build_/popen
# ./_build_/popen2
# ./_build_/popen3
# ./_build_/popen4
# ./_build_/pipe1
./_build_/pipe2
sftp.json
{
"name": "My Server",
"host": "192.168.31.138",
"protocol": "sftp",
"port": 22,
"username": "lkmao",
"password": "lkmao",
"remotePath": "/big/work/ipc",
"uploadOnSave": true,
"useTempFile": false,
"openSsh": false
}