1、共享内存
共享内存,实际上C#中可以有很多种实现方式,主要是借助于Win32的Api来实现以及,使用MemoryMappedFile这个类来实现共享内存,前者需要引入多个Win32的dll的方法,后者使用起来就比较简单,只需要调用类的CreatNew方法设置好内存映射文件名称以及大小,以及操作权限就可以实现,同时支持Accessor和Stream的方式去进行读写,但是性能方面肯定是Win32的性能好,而且Win32的话不受语言的限制,至于这个类是否受限于语言,目前我是不太清楚的。
接下来,咱们就看看客户端和服务端使用共享内存的方式和获取数据的代码。
服务端
MemoryMappedFile memoryAccessor = MemoryMappedFile.CreateNew("ProcessCommunicationAccessor", 500, MemoryMappedFileAccess.ReadWrite);
using (var accessor = memoryAccessor.CreateViewAccessor())//获取映射文件对象的视图
{
var helo = Encoding.UTF8.GetBytes("Accessor");
accessor.WriteArray(0, helo, 0, helo.Length);//将给定的值写入此视图中
richTextBox1.Text += Environment.NewLine + "Accessor Send Val:Accessor";
}
MemoryMappedFile memoryStream = MemoryMappedFile.CreateNew("ProcessCommunicationStream", 500, MemoryMappedFileAccess.ReadWrite);
using (var stream = memoryStream.CreateViewStream())//获取映射文件对象的视图
{
byte[] helo1 = Encoding.UTF8.GetBytes("Stream");
int len = helo1.Length;
stream.Write(helo1, 0, len);//将给定的值写入此内存流中
richTextBox1.Text += Environment.NewLine + "Accessor Send Val:Stream";
}
客户端
MemoryMappedFile memoryAccessor = MemoryMappedFile.OpenExisting("ProcessCommunicationAccessor");
using (var accessor = memoryAccessor.CreateViewAccessor())
{
var s = new byte[999];
var read = accessor.ReadArray(0, s, 0, s.Length);
var str = Encoding.UTF8.GetString(s);
richTextBox1.Text += Environment.NewLine + "Accessor Read Val:" + str.ToString();
}
MemoryMappedFile memoryStream = MemoryMappedFile.OpenExisting("ProcessCommunicationStream");
using (var stream = memoryStream.CreateViewStream())
{
using (var reader = new StreamReader(stream))
{
var str = reader.ReadToEnd();
richTextBox1.Text += Environment.NewLine + "Stream Read Val:" + str + "\r\n";
}
}
可以看到我们在服务端定义了一个是Accessor类型的MemoryMappedFile在写入数据的时候是用MemortViewAccessor的方式去写入的,然后又定义了一个使用Stream的方式去进行写入数据,在客户端中,我们直接使用OpenExisting方法去判断是否存在这个对象,如果存在的话,就使用了服务端定义的CreatNew这个对象,如果不存在则是Null,当然了也可以使用其他的方式去进行获取,例如CreateOrOpen判断是否是获取的还是重新创建的方式,我们在客户端使用ReadArray和ReadToEnd的方式读取了服务端写入的Accessor和Stream的数据,然后我们就可以在客户端和服务端之间进行一个数据传输的一个通讯。
2、Windows的MSMQ
使用MSMQ的前提是需要在本计算机安装了消息队列,安装方式需要在控制面板,程序和功能那里启用或关闭程序,在列表中找到我们需要的消息队列(MSMQ)服务器然后安装,安装完成后,我们点击我的电脑右键管理找到最下面的服务和应用程序就可以看到我们安装的消息队列了,然后找到专用队列,我们在这里新建一个队列,然后就可以在我们的代码中使用了,这里呢我只是简单写一个示范,实际上在Messaging命名空间里,还支持对消息队列权限的控制,等等的操作,接下来我们看看如何在代码中使用消息队列。
服务端中我们定义了我们需要使用的消息队列的类型以及名称,名称规范的话也可以参考官网对名称定义的介绍,还支持其他方式名称的定义,定义好之后呢,我们便发送了一个消息Message HelloWorld的一条消息
MessageQueue queue = new MessageQueue(".\\Private$\\MessageQueue");//右键我的电脑,点击管理 找到服务和应用程序找到专用队列,创建的专用队列名称就是MessageQueue
queue.Send("Message HelloWorld");//然后发送消息
richTextBox1.Text += Environment.NewLine + "MessageQueue Send Val:Message HelloWorld";
客户端中,我们也是和服务端定义了一个消息队列的一个对象,然后我们监听这个消息队列的收到消息的事件,开始异步接收消息,在接收完毕之后呢,会走到我们写的ReceiveCompleted的完成事件中,然后我们结束异步接收的,获取到服务端发送的消息,然后使用XmlMessageFormatter对象去格式化我们服务端发送的消息,这里的Type是服务端发送的消息类型,两者需要对应,在接受并展示到UI之后,我们在开始异步接收。
var context = WindowsFormsSynchronizationContext.Current;
MessageQueue myQueue = new MessageQueue(".\\Private$\\MessageQueue");//定义消息队列对象,和服务端的地址一样,
myQueue.ReceiveCompleted += (a, b) =>//定义接受完成的时间
{
var cts = context;
var queue = a as MessageQueue;//队列对象
queue.EndReceive(b.AsyncResult);
var msg = b.Message;//接收到的消息对象
msg.Formatter = new XmlMessageFormatter() { TargetTypes = new Type[] { typeof(string) } };//设置接收到的消息使用什么方式格式化
var msgVal = msg.Body;//此处是服务端发送的具体的消息对象
cts.Send(new System.Threading.SendOrPostCallback(s =>
{
richTextBox1.AppendText("MessageQueue Read Val:" + msgVal);
richTextBox1.HideSelection = false;
richTextBox1.AppendText("\r\n");
}), null);
queue.BeginReceive();
};
myQueue.BeginReceive();
3、命名管道
命名管道和匿名管道位于System.Io.Pipe命名空间下,顾名思义,命名管道是需要我们给管道命名一个名称的以便于客户端来进行连接,我们需要定义管道的名称,指定管道的方向,是输入还是输出 还是输入输出,还可以定义最大的服务端实例数量,以及传输的消息类型是Byte还是Message,以及是否开启异步等。接下来我们看看服务端和客户端之间通讯的代码。
服务端
我们定义了管道名称是ProcessCommunicationPipe,并且定义是可以输入也可以输出,10个实例,以及使用Message传输类型,开启异步通讯,然后我们异步的等待客户端链接,在链接成功之后呢,我们通知UI客户端已经链接到了服务端,然后异步去接收客户端发来的消息,并且展示到UI上面。
//异步等待客户端链接,如果上面的Options不是Asynchronous 异步则会报错
namedPipeServerStream.WaitForConnectionAsync().ContinueWith(s =>
{
var cts = SynchronizationContext.Current;
//刷新UI 告知有客户端链接
Invoke(new MethodInvoker(() => {
richTextBox1.Text += Environment.NewLine + "Client Is Connected;";
}));
var valByte = new byte[1024];
//异步读取客户端发送的消息
namedPipeServerStream.ReadAsync(valByte, 0, valByte.Length).ContinueWith(m =>
{
var val = valByte;
var str = Encoding.UTF8.GetString(val);
Invoke(new MethodInvoker(() => {
richTextBox1.Text += Environment.NewLine + "Server Receive Val:" + str;
}));
});
});
服务端发送代码:我们定义了一个Send的发送按钮,以及一个发送内容的文本框,然后我们只需要调用Server的WriteAsync就可以将我们的数据写入到Server中发送到客户端。
//命名管道发送消息到服务端
var data = Encoding.UTF8.GetBytes(textBox1.Text);
namedPipeServerStream.WriteAsync(data, 0, data.Length);
richTextBox1.Text += Environment.NewLine + "Server Send Val:" + textBox1.Text;
客户端
我们定义了一个Client的对象,.代表是当前计算机,以及和服务端一样的管道名称,同样定义为开启异步,以及是输入输出类型的。然后异步的去链接服务端,然后更新UI,通知已经链接成功,并且异步等待服务端给客户端发送消息,从而显示到UI上面。
var cts = WindowsFormsSynchronizationContext.Current;
//定义管道对象,如果需要是网络之间通信.替换为服务端的服务器名称和pipeName
//异步链接服务端
namedPipeClientStream.ConnectAsync().ContinueWith(s =>
{
var cs = cts;
cs.Send(new System.Threading.SendOrPostCallback(b =>
{
richTextBox1.Text += Environment.NewLine + "Server Is Connected;";
}), null);
var valByte = new byte[1024];
//异步等待收到服务端发送的消息 然后更新到UI
namedPipeClientStream.ReadAsync(valByte, 0, valByte.Length).ContinueWith(sb =>
{
var val = valByte;
var str = Encoding.UTF8.GetString(val);
cts.Send(new System.Threading.SendOrPostCallback(b =>
{
richTextBox1.Text += Environment.NewLine + "Client Receive Val:" + str;
}), null);
});
});
客户端发送代码:同服务端一样,写入我们的数据,服务端就会走到ReadAsync的方法中去,服务端就可以接收到我们发送的数据并且展示到UI,
//命名管道发送消息到服务端
var data = Encoding.UTF8.GetBytes(textBox1.Text);
namedPipeClientStream.WriteAsync(data, 0, data.Length);
richTextBox1.Text += Environment.NewLine + "Client Send Val:" + textBox1.Text;