【问题标题】:What can I use instead of a Unix pipe for inter-process communication on Windows?我可以用什么来代替 Unix 管道在 Windows 上进行进程间通信?
【发布时间】:2012-07-30 18:40:39
【问题描述】:

我有一个启动子进程的应用程序。子进程从标准输入读取要操作的文件。对于某些操作,它需要一个输入文件,其中包含有关如何处理它所操作的文件的信息——我们称之为“控制文件”。控制文件的名称也是从标准输入读取的。父应用程序可以使用临时文件作为控制文件,但我宁愿避免使用真实的磁盘支持文件。

在 Linux 上,这很简单:我可以创建一个 Unix 管道,fork,在启动子进程之前关闭管道的各个端,并使用/dev/fd/3(或任何文件描述符)作为控制文件名,并且然后将控制数据写入父应用程序中的管道。或者,我可以在/tmp(或其他)中使用命名管道。

如何在 Windows 上实现类似的功能?是否可以使用 Windows 提供的奇怪的“命名管道”,也就是说,它们可以被通常的 C 库 fread() 函数读取吗?如果是,我使用什么文件名来访问它们?或者有没有比使用命名管道更好的方法?

(子进程是exiftool command-line utility以批处理模式运行,所以我无法控制它。父应用程序是用Python编写的。)

【问题讨论】:

  • 恐怕不太可能工作;你可能需要一个真实的文件。如果涉及的磁盘 I/O 有问题,RAMdisk 软件可用于 Windows。
  • @HarryJohnston:磁盘 I/O 不是什么大问题。 exiftool所操作的文件通常是媒体文件,所以读写控制文件所涉及的I/O要比读写媒体文件所涉及的I/O相形见绌。从概念上讲,管道是我将在 Linux 上使用的,所以我希望在 Windows 上有一个等效的解决方案。如果不是这种情况,我将简单地使用临时文件解决方案。

标签: windows linux ipc pipe named-pipes


【解决方案1】:

更新:

@Harry Johnston 指出我误读了您的问题 - 您不想修改子流程。在这种情况下,您可以尝试拨打CreateProcess 并在hStdInput member of STARTUPINFO 中填写来自CreateNamedPipeHANDLE

上一个答案:

一般来说,Windows CRT(C 运行时,或 Unix 中的“libc”)是一个奇怪的野兽:它是 C 标准库的一个非常“准系统”的 shim,添加了一些额外的东西,不是很好维护而不暴露很多 Windows 可以做的事情。用 C 语言编写 Windows 软件最自然的方法是 Win32 API。也就是说:

Windows 提供的奇怪的“命名管道”能否用于此目的,也就是说,它们可以被常用的 C 库 fread() 函数读取吗?

是的,我相信您可以使用_open_osfhandle 做到这一点,其中第一个参数可以是HANDLE。这会给你一个整数,这是 Windows CRT 对 Unix 文件描述符的奇怪嘲弄。然后,您可以通过_fdopen 获得FILE*

如果是,我使用什么文件名来访问它们?

我想您可以尝试生成一个不会发生碰撞的随机事件。也许在它前面加上您的应用程序的名称,并采用进程 ID 和当前时间的某种组合?这只是我要扔掉的东西。

或者有没有比使用命名管道更好的方法?

您可以使用AF_UNIX 系列中的套接字,尽管它最终会非常相似...

【讨论】:

  • 使用 _open_osfhandle 需要 OP 修改子进程,他说他不能这样做。
  • @HarryJohnston 嗯,谢谢,我似乎错过了。我会更新答案。
  • 感谢您的回答。我不太明白hStdInput 成员如何帮助解决我的问题。请注意,我需要两者,通常的标准输入 控制文件与子进程通信的其他管道。子进程使用C库函数进行文件操作,我需要一个文件名传递给子进程。
【解决方案2】:

您可以使用 PowerShell 作为批处理子进程来创建命名管道,并使用它在 Windows 上的批处理和其他批处理/子系统之间构建 IPC。

这是一个使用快速着色批量输出的示例:PowerShell as a subprocess through named pipe

编辑:将其设置为消息模式并使其对@Bbb 更具可读性

EDIT2:添加缺少的 NOP 和安全警告。

注意安全问题。使用命名管道的凭据并注意 fd 5 可由服务器上的任何控制台/进程读取。使用真正的双向管道或其他命名管道而不是 fd 5 来避免这种情况。不要在野外使用这种“原样”。

通过 PowerShell 创建“双向”命名管道的示例批处理“代码”:

::
:: Launch a PowerShell child process in the background linked to the console and 
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
::   [ Name] : A name for the named pipe.
:: Return :
::   0 if the child PowerShell has been successfully launched and the named pipe is available.
::   1 if it fails.
::   3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
  SET LOCALV_NAME=
  IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
  IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
  powershell -command "$_" 2>&1 >NUL
  IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
  REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
  REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools. 
  REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous ! 
  (ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO   $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO   $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO   Try { & ^
ECHO     While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO       Try { & ^
ECHO         If ^^^($msg.Length -gt 0^^^) { & ^
ECHO           $disp='Server got :'+$msg; & ^
ECHO           Write-Host^^^($disp^^^); & ^
ECHO           $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO           $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO           $reply_flag=0; & ^
ECHO           While^^^($reply_flag -eq 0^^^) { & ^
ECHO             Try { & ^
ECHO               Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO               $reply_flag=1; & ^
ECHO             } Catch [System.IO.IOException] { & ^
ECHO               # Deal as you whish with potential errors & ^
ECHO             }; & ^
ECHO           }; & ^
ECHO         } & ^
ECHO       } Finally { & ^
ECHO         $npipeServer.Disconnect^^^(^^^); & ^
ECHO         $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO       } & ^
ECHO     }; & ^
ECHO   } Catch [System.IO.IOException] {  & ^
ECHO     # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO   } & ^
ECHO } Finally { & ^
ECHO   If ^^^($npipeServer^^^) { & ^
ECHO     $npipeServer.Dispose^^^(^^^); & ^
ECHO   } & ^
ECHO   # We close the fd & ^
ECHO   $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
  SET /A LOCALV_TRY=20 >NUL
  :LaunchPowerShellSubProcessPipe_WaitForPipe
  powershell -nop -c "& {sleep -m 50}"
  SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
  IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\\.\pipe\!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
  IF "!LOCALV_TRY!" == "0" EXIT /B 1
  REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
  powershell -nop -c "& {sleep -m 250}"
  EXIT /B 0

还有一个实现的例子:

@echo off
SETLOCAL ENABLEEXTENSIONS
IF ERRORLEVEL 1 (
  ECHO Can't use extensions
  EXIT /B 1
)
::
SETLOCAL ENABLEDELAYEDEXPANSION
IF ERRORLEVEL 1 (
  ECHO Can't use expansion 
  EXIT /B 1
)
REM We create 'MyNamedPipe'
CALL:LaunchPowerShellSubProcessPipe "MyNamedPipe"
SET "LOCALV_RET=!ERRORLEVEL!"
IF NOT "!LOCALV_RET!" == "0" (
  ECHO Failed to create the named pipe... Exit code from LaunchPowerShellSubProcessPipe : !LOCALV_RET!
  EXIT /B 1
)
REM Sending something through the pipe to the PowerShell subprocess that can be used as an IPC gate for other processes
ECHO Batch send hi to the PowerShell subprocess
ECHO Hi from the batch>\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
REM At this time, the PowerShell subprocess will take few cycles to write on the console and then write the response through fd 5.
REM We can check the pipe availability in the same way it's done in :LaunchPowerShellSubProcessPipe, but a direct read will suffice here. 
REM If we've read from the pipe, it should have ben a blocked read as we've created a synchronous pipe. 
REM As we read reply from fd 5, it's not synchronous, so we need to wait until we get the reply.
:WaitForReply
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply
ECHO caller is happy >\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply2
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a 2nd reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply2
REM Waiting one second for visible time delta
powershell -nop -c "& {sleep -m 1000}" 
ECHO This is my leave. >\\.\pipe\MyNamedPipe
REM A NOP equivalent to be sure pipe hand shake reach completion before any other comms and that nothing stay open from this CMD and the named pipe
ECHO[|SET /P=>NUL
:WaitForReply3
SET LOCALV_REPLY=0
FOR /F "tokens=*" %%R IN ('MORE ^<5') DO (
  ECHO Batch got a 3th reply from SubProcess : %%R
  SET LOCALV_REPLY=1
)
IF NOT "!LOCALV_REPLY!" == "1" GOTO :WaitForReply3
REM We can now tell goodbye to the PowerShell subprocess
ECHO QUIT>\\.\pipe\MyNamedPipe
EXIT /B 0
::
:: Launch a PowerShell child process in the background linked to the console and 
:: earing through named pipe which name provided as an argument of this function.
::
:: Parameters :
::   [ Name] : A name for the named pipe.
:: Return :
::   0 if the child PowerShell has been successfully launched and the named pipe is available.
::   1 if it fails.
::   3 if PowerShell is not present or doesn't work.
::
:LaunchPowerShellSubProcessPipe
  SET LOCALV_NAME=
  IF NOT "%~1" == "" SET "LOCALV_NAME=%~1"
  IF "%~1" == "" ( ECHO :LaunchPowerShellSubProcessPipe need a name.& EXIT /B 1)
  powershell -command "$_" 2>&1 >NUL
  IF NOT "!ERRORLEVEL!" == "0" EXIT /B 3
  REM As properly stated by WReach [https://mathematica.stackexchange.com/questions/198187/how-to-read-a-named-pipe-on-windows], Windows named pipe have their own hand shake logic (WaitNamedPipe... {humor}Microsoft hate us all probably{/humor}
  REM To "read" the named pipe from batch in a POSIX way, we redirected reply to fd 5. Reply to the output part of the named pipe can't be read from batch directly nor with common windows tools. 
  REM Do not use [System.IO.Pipes.PipeOptions]::Asynchronous ! 
  (ECHO $CloseHandle = Add-Type 'A' -PassThru -MemberDefinition '[DllImport^^^("kernel32.dll"^^^)] public static extern bool CloseHandle^^^(IntPtr hObject^^^);'; & ^
ECHO # In case of, we close the fd beforehand & ^
ECHO $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream^^^('%LOCALV_NAME%', [System.IO.Pipes.PipeDirection]::In, 1, [System.IO.Pipes.PipeTransmissionMode]::Message, [System.IO.Pipes.PipeOptions]::None, 256, 256^^^); & ^
ECHO Try { & ^
ECHO   $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO   $pipeReader = new-object System.IO.StreamReader^^^($npipeServer^^^); & ^
ECHO   Try { & ^
ECHO     While^^^(^^^($msg = $pipeReader.ReadLine^^^(^^^)^^^) -notmatch 'QUIT'^^^) { & ^
ECHO       Try { & ^
ECHO         If ^^^($msg.Length -gt 0^^^) { & ^
ECHO           $disp='Server got :'+$msg; & ^
ECHO           Write-Host^^^($disp^^^); & ^
ECHO           $curdte = Get-Date -Format 'HH:mm:ss'; & ^
ECHO           $reply='Reply to '+$msg+', im here, time is '+$curdte; & ^
ECHO           $reply_flag=0; & ^
ECHO           While^^^($reply_flag -eq 0^^^) { & ^
ECHO             Try { & ^
ECHO               Write-Output^^^($reply^^^) ^^^>5; & ^
ECHO               $reply_flag=1; & ^
ECHO             } Catch [System.IO.IOException] { & ^
ECHO               # Deal as you whish with potential errors & ^
ECHO             }; & ^
ECHO           }; & ^
ECHO         } & ^
ECHO       } Finally { & ^
ECHO         $npipeServer.Disconnect^^^(^^^); & ^
ECHO         $npipeServer.WaitForConnection^^^(^^^); & ^
ECHO       } & ^
ECHO     }; & ^
ECHO   } Catch [System.IO.IOException] {  & ^
ECHO     # Deal as you whish with potential errors, you will have InvalidOperation here when the streamReader is empty & ^
ECHO   } & ^
ECHO } Finally { & ^
ECHO   If ^^^($npipeServer^^^) { & ^
ECHO     $npipeServer.Dispose^^^(^^^); & ^
ECHO   } & ^
ECHO   # We close the fd & ^
ECHO   $Clean = $CloseHandle::CloseHandle^^^(5^^^); & ^
ECHO } & ^
ECHO exit & ^
ECHO[)| START /B powershell 2>NUL >NUL
  SET /A LOCALV_TRY=20 >NUL
  :LaunchPowerShellSubProcessPipe_WaitForPipe
  powershell -nop -c "& {sleep -m 50}"
  SET /A LOCALV_TRY=!LOCALV_TRY! - 1 >NUL
  IF NOT "!LOCALV_TRY!" == "0" cmd /C "ECHO[|SET /P=|MORE 1>\\.\pipe\!LOCALV_NAME!" 2>NUL || GOTO:LaunchPowerShellSubProcessPipe_WaitForPipe
  IF "!LOCALV_TRY!" == "0" EXIT /B 1
  REM 250 ms. PowerShell isn't the fastest thing to start when dealing with fd redirection... And we need it fully up to write to pipe and read from fd 5.
  powershell -nop -c "& {sleep -m 250}"
  EXIT /B 0

预期输出:

批量发送 hi 到 PowerShell 子进程 Batch 得到了 SubProcess 的回复 : 回复 Hi from the batch,我在这里,时间是 14:15:34 Batch 收到 SubProcess 的第二个回复:回复调用者很高兴,我在这里,时间是 14:15:34 Batch 收到了来自 SubProcess 的第三个回复:回复 This is my leave。 , 我在这里,时间是 14:15:35

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-09-10
    • 2013-01-24
    • 2011-02-09
    • 1970-01-01
    • 1970-01-01
    • 2011-01-17
    • 1970-01-01
    相关资源
    最近更新 更多