我们之前一直用的是vim来编写代码,现在有了vscode这样强大的编辑器,我们可以把我们的vim放一边了,如果还有小伙伴还没有配置好vscode的远端,可以点击这里:
我们今天进入管道的学习:
在计算机领域,管道(Pipeline)是一种将多个命令连接在一起以形成数据流的机制。它允许一个命令的输出成为另一个命令的输入,从而实现命令之间的数据传递和处理。
在 Unix/Linux 系统中,管道通常用竖线符号 | 表示。通过管道,可以将一个命令的输出传递给另一个命令进行处理,从而构建复杂的数据处理流程。
例如,假设我们有两个命令 command1 和 command2,我们可以使用管道将它们连接起来:
command1 | command2
这将会把 command1 的输出作为 command2 的输入,command2 将处理 command1 的输出并生成最终的结果。
管道的优势包括:
简化复杂任务: 管道可以将多个简单的命令组合成一个复杂的任务,使得任务的实现更加简单和高效。
模块化和可重用性: 通过将命令连接在一起,可以更好地组织代码并提高代码的可重用性。每个命令都可以专注于完成一个特定的任务。
减少临时文件: 管道可以避免将数据存储到临时文件中,从而减少了文件 I/O 的开销和磁盘空间的占用。
实时处理: 管道允许命令之间的实时数据传递,这对于需要连续处理数据的任务非常有用,比如日志处理、数据流分析等。
简单来说,管道就是连接多个指令。我们之前也在频繁使用管道:比如我们想统计当前登录到系统的用户数量。
who指令的结果作为wc -l的输入。
我们这里讲的简单一点,现在我们有一个进程,它自身会被以读和写的方式分别打开一次:
然后这个读和写都会往一个缓冲区输入输出数据:
这个时候父进程创建子进程,子进程发生浅拷贝,指向没有发生变化:
这里注意一下,管道一般是单向的,所以我们现在想让父进程读,让子进程写:
这样形成了一个单向通道,这个就是一个基本的匿名管道。
匿名管道(Anonymous Pipe)是一种用于进程间通信的机制,特别是在 Unix 和类 Unix 系统中。它允许一个进程将输出发送到另一个进程的输入,从而实现进程间的数据传输。
以下是匿名管道的一些关键特点:
单向通信:匿名管道是单向的,只能支持单向数据流。它只能用于单一方向的通信,通常是父进程到子进程或者相反。
创建:匿名管道通过调用系统调用 pipe() 来创建。这个系统调用创建了一个管道,返回两个文件描述符,其中一个用于读取管道,另一个用于写入管道。
父子进程通信:通常,匿名管道用于父子进程之间的通信。在创建子进程后,父进程可以将数据写入管道,而子进程则可以从管道中读取这些数据。
半双工:匿名管道是半双工的,意味着数据只能在一个方向上流动。如果需要双向通信,则需要创建两个管道,或者使用其他的进程间通信机制,比如命名管道或套接字。
进程同步:匿名管道通常用于进程间的同步和协作。一个进程可能会阻塞在读取管道上,直到另一个进程写入数据到管道中为止。
匿名管道在 Unix 系统中被广泛应用,特别是在 shell 编程和进程间通信方面。它提供了一种简单而有效的方式,允许不同进程之间进行数据交换和协作
我也有专门创建管道的函数pipe:
我们可以来试一下:
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;
int main()
{
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
cout<<"pipefd[0]"<<"--->"<<pipefd[0]<<"pipefd[1]"<<"--->"<<pipefd[1]<<endl;
return 0;
}
运行:
这里我们发现pipefd[0]指代的是3,而我们的pipefd[1]指代的是4。其实也很好理解,因为0,1,2被标准输入,标准输出,标准错误占了。所以从3开始。
同时,如果我么查手册会看到这样一段话:
这段话的主要意思是pipefd[0]是读端,而pipefd[1]是写端。这为我们以后哪个开哪个关提供了依据。
我们先搭建架子来观察我们匿名管道的现象:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
}
if(id ==0)
{
//子进程要做的事
exit(0);
}
//父进程要做的事
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
现在我们想让子进程写,父进程读,我们把相应用不到的管道关闭:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们让子进程写入一些东西,然后让父进程来读,看看行不行:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(1);
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们看到父进程真的拿到了子进程写的东西,这就是一个最基本的管道的应用。
我们模拟一下,写端慢,读端快的情况
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(100); //模拟写端慢
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们发现父进程处于一个休眠的状态,很明显,它是在等待我们的子进程进行写入。
这里我们可以得出匿名管道具有同步机制,读端和写端是协同工作的。
我们调换一下,让写端快,读端快:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 10000;
while(cnt)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt--;
//向管道写
write(pipefd[1],message,strlen(message));
cout<<"writing......"<<endl;
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
执行:
过了2秒之后:
数据一瞬间出来了。
这里我们可以得出匿名管道是面向字节流的,它没有硬性规定我写一条你必须马上读一条,而是以字节流的形式读或写。
我们可以写一段代码来测试我们管道的大小:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(1)
{
// //缓冲区
// char message[MAX];
// //向缓冲区里写
// snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
// cnt--;
// //向管道写
// write(pipefd[1],message,strlen(message));
// cout<<"writing......"<<endl;
char c = 'a';
write(pipefd[1], &c, 1);
cnt++;
cout << "write ....: " << cnt << endl;
}
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
// sleep(2); //睡眠2秒
// ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
// if(n > 0)
// {
// cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
// }
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
我们发现最后结果是65536,折合下来也就是64kb左右的大小。
我们也可以用指令来查看管道大小:ulimit -a:
我们查看的管道大小为512 * 8 = 4kb,好像比我们看到的小。这个其实不是真正的大小。
我们现在让写段写一段时间后直接关闭,但是读端没有关闭:
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(1)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt++;
//向管道写
write(pipefd[1],message,strlen(message));
//跳出
if(cnt > 3) break;
// char c = 'a';
// write(pipefd[1], &c, 1);
// cnt++;
// cout << "write ....: " << cnt << endl;
}
//关闭写端
close(pipefd[1]);
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
//sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
cout<<"father return value:"<< n << endl;
sleep(1);
}
//回收子进程
pid_t rid = waitpid(id,nullptr,0);
if(rid == id)
{
cout<<"wait success"<<endl;
}
return 0;
}
这样表示:写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾。
同时注意,进程退出,管道自动关闭。
#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024
int main()
{
//建立管道
int pipefd[2] = {
0};
int n = pipe(pipefd);
assert(n == 0);
//创建子进程
pid_t id = fork();
//子进程
if(id < 0)
{
perror("fork fail");
return 1;
}
if(id ==0)
{
//子进程要做的事
close(pipefd[0]); //关闭读的通道
//向管道写入
int cnt = 0;
while(true)
{
//缓冲区
char message[MAX];
//向缓冲区里写
snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);
cnt++;
//向管道写
write(pipefd[1],message,strlen(message));
sleep(1);
//跳出
//if(cnt > 3) break;
// char c = 'a';
// write(pipefd[1], &c, 1);
// cnt++;
// cout << "write ....: " << cnt << endl;
sleep(1);
}
//关闭写端
//close(pipefd[1]);
exit(0);
}
//父进程要做的事
close(pipefd[1]); //关闭写的通道
//从管道中读取数据
char buffer[MAX];
while(true)
{
//sleep(2); //睡眠2秒
ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
if(n > 0)
{
cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
}
cout<<"father return value:"<< n << endl;
sleep(1);
//直接跳出
break;
}
//关闭读端
close(pipefd[0]);
sleep(5);
int status = 0;
pid_t rid = waitpid(id, &status, 0);
if (rid == id)
{
cout << "wait success, child exit sig: " << (status&0x7F) << endl;
}
// //回收子进程
// pid_t rid = waitpid(id,nullptr,0);
// if(rid == id)
// {
// cout<<"wait success"<<endl;
// }
return 0;
}
我们得到一下它的信号:
我们查一下13号信号:
13号信号是:SIGPIPE:
SIGPIPE 是在进程向一个已经被关闭的管道(或者其他的类似的通信方式)写入数据时,内核向该进程发送的信号。这个信号的默认行为是终止进程。
常见的场景是,一个进程向另一个进程通过管道发送数据,但接收数据的进程提前退出,导致写入数据的进程尝试往已关闭的管道写入数据。在这种情况下,内核会发送 SIGPIPE 信号给写入数据的进程,通知它目标进程已经退出,不再接收数据。
所以我们才有上述现象。
总结一下管道有4种情况:
管道的4种情况
- 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
- 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
- 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
- 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程
5种特性:
管道的5种特性
- 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
- 匿名管道,默认给读写端要提供同步机制 — 了解现象就行
- 面向字节流的 — 了解现象就行
- 管道的生命周期是随进程的
- 管道是单向通信的,半双工通信的一种特殊情况
文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr
文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc
文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8
文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束
文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求
文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname
文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立
文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码
文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词
文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限
文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定
文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland