C#中的命名管道用法
简介
管道为进程间通信提供了平台, 管道分为两种类型:匿名管道、命名管道,具体内容参考.NET 中的管道操作。简单来说,匿名管道只能用于本机的父子进程或线程之间,命名管道可用于远程主机或本地的任意两个进程,本文主要介绍命名管道的用法。
匿名管道在本地计算机上提供进程间通信。 与命名管道相比,虽然匿名管道需要的开销更少,但提供的服务有限。 匿名管道是单向的,不能通过网络使用。 仅支持一个服务器实例。 匿名管道可用于线程间通信,也可用于父进程和子进程之间的通信,因为管道句柄可以轻松传递给所创建的子进程。
命名管道在管道服务器和一个或多个管道客户端之间提供进程间通信。 命名管道可以是单向的,也可以是双向的。 它们支持基于消息的通信,并允许多个客户端使用相同的管道名称同时连接到服务器进程。 命名管道还支持模拟,这样连接进程就可以在远程服务器上使用自己的权限。
使用
命名管道的使用方法直接见Ngsoft.Pipe,项目的特点如下:
- 客户端和服务端采取请求响应的交互模式
- 可在服务端创建时传入请求数据处理程序的委托
- 每次请求结束,使用服务端、客户端就会丢弃,不存在线程安全的问题
- 因为服务端和客户端只使用一次,所以单次交互存在一定的性能损耗,大约20毫秒
示例
服务端示例:
var cts = new CancellationTokenSource();
Task.Factory.StartNew(() =>
{
var server = new PipeServer("test", messageHandler: m => $"{m ?? string.Empty}_received", Encoding.UTF8);
server.Run(cts.Token);
}, TaskCreationOptions.LongRunning);
Console.WriteLine("Server started. Press enter to stop it and exit.");
Console.ReadLine();
客户端示例:
while (true)
{
var client = new PipeClient("test", Encoding.UTF8);
Console.Write("Enter message: ");
var message = Console.ReadLine();
var response = await client.SendMessage(message);
Console.WriteLine(response);
}
源码
源码只有PipeBase.cs、PipeClient.cs、PipeServer.cs三个文件,代码比较简单就不放网盘了,直接贴代码。
PipeBase
using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
namespace Ngsoft.Pipe
{
public abstract class PipeBase
{
protected string PipeName { get; }
protected Encoding Encoding { get; }
public PipeBase(string pipeName, Encoding encoding)
{
PipeName = string.IsNullOrWhiteSpace(pipeName) == false ? pipeName : throw new ArgumentException("Pipe name cannot be empty.", nameof(pipeName));
Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
}
protected string ReadInput(PipeStream pipe)
{
var buffer = new byte[1024];
using (var stream = new MemoryStream())
{
do
{
var count = pipe.Read(buffer, offset: 0, count: buffer.Length);
stream.Write(buffer, offset: 0, count);
}
while (pipe.IsMessageComplete == false);
return Encoding.GetString(bytes: stream.ToArray());
}
}
protected void WriteOutput(PipeStream pipe, string output)
{
var data = Encoding.GetBytes(output);
pipe.Write(buffer: data, offset: 0, count: data.Length);
pipe.WaitForPipeDrain();
}
}
}
PipeClient
using System;
using System.IO.Pipes;
using System.Text;
using System.Threading.Tasks;
namespace Ngsoft.Pipe
{
public class PipeClient : PipeBase
{
public PipeClient(string pipeName, Encoding encoding) : base(pipeName, encoding) { }
public async Task<string> SendMessage(string message, int timeout = 10000)
{
if (string.IsNullOrWhiteSpace(message))
{
throw new ArgumentException("Message cannot be empty.", nameof(message));
}
if (timeout < 0)
{
throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout value cannot be negative.");
}
using (var client = new NamedPipeClientStream(serverName: ".", pipeName: PipeName, direction: PipeDirection.InOut))
{
client.Connect(timeout);
client.ReadMode = PipeTransmissionMode.Message;
await Task.Run(() =>
{
WriteOutput(client, message);
});
return await Task.Run(() =>
{
return ReadInput(client);
});
}
}
}
}
PipeServer
using System;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ngsoft.Pipe
{
public class PipeServer : PipeBase
{
private readonly Func<string, string> _messageHandler;
public PipeServer(string pipeName, Func<string, string> messageHandler, Encoding encoding) : base(pipeName, encoding)
{
_messageHandler = messageHandler ?? throw new ArgumentNullException(nameof(messageHandler));
}
public async Task Run(CancellationToken token)
{
if (token == null)
{
throw new ArgumentNullException(nameof(token));
}
while (token.IsCancellationRequested == false)
{
using (var server = new NamedPipeServerStream(pipeName: PipeName, direction: PipeDirection.InOut, maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances, transmissionMode: PipeTransmissionMode.Message))
{
server.WaitForConnection();
try
{
var input = await Task.Run(() =>
{
return ReadInput(server);
});
var output = _messageHandler.Invoke(input);
if (string.IsNullOrWhiteSpace(output))
{
throw new InvalidOperationException("Message handler result cannot be empty.");
}
await Task.Run(() =>
{
WriteOutput(server, output);
});
server.Disconnect();
}
catch
{
if (server.IsConnected)
{
server.Disconnect();
}
throw;
}
}
}
}
}
}