C#进程间通讯技术-整理。

简介: 原文: C#进程间通讯技术-整理。   扩展阅读:http://www.cnblogs.com/joye-shen/archive/2012/06/16/2551864.html   一、进程间通讯的方式 1)共享内存 包括:内存映射文件,共享内存DLL,剪切板。

原文: C#进程间通讯技术-整理。

 

扩展阅读:http://www.cnblogs.com/joye-shen/archive/2012/06/16/2551864.html

 

一、进程间通讯的方式

1)共享内存

包括:内存映射文件,共享内存DLL,剪切板。

2)命名管道及匿名管道

3)消息通讯

4)利用代理方法。例如SOCKET,配置文件,注册表方式。

等方式。

方法一:通讯。

进程间通讯的方式有很多,常用的有共享内存(内存映射文件、共享内存DLL、剪切板等)、命名管道和匿名管道、发送消息等几种方法来直接完成,另外还可以通过socket口、配置文件和注册表等来间接实现进程间数据通讯任务。以上这几种方法各有优缺点,具体到在进程间进行大数据量数据的快速交换问题上,则可以排除使用配置文件和注册表的方法;另外,由于管道和socket套接字的使用需要有网卡的支持,因此也可以不予考虑。这样,可供选择的通讯方式只剩下共享内存和发送消息两种。

二、发送消息实现进程间通讯前准备

下面的例子用到一个windows api 32函数

[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);

要有此函数,需要添加using System.Runtime.InteropServices;命名空间

此方法各个参数表示的意义

wnd:接收消息的窗口的句柄。如果此参数为HWND_BROADCAST,则消息将被发送到系统中所有顶层窗口,包括无效或不可见的非自身拥有的窗口、被覆盖的窗口和弹出式窗口,但消息不被发送到子窗口。

msg:指定被发送的消息类型。

wP:消息内容。

lP:指定附加的消息指定信息。

用api参考手册查看SendMessage用法时,参考手册则提示

SendMessage与PostMessage之间的区别:SendMessage和PostMessage,这两个函数虽然功能非常相似,都是负责向指定的窗口发送消息,但是SendMessage() 函数发出消息后一直等到接收方的消息响应函数处理完之后才能返回,并能够得到返回值,在此期间发送方程序将被阻塞,SendMessage() 后面的语句不能被继续执行,即是说此方法是同步的。而PostMessage() 函数在发出消息后马上返回,其后语句能够被立即执行,但是无法获取接收方的消息处理返回值,即是说此方法是异步的。

三、发送消息实现进程间通讯具体步骤

1.新建windows应用程序

(1)打开VS2008,新建一个“windows 应用程序”,主窗口为Form1,项目名称:ProcessCommunication
(2)在Form1上添加一个标签为textBox1的文本框,并为Form1添加KeyDown事件,当Form1接收到KewDown消息时,将接收到的数据显示在label1上。

public Form1()
{
InitializeComponent();

this.KeyDown+=new KeyEventHandler(Form1_KeyDown);

}

private void Form1_KeyDown(object sender, KeyEventArgs e)
{
this.textBox1.Text = Convert.ToString(e.KeyValue);
}
(3)编译运行,生成ProcessCommunication.exe

2.新建windows应用程序

 

 

(1)打开VS2008,新建一个“windows 应用程序”,主窗口为Form1,项目名称:ProcessCommunication1,
并在Form1上添加一个按钮和一个文本框

namespace ProcessCommunication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Win32 API函数:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
private static extern int SendMessage(IntPtr wnd,int msg,IntPtr wP,IntPtr lP);

private void button1_Click(object sender, EventArgs e)
{
Process[] pros = Process.GetProcesses(); //获取本机所有进程
for (int i = 0; i < pros.Length; i++)
{
if (pros[i].ProcessName == "ProcessCommunication") //名称为ProcessCommunication的进程

{
IntPtr hWnd = pros[i].MainWindowHandle; //获取ProcessCommunication.exe主窗口句柄
int data = Convert.ToInt32(this.textBox1.Text); //获取文本框数据
SendMessage(hWnd, 0x0100, (IntPtr)data, (IntPtr)0); //点击该按钮,以文本框数据为参数,向Form1发送WM_KEYDOWN消息
}

}

}
}

3.启动ProcessCommunication.exe可执行文件,弹出Form1窗体称为接受消息窗体。

启动ProcessCommunication1.exe可执行文件,在弹出的窗体中的文本框中输入任意数字,点击button1按钮,接受消息窗体textBox1即显示该数字。

到此结束。

 

方法二:IPC通讯机制Remoting

=======

 

最近一直纠结与使用多进程还是多线程来构建程序。多线程的方法似乎不错,但是一个进程可承受的线程数有有限的,并且由于每个线程都与UI有着些许关系,线程的工作大多数时间浪费在阻塞上了,效率实在不是很高。

笔者遂在google上搜索进程间通讯的方案。发现有很多种,其中IPC通道似乎是个不错的选择,支持本机的进程间通讯,可以作为备选方案之一,下面介绍以下基本的编程方法,以作备忘。

首先建立一个IPC通讯中使用的对象,其中MarshalByRefObject 是必须的

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
 
namespace Ipctest
{
     public class test:MarshalByRefObject
     {
         private int iCount = 0;
         public int count()
         {
             iCount++;
             return iCount;
         }
 
         public int Add( int x)
         {
             iCount += x;
             return iCount;
         }
     }
}

接着建一个服务端控制台程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
 
namespace Ipctest
{
     class Program
     {
         static void Main( string [] args)
         {
            IpcChannel serverchannel = new IpcChannel( "testchannel" );
             ChannelServices.RegisterChannel(serverchannel, false );
             RemotingConfiguration.RegisterWellKnownServiceType( typeof (test), "test" , WellKnownObjectMode.Singleton);
             Console.WriteLine( "press Enter to exit" );
             Console.ReadLine();
             Console.WriteLine( "server stopped" );
         }
     }
}

最后是客户端控制台程序

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
 
namespace Ipctest
{
     class Program
     {
         static void Main( string [] args)
         {
             IpcChannel tcc = new IpcChannel();
             ChannelServices.RegisterChannel(tcc, false );
             WellKnownClientTypeEntry remotEntry = new WellKnownClientTypeEntry( typeof (test), "ipc://testchannel/test" );
             RemotingConfiguration.RegisterWellKnownClientType(remotEntry);
 
             test st = new test();
             Console.WriteLine( "{0},{1}" ,st.count(),st.Add(1));
             Console.ReadLine();
         }
     }
}

在测试的过程中会发现第一次调用客户端输出结果:

1,2

第二次输出结果

3,4

……

结果是比较符合要求的。

方法三:管道

 

 

最近在做一个数据库同步软件.!!

程序 服务端为 一个winform + windows Service 二大模块.!

由于程序功能的需求. 需要winform 与windows Service进程通讯. 因此使用了 命名管道 来实现功能需求.!

 

以此记下笔记 , 并付上一Demo

有关 NamedPipeServerStream  类 官方MSDN文档说明

服务端主要代码
 1 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
2
3 void Form1_Load(object sender, EventArgs e)
4 {
5 ThreadPool.QueueUserWorkItem(delegate
6 {
7 pipeServer.BeginWaitForConnection((o) =>
8 {
9 NamedPipeServerStream server = (NamedPipeServerStream)o.AsyncState;
10 server.EndWaitForConnection(o);
11 StreamReader sr = new StreamReader(server);
12 StreamWriter sw = new StreamWriter(server);
13 string result = null;
14 string clientName = server.GetImpersonationUserName();
15 while (true)
16 {
17 result = sr.ReadLine();
18 if (result == null || result == "bye")
19 break;
20 this.Invoke((MethodInvoker)delegate { lsbMsg.Items.Add(clientName+" : "+result); });
21 }
22 }, pipeServer);
23 });
24 }
1 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut, 4, PipeTransmissionMode.Message, PipeOptions.Asynchronous);
2
3 void Form1_Load(object sender, EventArgs e)
4 {
5 ThreadPool.QueueUserWorkItem(delegate
6 {
7 pipeServer.BeginWaitForConnection((o) =>
8 {
9 NamedPipeServerStream server = (NamedPipeServerStream)o.AsyncState;
10 server.EndWaitForConnection(o);
11 StreamReader sr = new StreamReader(server);
12 StreamWriter sw = new StreamWriter(server);
13 string result = null;
14 string clientName = server.GetImpersonationUserName();
15 while (true)
16 {
17 result = sr.ReadLine();
18 if (result == null || result == "bye")
19 break;
20 this.Invoke((MethodInvoker)delegate { lsbMsg.Items.Add(clientName+" : "+result); });
21 }
22 }, pipeServer);
23 });
24 }



有关 NamedPipeClientStream 类 官方MSDN文档说明

客户端主要代码
 1   NamedPipeClientStream pipeClient =
2 new NamedPipeClientStream("192.168.1.100", "testpipe",
3 PipeDirection.InOut, PipeOptions.Asynchronous,
4 TokenImpersonationLevel.None);
5 StreamWriter sw = null;
6 void Form2_Load(object sender, EventArgs e)
7 {
8 pipeClient.Connect();
9 sw = new StreamWriter(pipeClient);
10 sw.AutoFlush = true;
11 }
12
13 private void button1_Click_1(object sender, EventArgs e)
14 {
15 sw.WriteLine(textBox1.Text);
16 }
1   NamedPipeClientStream pipeClient =
2 new NamedPipeClientStream("192.168.1.100", "testpipe",
3 PipeDirection.InOut, PipeOptions.Asynchronous,
4 TokenImpersonationLevel.None);
5 StreamWriter sw = null;
6 void Form2_Load(object sender, EventArgs e)
7 {
8 pipeClient.Connect();
9 sw = new StreamWriter(pipeClient);
10 sw.AutoFlush = true;
11 }
12
13 private void button1_Click_1(object sender, EventArgs e)
14 {
15 sw.WriteLine(textBox1.Text);
16 }

 

 

经发现,命名管道, 其实是基于TCP/IP 来连接. 且端口为 445

 

当然, 我这里只是 传输一个字符串做为信息而已.! 其实仍然 可以传输自己所定义的 对象 等.(记得序列化哟..)

源码

方法四:共享内存

 

 

次发了利用发消息实现的C#进程间的通讯,这次又使用共享内存了,他们应用范围是不同的,共享内存适用于共享大量数据的情况。

复制代码
const int INVALID_HANDLE_VALUE = -1;
const int PAGE_READWRITE = 0x04;
  //共享内存
  [DllImport("Kernel32.dll",EntryPoint="CreateFileMapping")]
  private static extern IntPtr CreateFileMapping(IntPtr hFile, //HANDLE hFile,
   UInt32 lpAttributes,//LPSECURITY_ATTRIBUTES lpAttributes,  //0
   UInt32 flProtect,//DWORD flProtect
   UInt32 dwMaximumSizeHigh,//DWORD dwMaximumSizeHigh,
   UInt32 dwMaximumSizeLow,//DWORD dwMaximumSizeLow,
   string lpName//LPCTSTR lpName
   ); 

  [DllImport("Kernel32.dll",EntryPoint="OpenFileMapping")]
  private static extern IntPtr OpenFileMapping(
   UInt32 dwDesiredAccess,//DWORD dwDesiredAccess,
   int bInheritHandle,//BOOL bInheritHandle,
   string lpName//LPCTSTR lpName
   ); 

  const int FILE_MAP_ALL_ACCESS = 0x0002;
  const int FILE_MAP_WRITE = 0x0002; 

  [DllImport("Kernel32.dll",EntryPoint="MapViewOfFile")]
  private static extern IntPtr MapViewOfFile(
   IntPtr hFileMappingObject,//HANDLE hFileMappingObject,
   UInt32 dwDesiredAccess,//DWORD dwDesiredAccess
   UInt32 dwFileOffsetHight,//DWORD dwFileOffsetHigh,
   UInt32 dwFileOffsetLow,//DWORD dwFileOffsetLow,
   UInt32 dwNumberOfBytesToMap//SIZE_T dwNumberOfBytesToMap
   ); 

  [DllImport("Kernel32.dll",EntryPoint="UnmapViewOfFile")]
  private static extern int UnmapViewOfFile(IntPtr lpBaseAddress); 

  [DllImport("Kernel32.dll",EntryPoint="CloseHandle")]
  private static extern int CloseHandle(IntPtr hObject);
 然后分别在AB两个进程中定义如下两个信号量及相关变量;
 

  private Semaphore m_Write;  //可写的信号
  private Semaphore m_Read;  //可读的信号
  private IntPtr handle;     //文件句柄
  private IntPtr addr;       //共享内存地址
  uint mapLength;            //共享内存长
复制代码
复制代码
定义这两个信号量是为读写互斥用的。
在A进程中创建共享内存:
 

m_Write = new Semaphore(1,1,"WriteMap");
m_Read = new Semaphore(0,1,"ReadMap");
mapLength = 1024;
IntPtr hFile = new IntPtr(INVALID_HANDLE_VALUE);   
handle = CreateFileMapping(hFile,0,PAGE_READWRITE,0,mapLength,"shareMemory");
addr = MapViewOfFile(handle,FILE_MAP_ALL_ACCESS,0,0,0);
 
然后再向共享内存中写入数据:
 

m_Write.WaitOne();
byte[] sendStr = Encoding.Default.GetBytes(txtMsg.Text + '/0');
//如果要是超长的话,应另外处理,最好是分配足够的内存
if(sendStr.Length < mapLength)
      Copy(sendStr,addr);
m_Read.Release();
 

这是在一个单独的方法中实现的,可多次调用,但受信号量的控制。其中txtMsg是一个文本框控件,实际中可用任意字符串,加最后的'/0'是为了让在共享内存中的字符串有一个结束符,否则在内存中取出时是以'/0'为准的,就会出现取多的情况。
Copy方法的实现如下:
 

static unsafe void Copy(byte[] byteSrc,IntPtr dst)
  {
   fixed (byte* pSrc = byteSrc)
   {
    byte* pDst = (byte*)dst;
    byte* psrc = pSrc;
    for(int i=0;i<byteSrc.Length;i++)
    {
     *pDst = *psrc;
     pDst++;
     psrc ++;
    }
   }
  }
 注意unsafe 关键字,在编译时一定要打开非安全代码开关。
最后不要忘了在A进程中关闭共享内存对象,以免内存泄露。
 

   UnmapViewOfFile(addr);
   CloseHandle(handle);
 
要在B进程中读取共享内存中的数据,首先要打开共享内存对象:
 

m_Write = Semaphore.OpenExisting("WriteMap");
m_Read = Semaphore.OpenExisting("ReadMap");
handle = OpenFileMapping(0x0002,0,"shareMemory");
 读取共享内存中的数据:
 

   m_Read.WaitOne();
   string str = MapViewOfFile(handle,FILE_MAP_ALL_ACCESS,0,0,0);
   txtMsg.Text = str;
   m_Write.Release();
 这里获取了字符串,如果要获取byte数组,请参考上面的Copy函数实现。
目录
相关文章
|
2月前
|
Linux Shell
【Linux 进程间通讯 管道】使用Linux管道进行linux进程间通信
【Linux 进程间通讯 管道】使用Linux管道进行linux进程间通信
33 1
|
2月前
|
安全 Python
Python中的并发编程:多线程与多进程技术探究
本文将深入探讨Python中的并发编程技术,重点介绍多线程和多进程两种并发处理方式的原理、应用场景及优缺点,并结合实例分析如何在Python中实现并发编程,以提高程序的性能和效率。
|
2月前
|
安全 算法 网络安全
深入理解操作系统之进程调度策略网络安全与信息安全:防御前线的关键技术与策略
【2月更文挑战第29天】在多任务操作系统中,进程调度策略是核心机制之一,它决定了CPU资源的分配。本文将探讨三种经典的进程调度算法:先来先服务(FCFS)、短作业优先(SJF)和轮转调度(RR)。通过比较它们的优缺点,我们可以理解不同场景下的性能考量,以及如何根据实际需求选择合适的调度策略。 【2月更文挑战第29天】在数字化时代,网络安全和信息安全已成为维护信息完整性、确保数据隐私以及保障网络服务连续性的重要组成部分。本文将深入探讨网络安全漏洞的概念、加密技术的应用以及提升个人和企业安全意识的重要性。通过分析当前网络威胁的类型和特点,文章旨在为读者提供一系列针对网络攻击的预防措施和应对策略,
|
15天前
|
存储 C# 开发者
掌握 C#编程:关键技术与实践
【4月更文挑战第20天】掌握C#编程的关键技术,包括面向对象编程、数据类型、控制流、异常处理、集合泛型、委托事件和异步编程。通过学习基础、实践项目、代码调试、参考优秀代码、社区交流和持续学习来提升技能。注重代码质量,以开发高质量应用程序。
|
23天前
|
数据采集 C# 数据安全/隐私保护
掌握 C# 爬虫技术:使用 HttpClient 获取今日头条内容
本文介绍了如何使用C#的HttpClient与爬虫代理IP技术抓取今日头条内容,以实现高效的数据采集。通过结合亿牛云爬虫代理,可以绕过IP限制,增强匿名性。文中提供了一个代码示例,展示如何设置代理服务器信息、请求头,并用正则表达式提取热点新闻标题。利用多线程技术,能提升爬虫采集效率,为市场分析等应用提供支持。
掌握 C# 爬虫技术:使用 HttpClient 获取今日头条内容
|
2月前
|
负载均衡 网络协议 中间件
掌握 SOME/IP :访问进程数据 构建高效通信系统的关键技术
掌握 SOME/IP :访问进程数据 构建高效通信系统的关键技术
67 1
|
2月前
|
消息中间件 存储 网络协议
Linux IPC 进程间通讯方式的深入对比与分析和权衡
Linux IPC 进程间通讯方式的深入对比与分析和权衡
73 0
|
2月前
|
消息中间件 网络协议 API
Python语言的进程通讯及网络
Python语言的进程通讯及网络

相关实验场景

更多