Loading

进程间通讯-Pipe管道通讯

一、Pipe管道通讯基本概念

管道是一种最古老也是最基本的系统IPC形式,主要有两种:匿名管道(普通管道)和命名管道。

匿名管道(普通管道)

普通管道允许两个进程按标准的生产者-消费者方式进行通信:生产者向管道的一端(写入端)写,消费者从管道的另一端(读出端)读。

因此,普通管道是单向的,只允许单向通信。如果需要双向通信,那么就要采用两个管道,而每个管道向不同方向发送数据。通常情况下,父进程创建一个管道,并使用它来与其子进程进行通信。

命名管道

命名管道提供了一个更强大的通信工具。通信可以是双向的,并且父子关系不是必需的,当建立了一个有名管道后,多个进程都可用它通信。

二、命名管道的使用

例如,我们实现一个应用场景:客户端(进程A)通过管道请求服务端(进程B)后获取到数据。具体实现如下:

1、我们将客户端与服务端的共有方法抽象到基类PipeBase上:

 public class PipeBase
    {
        protected Encoding Encoding { get; set; } = Encoding.UTF8;
        protected StreamWriter PipeWriter { get; set; }
        protected StreamReader PipeReader { get; set; }
        protected const string Exit = nameof(Exit);
        protected void Send(string input)
        {
            string base64 = Convert.ToBase64String(Encoding.GetBytes(input));
            PipeWriter.WriteLine(base64);
        }

        protected string Receive()
        {
            string base64 = PipeReader.ReadLine();
            if (string.IsNullOrEmpty(base64)) return null;
            var bytes = Convert.FromBase64String(base64);
            var output = Encoding.GetString(bytes);
            if (output == Exit)
            {
                Environment.Exit(0);
            }
            return output;
        }
    }

2、编写Pipe管道客户端PipeClient类(需引入 System.Security.Principal命名空间):

  public class PipeClient: PipeBase
    {
        private NamedPipeClientStream Client { get; set; }
        public PipeClient (string serverName, string pipeName)
        {
            Client = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.None,
                    TokenImpersonationLevel.None);
            Client.Connect();
            PipeReader = new StreamReader(Client);
            PipeWriter = new StreamWriter(Client) { AutoFlush = true };
        }

        public string DoRequest(string input)
        {
            if (Client.IsConnected == false)
            {
                throw new Exception($"连接通过已断开");
            }
            Send(input);
            return Receive();
        }

        public void CloseServer()
        {
            Send(Exit);
        }
    }

3、编写Pipe管道客户端PipeServer类(需引入 System.Security.Principal命名空间):

 public class PipeServer : PipeBase
    {
        private NamedPipeServerStream Server { get; set; }
        public Action<string> Received { get; set; }
        public PipeServer(string pipeName)
        {
            Server = new NamedPipeServerStream(pipeName, PipeDirection.InOut, 1);
            Server.WaitForConnection();
            PipeReader = new StreamReader(Server);
            PipeWriter = new StreamWriter(Server) { AutoFlush = true };
            BeginReceive();
        }

        public new void Send(string input)
        {
            if (Server.IsConnected == false)
            {
                throw new Exception($"连接通过已断开");
            }
            base.Send(input);
        }

        private void BeginReceive()
        {
           new Thread(() =>
           {
               try
               {
                   while (true)
                   {
                       if (Server.IsConnected == false)
                       {
                           throw new Exception($"连接通过已断开");
                       }
                       string data = Receive();
                       Received?.Invoke(data);
                   }
               }
               catch (Exception e)
               {
                   Console.WriteLine(e);
               }
             
           }).Start();
        }
    }

4、编写测试用例

(1)编写服务端测试用例,编写一个控制台程序

   class Program
    {
        private static PipeServer Server { get; set; }
        static void Main(string[] args)
        {
            try
            {
                Server = new PipeServer("OEB");
                Console.WriteLine("连接成功");
                Server.Received += OnReceived;
                    Console.ReadKey();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Console.ReadKey();
            }
        }

        static void OnReceived(string message)
        {
            try
            {
                Console.WriteLine($"{DateTime.Now:HH:mm:ss:fff} 收到客户端请求:{message}");
                Server.Send($"Server Reply-{message}");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Environment.Exit(0);
            }
        }
    }

(2)编写客户端程序,创建一个WPF程序

  public partial class MainWindow : Window
    {
        public static readonly DependencyProperty InputProperty = DependencyProperty.Register("Input", typeof(string), typeof(MainWindow), new PropertyMetadata("test data"));
        public static readonly DependencyProperty OutputProperty = DependencyProperty.Register("Output", typeof(string), typeof(MainWindow), new PropertyMetadata(""));
        public string Output
        {
            get { return (string)GetValue(OutputProperty); }
            set { SetValue(OutputProperty, value); }
        }
        public string Input
        {
            get { return (string)GetValue(InputProperty); }
            set { SetValue(InputProperty, value); }
        }

       private PipeClient Client { get; set; }
        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;

            Closed += MainWindow_Closed;
        }

        private void MainWindow_Closed(object sender, EventArgs e)
        {
           Client.CloseServer();
        }

        private async void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            await Task.Run(() =>
            {
                Client = new PipeClient(".", "OEB");
                this.Dispatcher?.Invoke(() =>
                {
                    Input = "连接服务端成功...";
                });
            });
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            string input = Input;
            await Task.Run(() =>
            {
                try
                {
                    this.Dispatcher?.Invoke(() =>
                    {
                        Output += $"{DateTime.Now:HH:mm:ss:fff} 发送请求:{input}\r\n";
                    });
                    string output = Client.DoRequest(input); ;
                    this.Dispatcher?.Invoke(() =>
                    {
                        Output += $"{DateTime.Now:HH:mm:ss:fff} 收到服务端回复:{output}\r\n";
                    });
                }
                catch (Exception exception)
                {
                    MessageBox.Show($"断开连接:{exception.InnerException}");
                }
            });
        }
    }

(3)测试效果

image-20211230203335398

posted @ 2021-12-30 20:26  Dwaynerbing  阅读(428)  评论(0编辑  收藏  举报