C# 进程间通信 IPC

C# 进程间通信 IPC

在 C# 中,进程间通信(Inter-Process Communication,IPC)进程间通信是指两个或多个程序进程之间的通讯,可以通过多种方式实现:

  • 套接字通信Socket
  • 共享内存MemoryMappedFile
  • 管道通讯PipeStream
  • 消息队列 MessageQueue
  • WinApi SendMessage
  • 使用剪切板

在这里插入图片描述

Socket套接字通信

使用Socket来进行ICP通讯优点如下:

  1. 可以跨越多种网络拓扑结构进行通信,包括局域网、广域网和互联网。
  2. 可以通过多种协议进行通信,包括 TCP 和 UDP。
  3. 支持高性能的流式传输,适用于大量数据的传输。

缺点如下:

  1. 需要编写较多的底层代码来处理网络连接、消息传递等细节。
  2. 在不同的操作系统之间可能存在差异,需要注意兼容性问题。
  3. 在处理大量并发连接时,需要自己实现连接池等机制来优化性能。
TcpListener 服务器
  • TcpListener(IPAddress localaddr, int port) 构造一个tcp服务器

  • Start() 启动一个服务器

  • BeginAcceptTcpClient 异步等待一个客户连接

    • TcpClient EndAcceptTcpClient() 异步等待的回调中可以使用此方法来获取一个tcp客户端
TcpClient 客户端
  • BeginConnect() 异步连接服务器,如果连接不上可能会报错
  • NetworkStream GetStream() 获取一个网络流
    • NetworkStream 网络流对象
      • BeginRead 异步读取数据
      • BeginWrite 异步写入数据
TcpListener 服务器示例代码
// 创建一个 TcpListener 对象,并开始侦听指定的 IP 地址和端口
var listener = new TcpListener(IPAddress.Any, 5000);
// 启动服务器
listener.Start();

Console.WriteLine("服务器已启动,正在等待客户端连接...");

// 异步等待服务器连接
tcpListener.BeginAcceptTcpClient(ClientCallBack, tcpListener);


// 服务器连接回调
private void ClientCallBack(IAsyncResult ar)
{
    // 获取绑定的对象
    TcpListener listener = (TcpListener)ar.AsyncState;

    // 结束连接等待,获取客户端实例
  	TcpClient tcpClient = listener.EndAcceptTcpClient(ar);

    // 获取网络流对象
 	NetworkStream stream = tcpClient.GetStream();
    // 异步发送数据
    byte[] data = Encoding.ASCII.GetBytes("hello from service.");
    stream.BeginWrite(data, 0, data.Length, SendCallBack, tcpClient);
    
    // 异步等待客户端消息
    stream.BeginRead(data, 0, data.Length, ReceiveCallBack, tcpClient);    
}
TcpClient 示例代码
// 创建客户端实例
var client = new TcpClient();
// 连接到服务器
client.Connect(IPAddress.Parse("127.0.0.1"), 5000);
Console.WriteLine("Connected to server.");

// 获取网络流
var stream = client.GetStream();

// 向服务器发送数据
var data = Encoding.ASCII.GetBytes("Hello from client.");
stream.Write(data, 0, data.Length);

// 异步等待服务器消息
stream.BeginRead(data, 0, data.Length, ReceiveCallBack, client);   


// 接收消息回调
private void ReceiveCallBack(IAsyncResult ar)
{
    // 获取绑定的对象
    var client = (TcpClient)ar.AsyncState;

    // 结束读取,获取读到消息的长度
  	var dataLen = client.EndRead(ar);

     // 读取到的数据
    byte[] temp = new byte[dataLen];

    // 从 buffer 读取获取到的数据  复制到 temp
    Array.Copy(this.Buffer, 0, temp, 0, dataLen);
  
    // 异步发送数据
    var data = Encoding.ASCII.GetString(temp);
    
  	Console.WriteLine("Receive data: "+data);
}

共享内存

使用系统的共享内存来进行通讯,

优点:代码简单,速度快

缺点:不适用于跨机器通信, 内存是有限制的是不能满足大量数据通信,多进程数据同步问题

using System;
using System.IO;
using System.IO.MemoryMappedFiles;

namespace SharedMemory
{
    class Program
    {
        static void Main(string[] args)
        {
            // 在当前进程中创建一个名为 "MyMemory" 的共享内存区域,大小为 1024 字节
            using (var memoryMappedFile = MemoryMappedFile.CreateOrOpen("MyMemory", 1024))
            {
                // 使用 MemoryMappedViewAccessor 类来创建一个视图,可以通过它来读写共享内存区域
                using (var accessor = memoryMappedFile.CreateViewAccessor())
                {
                    // 将整数 123 写入共享内存区域的偏移量为 0 的位置
                    accessor.Write(0, 123);
                }
            }

            // 在另一个进程中打开名为 "MyMemory" 的共享内存区域
            using (var memoryMappedFile = MemoryMappedFile.OpenExisting("MyMemory"))
            {
                // 使用 MemoryMappedViewAccessor 类来创建一个视图,可以通过它来读写共享内存区域
                using (var accessor = memoryMappedFile.CreateViewAccessor())
                {
                    // 从共享内存区域的偏移量为 0 的位置读取一个整数
                    int value = accessor.ReadInt32(0);
                    Console.WriteLine(value); // 输出 123
                }
            }
        }
    }
}
管道通讯

管道是一种在两个进程之间进行通信的方式,它们可以在进程之间创建一个通信管道,这样就可以在进程之间发送和接收数据,管道建立连接后,一旦选择阻塞管道接收数据,就不能在调用发送消息方法了,也就是说,一次连接的管道只能用来发消息或接收消息,不能在一个管道上同时发和接收数据,如果需要同时发送或接收数据,请考虑使用Socket通讯

优点有:

  • 可以跨机器通信:管道可以在不同的计算机之间使用,因此可以用于跨机器通信。
  • 可以传输任意类型的数据:管道可以传输任意类型的数据,而不受内存大小的限制。

缺点有:

  • 速度较慢:管道需要经过网络或文件系统,因此通信速度比共享内存慢。
  • 需要序列化和反序列化数据:在使用管道进行通信时,需要将数据序列化为字节流,然后在另一端反序列化为原来的数据类型。这会增加程序的复杂度。
匿名管道
AnonymousPipeServerStream
AnonymousPipeClientStream
命名管道

NamedPipeServerStream 服务器通信实例

NamedPipeClientStream 客户端通信实例

NamedPipeServerStream 服务器通信对象
  • NamedPipeServerStream - 构造方法可以指定管道名称,流方向和 连接实例数量
  • WaitForConnection - 阻塞线程,并等待客户段连接
NamedPipeClientStream 客户端通信对象
  • NamedPipeClientStream - 构造方法可以指定客户端名称,管道名称,流方向
  • Connect - 阻塞线程,并连接服务器,超时会触发异常

共有方法:

  • Write 同步写入数据-此方法会和Read方法互斥,就是不能在一个线程里同时读和写

  • Read 同步读取数据-此方法会和Write方法互斥,就是不能在一个线程里同时读和写

  • BeginWrite 异步写入数据方法

    • WaitForPipeDrain 使用Write方法写完数据后,调用此方法,触发对方Read读取消息
  • BeginRead 异步读取数据方法

    • EndRead 结束读取数据,在异步回调的方法里面使用此方法可以获取收到的数据长度

写入数据示例:

// 同步写入数据
public void WriteData(byte[] data)
{
    Console.WriteLine("发送数据");

    Client.Write(data, 0, data.Length);
    Client.Flush();
    Client.WaitForPipeDrain();

    Console.WriteLine("发送完成");
}

// 异步写入数据
Client.BeginWrite(data, 0, data.Length, new AsyncCallback(SendCallback), Client);

private void SendCallback(IAsyncResult arg)
{
    try
    {   // Get the pipe NamedPipeClientStream
        var client = arg.AsyncState as NamedPipeClientStream;
        // End the write pipeStream.EndWrite(iar);
        client.Flush();
        // notify receive
        client.WaitForPipeDrain();      
    }
    catch (Exception)
    {

    }
}

读取数据示例:

// 同步接收数据
byte[] buffer = new byte[1024];
int bytesRead = 0;

do
{
    bytesRead = Client.Read(buffer, 0, buffer.Length);

    if (bytesRead > 0)
    {
        string receivedData = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);
        Console.WriteLine("Received data: " + receivedData);
        bytesRead = 0;
    }

} while (bytesRead == 0);

// 异步接收数据
client.BeginRead(this.Buffer, 0, this.Buffer.Length, new AsyncCallback(ReadCallback), client);

 private void ReadCallback(IAsyncResult arg)
 {
     try
     {
         var client = arg.AsyncState as NamedPipeClientStream;
         int length = client.EndRead(arg);

         if (length > 0)
         {
             byte[] temp = new byte[length];

             // 从 buffer 读取获取到的数据  复制到 temp
             Array.Copy(this.Buffer, 0, temp, 0, length);

             string receivedData = System.Text.Encoding.UTF8.GetString(temp, 0, bytesRead);
             Console.WriteLine("Received data: " + receivedData);
         }

         //再次执行异步读取操作
         OnReceiveData(client);

     }
     catch (Exception)
     {
     }
 }
消息队列

消息队列是一种用于在进程、应用程序或主机之间进行消息传递的方法。使用消息队列,可以将消息发送到队列中,然后由接收器从队列中读取消息。这种方式使得发送和接收进程可以独立地运行,并且还可以保证消息按照先进先出(FIFO)的顺序进行处理。

使用消息队列的优点:
  1. 可以跨机器通信:消息队列可以在不同的计算机之间使用,因此可以用于跨机器通信。
  2. 支持异步通信:消息队列支持异步通信,因此可以在不阻塞当前线程的情况下进行通信。
  3. 可以传输任意类型的数据:消息队列可以传输任意类型的数据,而不受内存大小的限制。
使用消息队列的缺点:
  1. 需要系统支持,在Windows中需要手动开启才可以使用消息队列通信
  2. 速度较慢:消息队列需要经过网络或文件系统,因此通信速度比共享内存慢。
  3. 需要序列化
using System;
using System.Messaging;

namespace MessageQueueExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // 连接到本地消息队列
            string queueName = ".\\private$\\myqueue";
            MessageQueue queue = null;
            if (MessageQueue.Exists(queueName))
            {
                queue = new MessageQueue(queueName);
            }
            else
            {
                queue = MessageQueue.Create(queueName);
            }

            // 发送消息到队列中
            string messageBody = "Hello, World!";
            Message message = new Message(messageBody);
            queue.Send(message);

            // 从队列中接收消息
            Message receivedMessage = queue.Receive();
            string receivedMessageBody = receivedMessage.Body.ToString();
            Console.WriteLine("Received message: " + receivedMessageBody);
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值