c++ 使用管道读取子进程的输出 不完整 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
v2yllhwa
0.01D
V2EX    C++

c++ 使用管道读取子进程的输出 不完整

  •  
  •   v2yllhwa
    yllhwa 2021-01-30 13:50:45 +08:00 3133 次点击
    这是一个创建于 1782 天前的主题,其中的信息可能已经有所发展或是发生改变。

    需要读取 ffmpeg 解码的输出来做一个进度条。
    用 CreateProcess 来创建子进程,匿名管道重定向了 stdout 和 stderr,但是间歇性地会出现 strstr 找到了“Duration:”,但是此时 ReadBuff 里面就只有“Duration:”的情况。这种情况下再 ReadFile 也读不出来数据。

    有什么头绪吗?

    我的核心代码如下

     bRet = CreateProcess(NULL, (LPSTR)"ffmpeg.exe -i test.mkv output.mp4 -y", NULL, NULL, TRUE, NULL, NULL, NULL, &si, &pi); SetStdHandle(STD_OUTPUT_HANDLE, hTemp); CloseHandle(hWrite); while (ReadFile(hRead, ReadBuff, 1024, &ReadNum, NULL)) { ReadBuff[ReadNum] = '\0'; if (strstr(ReadBuff, "Duration:")) { for(int i=0;i<=10;i++) { putchar(*(strstr(ReadBuff, "Duration:")+10+i)); } putchar('\n'); } } 
    24 条回复    2021-02-01 00:18:55 +08:00
    lpts007
        1
    lpts007  
       2021-01-30 13:57:06 +08:00
    不懂,先问一下,if 没有 else 是认真的吗
    lpts007
        2
    lpts007  
       2021-01-30 13:59:05 +08:00
    是不是 Duration 被分到前边,剩下内容被读走进入并不存在的 else 分支 导致的?
    v2yllhwa
        3
    v2yllhwa  
    OP
       2021-01-30 14:03:02 +08:00 via Android
    @lpts007 ReadFile 是在 while 上面做的,相当于读一次处理一次。
    我的缓存已经开得很大了,应该不是被刚好切开的问题。
    linux40
        4
    linux40  
       2021-01-30 14:13:53 +08:00
    Duration 后面的内容应该是随时间变化的,或许不是单纯的字符,你最好先确认一下。
    alazysun
        5
    alazysun  
       2021-01-30 14:16:26 +08:00
    检查下 strlen(ReadBuff)和 ReadNum 长度区别?
    Mohanson
        6
    Mohanson  
       2021-01-30 14:26:46 +08:00 via Android
    Duration 并不一定会和它后面的数据被你一次读到 buf 里,甚至第一次 read 你只能读到 Durat, 第二次才会读到 ion

    要明白流这个概念(虽迟但到)。
    v2yllhwa
        7
    v2yllhwa  
    OP
       2021-01-30 14:29:32 +08:00
    @linux40 可以确定 Duration 只会出现在开头一次
    v2yllhwa
        8
    v2yllhwa  
    OP
       2021-01-30 14:34:05 +08:00
    @Mohanson 也就是说读的时候子进程可能还没有写完吗?但是我在后面加入 cout << ReadBuff << endl;后发现每次出现异常 ReadBuff 里面都是“ Duration:”。
    Mohanson
        9
    Mohanson  
       2021-01-30 14:44:02 +08:00
    @v2yllhwa 原因不是写没写完的问题, 而是为什么你会认为你 read() 一次正好能读取出 "Duration: xxxxxxx" 这一行数据? 为什么不会一次 read() 读出两行 "Duration: xxxxxxx", 为什么不会一次 read() 读出 "Durat"? 只是因为 xxxxxxx 后面有换行符吗?

    "流"就像水龙头, 它是连续不断的流出数据, 而不是每次流正好一杯子(一行输出)的数据.
    Mohanson
        10
    Mohanson  
       2021-01-30 14:48:41 +08:00
    当然从我的猜测讲, ffmpeg 在打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 第一次打印 Duration, 第二次才打印 xxxxxxxx, 能不能一次 read() 同时读到这部分数据全看运气.
    v2yllhwa
        11
    v2yllhwa  
    OP
       2021-01-30 14:54:30 +08:00
    @Mohanson 大概明白了,谢谢~ 决定改用 system 调用“start ffmpeg xxx 2>>test.txt”再从文件里面读数据了。但是多进程访问文件又是个坑 hhh
    yolee599
        12
    yolee599  
       2021-01-30 15:41:02 +08:00
    输出的数据不是一下子全部出来的,数据不符合要求不要丢,把它和新的数据拼接到一起。最好用环形缓冲区,一个进程负责执行转换程序并读取管道数据放到环形缓冲区里。另一个进程负责读取环形缓冲区数据,并实现字符串的拼接处理
    ipwx
        13
    ipwx  
       2021-01-30 17:41:16 +08:00
    其实,这个问题的本质,和所谓的 TCP 黏包是一回事。
    ipwx
        14
    ipwx  
       2021-01-30 17:42:45 +08:00
    @v2yllhwa 楼主你要不去查一下 TCP 黏包他们都是怎么处理的,你这边也就会怎么处理了。。。
    GuuJiang
        15
    GuuJiang  
       2021-01-30 17:56:28 +08:00
    @v2yllhwa 你以为你明白了,实际上并没有明白,如果你是等命令执行完才读的文件,那么做进度条就没有意义了,如果你是进程执行中去读的文件,那么读管道会面临的问题读文件一样会面临,甚至有可能更复杂
    GuuJiang
        16
    GuuJiang  
       2021-01-30 17:57:18 +08:00
    忘了说了,上面这条回复是针对你在 11 楼的补充
    no1xsyzy
        17
    no1xsyzy  
       2021-01-31 00:48:13 +08:00
    @Jirajine 关于你 /t/747735 #21 说的
    > 你读写文件、打 log 的时候怎么没遇到过“粘包”?
    如果 #6 的猜测正确的话,这不就来了么(
    v2yllhwa
        18
    v2yllhwa  
    OP
       2021-01-31 07:14:34 +08:00 via Android
    可能我还没有理解完全,但是实际操作起来的时候,发现总是能完整地读取到 ffmpeg 在标准错误流中最后一行(如果允许我用这个单位的话)的数据(也就是那一行'xxxxxx\r'的信息)。没有出现只读到一部分的情况,这是巧合吗?
    v2yllhwa
        19
    v2yllhwa  
    OP
       2021-01-31 07:27:57 +08:00 via Android
    貌似之前想错了,应该有某种特殊的保护模式可以保证每次读到的'包'是完整的(子进程一次写入的数据),但是就像
    @Mohanson #10 说的,大概打印 "Duration: xxxxxxx" 的时候用了两次系统调用, 也就是两个包,这两个包有可能读到第一个第二个还没进来
    whi147
        20
    whi147  
       2021-01-31 13:46:37 +08:00 via iPhone
    环形缓冲区方案会好很多,专门开一个线程 read 。然后 ui 线程每秒去读环形缓冲区数据就行了
    whi147
        21
    whi147  
       2021-01-31 13:47:58 +08:00 via iPhone
    还有你这个创建子进程方案还会遇到兼容性问题,最好用后缀是 ex 的函数
    ysc3839
        22
    ysc3839  
       2021-01-31 19:37:02 +08:00 via Android
    @whi147 CreateProcess 没有带 Ex 的版本,没有特殊需求的话用这个是没问题的。
    no1xsyzy
        23
    no1xsyzy  
       2021-01-31 21:23:56 +08:00
    @v2yllhwa 看了一下 man 7 pipe
    POSIX 规范中要求短于 PIPE_BUF 的 write 操作是原子的。(这个值要求至少 512,Linux 上是 4096 )
    v2yllhwa
        24
    v2yllhwa  
    OP
       2021-02-01 00:18:55 +08:00
    感谢诸位的帮助。因为是在 qt 里面写,本来想先把核心代码搞通顺再封装进去来着,今天晚上晕头转向用 QProcess 来实现居然完美解决了?!有时间我再深入研究下各位说的问题。
    现在的代码~ (比起之前 CreateProcess 既方便还更兼容)
    ```c++
    process.start(ffmpegPath,params);
    waitResult = process.waitForStarted();
    process.setReadChannel(QProcess::StandardError);
    ......
    bufferLength=process.readLine(buffer,1024);
    if(output.indexOf("Duration:")!=-1)
    {
    videoSecOnds= output.mid(12,2).toInt()*60*60+output.mid(15,2).toInt()*60+output.mid(18,2).toInt();
    }
    ```
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1020 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 18:45 PVG 02:45 LAX 10:45 JFK 13:45
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86