命名管道常常用于应用程序之间的通迅,因为不需要进行序列化和反序列化操作,所以效率是非常高的,C#也提供了NamedPipeServerStream与NamedPipeClientStream两个类库用于进程间命名管道通讯,由此写两篇文章记录一下这两个类库的用法。本文章欲构建一个Server端从队列中取数据发送到客户端,文章的实际意义,一方面可以展示这两个类库的使用,另一方面可以对在实际项目中有一定的启示作用。
一、使用NamedPipeServerStream构造Server端
1、构造一个队列,每秒钟向队列中插入一条数据,代码如下:
static ConcurrentQueue<string> _pipeQueue = new ConcurrentQueue<string>();
static void Main(string[] args)
{
Task.Run(() => AddQueue());
}
static void AddQueue()
{
int i = 0;
while (true)
{
_pipeQueue.Enqueue($"test pipe{i}");
if (_pipeQueue.Count == 100)
{
string str = "";
_pipeQueue.TryDequeue(out str);
}
Thread.Sleep(1000);
i++;
}
}
实际项目中,我们可以将队列设置为单例队列,可是多个线程同时插入数据。
2、创建一个Server,代码如下:
static NamedPipeServerStream CreatPipeServer(string pipename)
{
NamedPipeServerStream pipeServer = new NamedPipeServerStream(pipename, PipeDirection.InOut, 1, PipeTransmissionMode.Message, PipeOptions.None);
return pipeServer;
}
使用NamedPipeServerStream构造Server,有几个参数值得注意,PipeDirection.InOut表示可以发送数据和接收数据,PipeTransmissionMode表示发送的方式有Message和byte两种,客户端接收数据时,对应的数据交互方式也要统一。
3、启动Server,等待客户端连接
在Main函数中循环创建Server,将队列中的数据发送出去,使用循环的目的是,在创建Server端之后,可能客户端连接中断或者其他异常都会导致Server终止,使用循环可以在Server终止时,可以重启Server,具体代码如下:
static void Main(string[] args)
{
Task.Run(() => AddQueue());
while (true)
{
using (NamedPipeServerStream pipeServer = CreatPipeServer("testpipe"))
{
try
{
pipeServer.WaitForConnection();
using (StreamWriter sw = new StreamWriter(pipeServer))
{
while (true)
{
sw.Flush();
string str = "";
_pipeQueue.TryDequeue(out str);
sw.WriteLine(str);
Console.WriteLine("send ok :", str);
}
}
}
catch (Exception)
{
break;
}
}
}
}
此时启动Server,运行到pipeServer.WaitForConnection();之后代码就会一直阻塞,直至有客户端连接,实际项目中在此处理的逻辑是,假如会有多个客户端时,那么当有客户端连接成功时,需要重新启动一个线程等待客户端连接。
二、使用NamedPipeCLientStream构造客户端。
1,创建Client
使用NamedPipeCLientStream构建Client,代码如下:
static NamedPipeClientStream CreatPipeClient(string servername, string pipename)
{
NamedPipeClientStream pipeClient = new NamedPipeClientStream(servername, pipename, PipeDirection.InOut, PipeOptions.None);
return pipeClient;
}
如上有两个重要的参数,一个Server的地址,一个管道的名称
2、使用While启动Client
使用While的原因也是因为可能因为Server终止等原因造成异常可以重新启动Client。代码如下:
static void Main(string[] args)
{
while (true)
{
using (NamedPipeClientStream pipeClient = CreatPipeClient(".", "testpipe"))
{
try
{
pipeClient.Connect();
using (StreamReader sr = new StreamReader(pipeClient))
{
string str;
while ((str = sr.ReadLine()) != null)
{
Console.WriteLine($"Received Message: {str}");
Thread.Sleep(1000);
}
}
}
catch (Exception)
{
continue;
}
}
}
}
如上代码有个特别需要注意的一行: pipeClient.Connect();这句是连接Server,假如没有这句Server依旧会阻塞在等待客户端连接。
注意的是,StreamReader 类的Read(char[] buffer, int index, int count)方法,这个方法读取固定长度的字节,假如在Server发送的byte,那么客户端接收时无法判断一行结束,可使用该方法读取数据,如每次读取100字节,而buffer的长度是以实际数据的长度,在此需要注意的是发送端发送的数据的长度是否超过设定的固定长度。
另外需要注意的是,并不是Server端发送一条数据,Client就读一条数据,例如上例中Server一秒发送一条数据,假如Client不设置延迟一秒,将会返回null,所以在实际项目中需要考虑数据交互的频率,不考虑将会遇到两个问题,一个是读取到null数据,一个消耗大量CPU。