Channel 信道
上篇介绍了共享内存、命名管道、匿名管道这三种进程间通讯方法,下面介绍通过Channel(信道)进行通讯的几种方法,包括IPC、HTTP、TCP三种。
什么是信道呢?本质上信道是生产者/消费者编程模型的一种实现,生产者生成数据,然后将数据传递进信道之中,而消费者从信道中不断地获取并使用数据,这两者都可以是异步的。信道维护了这么一个数据传输的通路,它可以规定数据传输的各种配置,并控制各个生产者/消费者的并发请求。从这个意义上看,信道类似于队列,是一种数据结构,它与队列的区别在于,它提供了一种完全异步的数据传输方式。
在.NET Core中,是有提供一个信道结构的封装,那就是System.Threading.Channels类,这个类封装了一个泛型Channel<T>类,可以用于快速地创建并使用一个类型化的信道,当然这里的信道只是一种简单的数据结构,只用于本地程序中数据的传输。有关信道结构的具体实现,可以见:An Introduction to System.Threading.Channels - .NET Blog
下面展示了一个例子:
using System.Threading.Channels; Channel<double> channel = Channel.CreateUnbounded<double>(new UnboundedChannelOptions { SingleWriter = false, SingleReader = false, AllowSynchronousContinuations = true }); ChannelWriter<double> writer = channel.Writer; ChannelReader<double> reader = channel.Reader; Consume(reader); Produce(writer, 50); Console.ReadLine(); static void Produce(ChannelWriter<double> writer, double value) { while (value < 100) { double temp = value + 1; if (writer.TryWrite(temp)) { value = temp; } Thread.Sleep(1000); } } static async ValueTask Consume(ChannelReader<double> reader) { while (true) { double value = await reader.ReadAsync(); Console.WriteLine(value); } }
在这个例子中,消费者使用ReadAsync等待信道数据,而生产者每秒都向信道中写入一个double数据,一旦数据到达信道,另一边的消费者即可异步读取,然后又等待新的数据,这样就实现了生产者和消费者模式。
下面介绍用于进程间通讯的三种信道。
HttpChannel
所谓HttpChannel,即使用了HTTP协议去传输信息的一种信道,所以它支持进程间通讯,也支持网络间通讯。.NET的HttpChannel类位于System.Runtime.Remoting.Channels命名空间。需要注意的是,.NET5+和.NET Core均不再支持Remoting功能,所以HttpChannel(以及下面的TcpChannel、IpcChannel)只能在.NET Framework中使用。
下面展示了一个例子,服务端:
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; namespace RemotingTest { public class RemoteObject : MarshalByRefObject { private int count = 0; public int GetCount() { Console.WriteLine("GetCount"); count++; return count; } } class Program { static void Main(string[] args) { HttpServerChannel server = new HttpServerChannel(9090); //注册信道 ChannelServices.RegisterChannel(server, false); //注册对象类型,指定URI和生命周期 RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject), "RemoteTest.rem", WellKnownObjectMode.Singleton); Console.ReadLine(); } } }
这里服务端先注册一个Http信道,然后注册了注入对象类型,此后这个注入对象将作为信道传输的媒介,该对象的生命周期为Singleton,这表示任何连接都将使用同一个对象。这里的<RemoteTest.rem>指定了访问信道的路径,如果是本机访问,那么完整路径为<http://localhost:9090/RemoteTest.rem>。
客户端:
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; namespace RemotingTest { public class RemoteObject { private int count = 0; public int GetCount() { Console.WriteLine("GetCount"); count++; return count; } } class Program { static void Main(string[] args) { HttpClientChannel client = new HttpClientChannel(); ChannelServices.RegisterChannel(client, false); string url = "http://localhost:9090/RemoteTest.rem"; WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(RemoteObject), url); RemotingConfiguration.RegisterWellKnownClientType(entry); RemoteObject obj = new RemoteObject(); Console.WriteLine(obj.GetCount()); Console.ReadLine(); } } }
客户端的操作与之类似,也是先注册信道,再注册注入对象类型,但注册时需要额外指定服务端的URL,以便连接到服务端。之后客户端只需创建对应的注入类型的对象,那么就能和服务端共享一个对象了。
这里注意,客户端和服务端必须处在同一个程序集中,且注入对象的类型必须在同一个命名空间里,否则连接就会抛出异常。
这里的注入对象被称为Remote Object,即远程对象。远程对象本质上是在服务端创建的,其数据也是储存在服务端中,远程对象必须继承自MarshalByRefObject,这是因为远程对象在被本机以外的程序创建和访问时,应当只是创建一个代理,这个代理将程序对远程对象的所有操作通过RPC来传输到服务端,并在服务端执行对应的方法。这样,在客户端的程序就能操作实际上在服务端储存的对象了,这就实现了数据传输。这个代理是由MarshalByRefObject对象来提供的,如果不继承自这个类,那么客户端储存的就是一个具体的对象,而不是代理,对该对象的操作仅限于本机,而不会同步到服务端的对象当中。
TcpChannel
TcpChannel和HttpChannel类似,区别是使用的协议不同,Tcp使用的协议采用二进制的格式去传输数据,Http采用文本的形式去传输数据,且Tcp无需像Http协议一样创建较大的报文头,从这个角度来说,TcpChannel会比HttpChannel更快。当然,HttpChannel提供了更多的灵活性,意味着在与Web服务器交互时会更方便。
TcpChannel的使用方法和HttpChannel是基本相同的。
服务端:
static void Main(string[] args) { TcpServerChannel server = new TcpServerChannel(9090); //注册信道 ChannelServices.RegisterChannel(server, false); //注册对象,指定URI和生命周期 RemotingConfiguration.RegisterWellKnownServiceType(typeof(RemoteObject), "RemoteTest.rem", WellKnownObjectMode.Singleton); Console.ReadLine(); }
客户端:
static void Main(string[] args) { TcpClientChannel client = new TcpClientChannel(); ChannelServices.RegisterChannel(client, false); string url = "tcp://localhost:9090/RemoteTest.rem"; WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(RemoteObject), url); RemotingConfiguration.RegisterWellKnownClientType(entry); RemoteObject obj = new RemoteObject(); Console.WriteLine(obj.GetCount()); Console.ReadLine(); }
IpcChannel
IpcChannel 采用的是WindowsIPC协议,这种协议专门用于进程间通讯,因为它不支持网络间通讯,但优点是它一般比Http、Tcp信道更快。IpcChannel主要就是为了改善本机信道的通讯速度的。
使用方法与上文一致,区别是Ipc协议不采用端口,而是采用端口名称。
服务端创建代码:
IpcServerChannel server = new IpcServerChannel("IpcTest");
客户端连:
TcpClientChannel client = new TcpClientChannel(); ChannelServices.RegisterChannel(client, false); string url = "ipc://IpcTest/RemoteTest.rem"; WellKnownClientTypeEntry entry = new WellKnownClientTypeEntry(typeof(RemoteObject), url); RemotingConfiguration.RegisterWellKnownClientType(entry);