进程间通讯-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}");
}
});
}
}