没有从控制台发出命令的 C# 命名管道?

Posted

技术标签:

【中文标题】没有从控制台发出命令的 C# 命名管道?【英文标题】:C# Named Pipes without issuing commands from the Console? 【发布时间】:2012-05-03 13:04:16 【问题描述】:

我正在使用命名管道与进程通信。我已经能够使用以下代码使其工作。 (此处找到原始代码:via archive.org)

class ProgramPipeTest
    

        public void ThreadSenderStartClient(object obj)
        
            // Ensure that we only start the client after the server has created the pipe
            ManualResetEvent SyncClientServer = (ManualResetEvent)obj;

            using (NamedPipeClientStream pipeStream = new NamedPipeClientStream(".","ToSrvPipe",PipeDirection.Out,PipeOptions.None))
            
                // The connect function will indefinately wait for the pipe to become available
                // If that is not acceptable specify a maximum waiting time (in ms)
                pipeStream.Connect();

                Console.WriteLine("[Client] Pipe connection established");
                using (StreamWriter sw = new StreamWriter(pipeStream))
                
                    sw.AutoFlush = true;
                    string temp;
                    Console.WriteLine("Please type a message and press [Enter], or type 'quit' to exit the program");
                    while ((temp = Console.ReadLine()) != null)
                    
                        if (temp == "quit") break;
                        sw.WriteLine(temp);
                    
                
            
        

        public void ThreadStartReceiverClient(object obj)
        
            // Ensure that we only start the client after the server has created the pipe
            ManualResetEvent SyncClientServer = (ManualResetEvent)obj;

            using (NamedPipeClientStream pipeStream = new NamedPipeClientStream(".", "FromSrvPipe", PipeDirection.In, PipeOptions.None))
            
                // The connect function will indefinately wait for the pipe to become available
                // If that is not acceptable specify a maximum waiting time (in ms)
                pipeStream.Connect();

                Console.WriteLine("[ClientReceiver] Pipe connection established");

                using (StreamReader sr = new StreamReader(pipeStream))
                
                    // Display the read text to the console
                    string temp;
                    while ((temp = sr.ReadLine()) != null)
                    
                        Console.WriteLine("Received from server: 0", temp);
                    
                
            
        

        static void Main(string[] args)
        

            // To simplify debugging we are going to create just one process, and have two tasks
            // talk to each other. (Which is a bit like me sending an e-mail to my co-workers)

            ProgramPipeTest Client = new ProgramPipeTest();

            Thread ClientThread = new Thread(Client.ThreadSenderStartClient);
            Thread ReceivedThread = new Thread(Client.ThreadStartReceiverClient);

            ClientThread.Start();
            ReceivedThread.Start();


        
    

一切都按预期进行。我能够向我的目标进程(大胆)发出命令。

我的问题是,我基本上想在这段代码周围包装一个 C# GUI,但不知道如何修改它以便在无需使用控制台的情况下完成通信,因为命令将通过 GUI 或从代码。

我曾尝试将 streamWriter sw 转换为类变量,通过属性公开它并使用方法调用 sw.WriteLine(),但这似乎不起作用。

所以我不确定如何在对象中很好地来回封装流。

我发现这篇文章似乎就在上面,Using Named Pipes to Connect a GUI to a Console App in Windows,但不幸的是,它似乎没有附带任何代码,而且我没有任何参考。

那么如何在不使用控制台发出命令的情况下使用命名管道呢?

【问题讨论】:

【参考方案1】:

您要做的是从该代码中取出主要的逻辑部分,即发送者和接收者,并将其重写为一个可重用的类,该类可以像特定用途的包装类一样使用。

也许下面的代码可以作为指导(我没有检查这是否有效,它可能需要稍作改动)

public sealed class ResponseReceivedEventArgs : EventArgs

    public ResponseReceivedEventArgs(string id, string response)
    
        Id = id;
        Response = response;
    

    public string Id
    
        private set;
        get;
    

    public string Response
    
        private set;
        get;
    


public delegate void ResponseReceived(object sender, ResponseReceivedEventArgs e);

public sealed class NamedPipeCommands

    private readonly Queue<Tuple<string, string>> _queuedCommands = new Queue<Tuple<string,string>>();
    private string _currentId;

    private readonly Thread _sender;
    private readonly Thread _receiver;

    // Equivalent to receiving a "quit" on the console
    private bool _cancelRequested; 

    // To wait till a response is received for a request and THEN proceed
    private readonly AutoResetEvent _waitForResponse = new AutoResetEvent(false);

    // Lock to modify the command queue safely
    private readonly object _commandQueueLock = new object();

    // Raise an event when a response is received
    private void RaiseResponseReceived(string id, string message)
    
        if (ResponseReceived != null)
            ResponseReceived(this, new ResponseReceivedEventArgs(id, message));
    

    // Add a command to queue of outgoing commands
    // Returns the id of the enqueued command
    // So the user can relate it with the corresponding response
    public string EnqueueCommand(string command)
    
        var resultId = Guid.NewGuid().ToString();
        lock (_commandQueueLock)
        
            _queuedCommands.Enqueue(Tuple.Create(resultId, command));
        
        return resultId;
    

    // Constructor. Please pass in whatever parameters the two pipes need
    // The list below may be incomplete
    public NamedPipeCommands(string servername, string pipeName)
    
        _sender = new Thread(syncClientServer =>
        
            // Body of thread
            var waitForResponse = (AutoResetEvent)syncClientServer;

            using (var pipeStream = new NamedPipeClientStream(servername, pipeName, PipeDirection.Out, PipeOptions.None))
            
                pipeStream.Connect();

                using (var sw = new StreamWriter(pipeStream)  AutoFlush = true )
                    // Do this till Cancel() is called
                    while (!_cancelRequested)
                    
                        // No commands? Keep waiting
                        // This is a tight loop, perhaps a Thread.Yield or something?
                        if (_queuedCommands.Count == 0)
                            continue;

                        Tuple<string, string> _currentCommand = null;

                        // We're going to modify the command queue, lock it
                        lock (_commandQueueLock)
                            // Check to see if someone else stole our command
                            // before we got here
                            if (_queuedCommands.Count > 0)
                                _currentCommand = _queuedCommands.Dequeue();

                        // Was a command dequeued above?
                        if (_currentCommand != null)
                        
                            _currentId = _currentCommand.Item1;
                            sw.WriteLine(_currentCommand.Item2);

                            // Wait for the response to this command
                            waitForResponse.WaitOne();
                        
                    
            
        );

        _receiver = new Thread(syncClientServer =>
        
            var waitForResponse = (AutoResetEvent)syncClientServer;

            using (var pipeStream = new NamedPipeClientStream(servername, pipeName, PipeDirection.In, PipeOptions.None))
            
                pipeStream.Connect();

                using (var sr = new StreamReader(pipeStream))
                    // Do this till Cancel() is called
                    // Again, this is a tight loop, perhaps a Thread.Yield or something?
                    while (!_cancelRequested)
                        // If there's anything in the stream
                        if (!sr.EndOfStream)
                        
                            // Read it
                            var response = sr.ReadLine();
                            // Raise the event for processing
                            // Note that this event is being raised from the
                            // receiver thread and you can't access UI here
                            // You will need to Control.BeginInvoke or some such
                            RaiseResponseReceived(_currentId, response);

                            // Proceed with sending subsequent commands
                            waitForResponse.Set();
                        
            
        );
    

    public void Start()
    
        _sender.Start(_waitForResponse);
        _receiver.Start(_waitForResponse);
    

    public void Cancel()
    
        _cancelRequested = true;
    

    public event ResponseReceived ResponseReceived;

您可以看到我为 Console.ReadLine(命令队列)和 Console.WriteLine(事件)创建了抽象。 “quit”也是一个布尔变量,现在由“Cancel()”方法设置。显然,这不是最佳/正确的做法——我只是向您展示一种将上面的命令式代码关联到可以重用的包装类的方法。

【讨论】:

上帝保佑你。惊人的!这完美无缺。我所做的唯一更改是在构造函数中为另一个 pipeName 添加了一个附加参数,因为 To 和 From 管道的名称不同。 大声笑,我不确定它有多完整 - 但它旨在作为未来同样问题的指南。很高兴它成功了!

以上是关于没有从控制台发出命令的 C# 命名管道?的主要内容,如果未能解决你的问题,请参考以下文章

从 Windows 上的 C# Service Fabric 应用程序连接到 docker_engine(命名管道)

使用命名管道将图像从 C++ 发送到 C# 应用程序

命名管道 C# 服务器 C++ .dll 客户端不工作?

C#的命名管道(named pipe)

c#命名管道双向通信

如何将命名管道c#服务器连接到命名管道php客户端