在Windows 10上NamedPipeServerStream无法工作(在Windows 7上可以工作)

8
似乎在Windows 10上NamedPipeServerStream无法工作。
我正在使用以下代码从我的C#应用程序中创建一个命名管道。这段代码是直接从MSDN示例复制过来的,因此应该是正确的,我想:
class Program
{
    static void Main(string[] args)
    {
        using (NamedPipeServerStream pipeServer =
                new NamedPipeServerStream("testpipe", PipeDirection.Out))
        {
            Console.WriteLine("NamedPipeServerStream object created.");
            Console.Write("Waiting for client connection... ");
            pipeServer.WaitForConnection();
            Console.WriteLine("Client connected.");
            try
            {
                using (StreamWriter sw = new StreamWriter(pipeServer))
                {
                    sw.AutoFlush = true;
                    sw.WriteLine("Hallo world!");
                    Console.WriteLine("Data was written.");
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("{0}: {1}", e.GetType().Name, e.Message);
            }
            Console.WriteLine("Pipe closed.");
        }
    }
}

现在,如果我运行这个程序,管道会被成功创建。但是,在Windows 10中,在终端尝试从管道读取数据每次都会立即失败,并显示"All pipe instances are busy"错误:

Microsoft Windows [Version 10.0.17134.228]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Users\MuldeR>type \\.\pipe\testpipe
All pipe instances are busy.

紧接着,"主"程序说管道已经断开。

令人困惑的是,完全相同的程序在Windows 7上可以正常工作:文本 "Hello world!" 可以通过终端从管道中读取(与上面完全相同的命令)。

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation. Alle Rechte vorbehalten.

C:\Users\testuser>type \\.\pipe\testpipe
Hallo!

我漏掉了什么???

谢谢!


背景:

我的目标是将字符串(密码)传递给命令行应用程序,但该程序不能够直接从命令行获取字符串。相反,命令行程序只能接收文件名,并从指定的文件中读取字符串。但我不想创建(临时的)“物理”文件,而是想通过命名管道传递字符串——这与Unix上使用mkfifo的方式类似。

(我不能够更改命令行程序)


你找到任何解决方案了吗?我有类似的问题。 - Barış Akkurt
2个回答

2

这篇文章非常长,覆盖了许多方面,但简单的答案在第一段中已经涵盖。

虽然不能保证结果,但测试(稍后展示)表明

type \\.\pipe\testpipe

打开管道两次,第二次打开失败,因为在class Program中创建的实例已经被使用。 CreateFile允许以所需访问级别0(不读取或写入)打开文件(包括管道等设备),以获取某些元数据,包括可能是否存在。如果type正在执行此操作,则无法用于访问(单个实例)命名管道。也许type的行为从Win7到Win10发生了变化。(有一天我会在Win7上运行以下内容。)
证明(好像是这样的):
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;

class makepipes
{
    const int   numserv = 8;

    static void Main(string[] args)
    {
    int i;
    Thread[] servers = new Thread[numserv];

    Console.Write("Waiting for client connection... ");
        for (i = 0; i < numserv; i++)
        {
            servers[i] = new Thread(ServerThread);
            servers[i].Start();
        }
        Thread.Sleep(250);
        while (i > 0)
        {
            for (int j = 0; j < numserv; j++)
            {
                if (servers[j] != null)
                {
                    if (servers[j].Join(250))
                    {
                        Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId);
                        servers[j] = null;
                        i--;    // decrement the thread watch count
                    }
                }
            }
        }
        Console.WriteLine("\nServer threads exhausted, exiting.");
    }

    private static void ServerThread(object data)
    {
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("testpipe", PipeDirection.Out, numserv);

        int threadId = Thread.CurrentThread.ManagedThreadId;

    Console.WriteLine("NamedPipeServerStream object created in {0}.", threadId);
        // Wait for a client to connect
        pipeServer.WaitForConnection();

        Console.WriteLine("Client connected on thread[{0}].", threadId);
            try
            {
                using (StreamWriter sw = new StreamWriter(pipeServer))
                {
                    sw.AutoFlush = true;
                    sw.WriteLine("Hallo world from {0}!", threadId);
                    Console.WriteLine("Data was written from {0}.", threadId);
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("Thread {2} failed {0}: {1}", e.GetType().Name, e.Message, threadId);
            }
            Console.WriteLine("Pipe closed in {0}.", threadId);
    }
}

给出以下结果(注意:必须在 cmd.exe 中使用 type,而 PowerShell 的 "type" 是 Get-Content 的别名,使用 FileStream 对象,无法直接访问设备或管道。请参见底部。)服务器输出由 S#...#S 限定,客户端由 C#...#C 限定。
S#######
Servers> makepipes
Waiting for client connection... NamedPipeServerStream object created in 3.
NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 9.
NamedPipeServerStream object created in 4.
#######S

C#######
Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[3].
Client connected on thread[10].
Data was written from 10.
Pipe closed in 10.
Server thread[10] finished.
Thread 3 failed IOException: Pipe is broken.
Pipe closed in 3.
Server thread[3] finished.
#######S

C#######
Hallo world from 10!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[8].
Client connected on thread[5].
Data was written from 5.
Pipe closed in 5.
Thread 8 failed IOException: Pipe is broken.
Pipe closed in 8.
Server thread[5] finished.
Server thread[8] finished.
#######S

C#######
Hallo world from 5!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[9].
Client connected on thread[6].
Data was written from 6.
Pipe closed in 6.
Server thread[6] finished.
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
#######S

C#######
Hallo world from 6!

Client>type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[7].
Client connected on thread[4].
Data was written from 4.
Pipe closed in 4.
Thread 7 failed IOException: Pipe is broken.
Pipe closed in 7.
Server thread[7] finished.
Server thread[4] finished.

Server threads exhausted, exiting.
#######S

C#######
Hallo world from 4!

Client>type \\.\pipe\testpipe
The system cannot find the file specified.
#######C

请注意最后一条响应。"All pipe instances are busy."的响应意味着在尝试第二次打开之前,type没有关闭第一个打开的管道(或者存在一些非常紧密的进程间定时问题)。 (这看起来就像是 ProcessMon 的工作!)
因此,这解释了为什么type表现出它所表现的方式(也许),但OP似乎只是在使用type进行测试。声明的意图是命令行应用程序将从提供管道名称的文件名参数中读取。因此,只要它只打开管道一次,它就应该可以工作。一些测试示例:
S#######
Servers> makepipes
Waiting for client connection... NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 4.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 3.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 9.
#######S

C#######
Client>more < \\.\pipe\testpipe
#######C

S#######
Client connected on thread[5].
Data was written from 5.
Pipe closed in 5.
Server thread[5] finished.
#######S

C#######
Hallo world from 5!

Client>more \\.\pipe\testpipe
#######C

S#######
Client connected on thread[7].
Data was written from 7.
Pipe closed in 7.
Server thread[7] finished.
#######S

C#######
Hallo world from 7!

Client>sort \\.\pipe\testpipe
#######C

S#######
Client connected on thread[6].
Data was written from 6.
Pipe closed in 6.
Server thread[6] finished.
#######S

C#######
Hallo world from 6!

Client>findstr world \\.\pipe\testpipe
#######C

S#######
Client connected on thread[4].
Client connected on thread[10].
Thread 10 failed IOException: Pipe is broken.
Pipe closed in 10.
Thread 4 failed IOException: Pipe is broken.
Pipe closed in 4.
Server thread[10] finished.
Server thread[4] finished.
#######S

C####### (no output from findstr)

Client>find "world" \\.\pipe\testpipe

---------- \\.\PIPE\TESTPIPE
#######C

S#######
Client connected on thread[3].
Data was written from 3.
Pipe closed in 3.
Server thread[3] finished.
#######S

C#######
Hallo world from 3!
Unable to read file

Client>copy \\.\pipe\testpipe con:
#######C

S#######
Client connected on thread[9].
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
Client connected on thread[8].
Data was written from 8.
Pipe closed in 8.
Server thread[8] finished.

Server threads exhausted, exiting.
#######S

C#######
Hallo world from 8!
The pipe has been ended.
        0 file(s) copied.
#######C

因此,findstrcopy都有双重打开。对于copy \\.\pipe\testpipe pipeit,响应为1 file(s) copied。(经过4次打开,其中3次失败),但是pipeit为空。 看起来find可以工作,但无法正确处理管道的写端关闭。请注意,more < \\.\pipe\testpipe可以正常工作。这意味着(已经测试为真)任何读取StdIn的命令行应用程序都可以连接到命名管道(使用cmd.exe)。即使findstr world < \\.\pipe\testpipe也可以工作,并且find "world" < \\.\pipe\testpipe没有抱怨。在cmd中,重定向可能也适用于将StdOut和StdErr发送到读取管道服务器(或多个服务器!),但尚未测试(有评论吗?)

最后确认,

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

public class oneopen {
    [DllImport("Kernel32.dll",SetLastError=true)]
    extern static IntPtr CreateFile(
        string  lpFileName,
        int dwDesiredAccess,
        int dwShareMode,
        IntPtr  lpSecurityAttributes,
        int dwCreationDisposition,
        int dwFlagsAndAttributes,
        IntPtr  hTemplateFile
    );

    [DllImport("Kernel32.dll",SetLastError=true)]
    static extern bool ReadFile(
        IntPtr handle,
        byte[] bytes,
        int numBytesToRead,
        out int numBytesRead,
        IntPtr overlapped_MustBeZero
    );

    [DllImport("Kernel32.dll",SetLastError=true)]
    static extern bool CloseHandle(IntPtr handle);

    public static int Main(string[] args)
    {
        IntPtr  fh, fh2;
        byte[]  bytes = new byte[1024];
        int red;

        if (args.Length > 0)
        {
            if (args.Length > 1)
            {
                fh2 = CreateFile(args[0], 1, 0, IntPtr.Zero, 3, 0, IntPtr.Zero);
                Console.WriteLine("{0} {1}\n", fh2.ToInt64(), Marshal.GetLastWin32Error());
                if (args.Length > 2)
                {
                    CloseHandle(fh2); // Don't care if fh == INVALID_HANDLE_VALUE
                    Thread.Sleep(Int32.Parse(args[2]));
                }
            }
            fh = CreateFile(args[0], 1, 3, IntPtr.Zero, 3, 0, IntPtr.Zero);
            Console.WriteLine("{0} {1}\n", fh.ToInt64(), Marshal.GetLastWin32Error());
        
            while (ReadFile(fh, bytes, bytes.Length, out red, IntPtr.Zero) && red > 0)
                Console.WriteLine(Encoding.ASCII.GetString(bytes, 0, red));
        }
        return Marshal.GetLastWin32Error();
    }
}

这允许测试管道上的单个打开,打开/关闭/(延迟)/打开和两个同时打开。各种测试显示了预期的结果,其中一个与OP相关的结果如下所示(使用原始的单实例管道)。

S#######
Server> makepipe
NamedPipeServerStream object created.
Waiting for client connection...#######S

C#######
Client> oneopen \\.\pipe\testpipe
#######C

                        S#######Client connected.
Data was written.
Pipe closed.
#######S

C#######
604 0

Hallo world!
#######C

我知道这有点太长了;但我想涵盖许多变体。

最后,与承诺的一样关于PowerShell,下面是获得的测试结果。

S#######
PS Server> .\makepipes
Waiting for client connection... NamedPipeServerStream object created in 6.
NamedPipeServerStream object created in 7.
NamedPipeServerStream object created in 5.
NamedPipeServerStream object created in 8.
NamedPipeServerStream object created in 9.
NamedPipeServerStream object created in 10.
NamedPipeServerStream object created in 4.
NamedPipeServerStream object created in 3.
#######S

C#######
PS Client> type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[6].
Client connected on thread[7].
Client connected on thread[10].
Client connected on thread[8].
Thread 8 failed IOException: Pipe is broken.
Pipe closed in 8.
Thread 10 failed IOException: Pipe is broken.
Pipe closed in 10.
Thread 6 failed IOException: Pipe is broken.
Pipe closed in 6.
Thread 7 failed IOException: Pipe is broken.
Pipe closed in 7.
Server thread[10] finished.
Server thread[6] finished.
Server thread[7] finished.
Server thread[8] finished.
#######S

C#######
type : FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:',
call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Content], NotSupportedException
    + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetContentCommand

PS Client> type \\.\pipe\testpipe
#######C

S#######
Client connected on thread[9].
Client connected on thread[3].
Thread 9 failed IOException: Pipe is broken.
Pipe closed in 9.
Server thread[9] finished.
Client connected on thread[4].
Thread 3 failed IOException: Pipe is broken.
Pipe closed in 3.
Client connected on thread[5].
Thread 5 failed IOException: Pipe is broken.
Pipe closed in 5.
Server thread[3] finished.
Thread 4 failed IOException: Pipe is broken.
Pipe closed in 4.
Server thread[4] finished.
Server thread[5] finished.

Server threads exhausted, exiting.
#######S

C#######
type : FileStream was asked to open a device that was not a file. For support for devices like 'com1:' or 'lpt1:',
call CreateFile, then use the FileStream constructors that take an OS handle as an IntPtr.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Get-Content], NotSupportedException
    + FullyQualifiedErrorId : System.NotSupportedException,Microsoft.PowerShell.Commands.GetContentCommand

PS Client> type \\.\pipe\testpipe
type : Cannot find path '\\.\pipe\testpipe' because it does not exist.
At line:1 char:1
+ type \\.\pipe\testpipe
+ ~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (\\.\pipe\testpipe:String) [Get-Content], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
#######C

因此,PowerShell不仅不喜欢从管道获取内容,而且在决定之前会打开(并关闭)它 4 次!!

-1

我们的软件与许多客户存在类似问题,似乎某些版本的Windows 10会破坏命名管道,非常令人沮丧。

MSDN文档现在说明如下:

Windows 10, version 1709:管道仅在应用程序容器内受支持;即从一个UWP进程到同一应用程序的另一个UWP进程中。此外,命名管道必须使用"\.\pipe\LOCAL\"语法作为管道名称。

这非常不清楚是什么意思...


1
这是我猜测的页面:https://learn.microsoft.com/zh-cn/windows/desktop/api/winbase/nf-winbase-getnamedpipeclientprocessid - Barış Akkurt
你曾经想明白过吗? - Digika
其他地方的观点认为,这个MSDN引用措辞不当,只适用于来自Microsoft商店的应用程序。我的答案似乎证实了这一点,因为命令行程序似乎没有特定的限制。 - Uber Kluger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接