【C#】进程间通讯2

/ 0评 / 0

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);

 

Leave a Reply

Your email address will not be published. Required fields are marked *