想象你在一家大公司工作
传统方式(同一部门内沟通)
csharp
// 就像在一个大办公室里,大家直接说话
public class 同一个部门
{
private string 共享白板 = ""; // 共享内存,所有人都能改
public void 开会讨论()
{
// 直接沟通,速度快
程序员A说("这个功能怎么做?");
程序员B立即回答("用这个方法...");
// 问题:如果B不在座位上,就没人回答
// 而且大家都在一个房间,很吵
}
}
进程间通信(不同部门协作)
csharp
// 像在不同楼层的部门之间沟通
public class 跨部门协作
{
// 部门A在3楼,部门B在5楼
// 他们不能直接对话,需要特定的沟通方式
public void 请求协助()
{
// 方法1:打电话(管道)
拿起电话("拨分机号给财务部");
说需求("请审批这笔费用");
等待回复();
// 方法2:发邮件(消息队列)
写邮件("收件人:人事部");
邮件内容("新员工入职材料");
点击发送(); // 不需要对方立刻回复
// 方法3:用内部聊天软件(Socket)
打开Teams("技术部群聊");
发送消息("服务器出问题了!");
等待技术部回复();
// 方法4:共享文件夹(共享内存)
把文件放到("共享盘/项目文档");
通知大家("文档已更新");
}
}
进程间通信的主要方式(像公司沟通渠道)
1. 管道(Pipe) - 像公司内部电话
csharp
// 匿名管道(像分机直拨)
public class 内部电话系统
{
public void 父子部门沟通()
{
// 创建管道(安装内部电话)
using var pipe = new AnonymousPipeServerStream(PipeDirection.Out);
// 告诉子进程管道的句柄(告诉分机号)
string handle = pipe.GetClientHandleAsString();
// 父进程发送消息
byte[] 消息 = Encoding.UTF8.GetBytes("请处理这个任务");
pipe.Write(消息, 0, 消息.Length);
// 子进程接收消息(在另一个进程中)
using var clientPipe = new AnonymousPipeClientStream(PipeDirection.In, handle);
byte[] 缓冲区 = new byte[256];
int 读取长度 = clientPipe.Read(缓冲区, 0, 缓冲区.Length);
string 收到的消息 = Encoding.UTF8.GetString(缓冲区, 0, 读取长度);
// 子进程收到:"请处理这个任务"
}
}
// 命名管道(像公共客服热线)
public class 客服热线系统
{
public async Task 处理客户咨询()
{
// 服务器端:开通客服热线
using var server = new NamedPipeServerStream("公司客服热线", PipeDirection.InOut);
// 等待客户拨打
Console.WriteLine("客服热线已开通,等待来电...");
await server.WaitForConnectionAsync();
// 读取客户问题
byte[] 缓冲区 = new byte[1024];
int 读取长度 = await server.ReadAsync(缓冲区, 0, 缓冲区.Length);
string 客户问题 = Encoding.UTF8.GetString(缓冲区, 0, 读取长度);
// 回复客户
string 回答 = $"您的问题是:{客户问题},我们将尽快处理";
byte[] 回复数据 = Encoding.UTF8.GetBytes(回答);
await server.WriteAsync(回复数据, 0, 回复数据.Length);
}
public async Task 拨打客服热线()
{
// 客户端:拨打客服热线
using var client = new NamedPipeClientStream(".", "公司客服热线", PipeDirection.InOut);
// 连接服务器
await client.ConnectAsync();
// 发送问题
string 我的问题 = "我的订单什么时候发货?";
byte[] 问题数据 = Encoding.UTF8.GetBytes(我的问题);
await client.WriteAsync(问题数据, 0, 问题数据.Length);
// 等待回复
byte[] 回复缓冲区 = new byte[1024];
int 回复长度 = await client.ReadAsync(回复缓冲区, 0, 回复缓冲区.Length);
string 客服回复 = Encoding.UTF8.GetString(回复缓冲区, 0, 回复长度);
Console.WriteLine($"客服回复:{客服回复}");
}
}
2. 消息队列 - 像公司邮件系统
csharp
public class 公司邮件系统
{
// 发送邮件(不需要立即回复)
public void 发送部门邮件()
{
// 创建消息队列
MessageQueue 邮件系统;
if (!MessageQueue.Exists(@".\Private$\部门沟通"))
邮件系统 = MessageQueue.Create(@".\Private$\部门沟通");
else
邮件系统 = new MessageQueue(@".\Private$\部门沟通");
// 准备邮件内容
var 邮件 = new Message
{
Label = "项目进度报告", // 邮件主题
Body = "项目已完成80%,预计周五交付", // 邮件正文
Priority = MessagePriority.Normal // 邮件优先级
};
// 发送邮件(不等待回复)
邮件系统.Send(邮件);
Console.WriteLine("邮件已发送到邮件系统");
// 接收方可以在方便时查看邮件
// 邮件系统会保存邮件,直到接收方取走
}
// 接收邮件(异步处理)
public void 查看部门邮件()
{
var 邮件系统 = new MessageQueue(@".\Private$\部门沟通");
邮件系统.Formatter = new XmlMessageFormatter(new[] { typeof(string) });
// 查看所有未读邮件
var 所有邮件 = 邮件系统.GetAllMessages();
foreach (var 邮件 in 所有邮件)
{
Console.WriteLine($"主题:{邮件.Label}");
Console.WriteLine($"内容:{邮件.Body}");
// 处理完邮件后删除
邮件系统.Receive(); // 取走邮件
}
}
}
3. 共享内存 - 像公司共享白板
csharp
public class 项目共享白板
{
public void 协作编辑文档()
{
// 创建共享内存(挂一块白板)
using var 共享白板 = MemoryMappedFile.CreateNew("项目白板", 1024 * 1024); // 1MB大小
// 部门A写入数据(在白板上写字)
using (var 写入器 = 共享白板.CreateViewAccessor())
{
string 任务分配 = "张三:前端页面,李四:后端接口,王五:数据库设计";
byte[] 数据 = Encoding.UTF8.GetBytes(任务分配);
写入器.WriteArray(0, 数据, 0, 数据.Length);
Console.WriteLine("任务已写在共享白板上");
}
// 部门B读取数据(看白板上的内容)
using (var 读取器 = 共享白板.CreateViewAccessor())
{
byte[] 缓冲区 = new byte[256];
读取器.ReadArray(0, 缓冲区, 0, 缓冲区.Length);
string 任务内容 = Encoding.UTF8.GetString(缓冲区).TrimEnd('\0');
Console.WriteLine($"看到白板上的任务:{任务内容}");
}
// 问题:如果两个部门同时写,会互相覆盖
// 需要加锁(像白板笔一次只能一个人用)
}
}
4. Socket(本地) - 像公司内部聊天软件
csharp
public class 内部聊天系统
{
// 服务器端:建立聊天室
public void 开启部门聊天室()
{
var ip地址 = IPAddress.Parse("127.0.0.1");
var 聊天室 = new TcpListener(ip地址, 8888);
聊天室.Start();
Console.WriteLine("部门聊天室已开启,等待同事加入...");
while (true)
{
// 等待同事连接
TcpClient 同事客户端 = 聊天室.AcceptTcpClient();
// 新开线程处理这个同事
ThreadPool.QueueUserWorkItem(与同事聊天, 同事客户端);
}
}
private void 与同事聊天(object state)
{
var 同事 = (TcpClient)state;
var 网络流 = 同事.GetStream();
// 发送欢迎消息
byte[] 欢迎消息 = Encoding.UTF8.GetBytes("欢迎加入部门聊天室!");
网络流.Write(欢迎消息, 0, 欢迎消息.Length);
// 接收同事消息
byte[] 缓冲区 = new byte[1024];
int 接收长度 = 网络流.Read(缓冲区, 0, 缓冲区.Length);
string 同事消息 = Encoding.UTF8.GetString(缓冲区, 0, 接收长度);
Console.WriteLine($"同事说:{同事消息}");
// 回复
string 我的回复 = $"收到:{同事消息}";
byte[] 回复数据 = Encoding.UTF8.GetBytes(我的回复);
网络流.Write(回复数据, 0, 回复数据.Length);
}
}
实际场景:财务审批系统
csharp
// 场景:员工申请报销,需要多个部门审批
public class 报销审批系统
{
// 方法1:使用命名管道(像打电话逐个审批)
public async Task 管道审批流程()
{
// 员工提交申请
string 报销申请 = "申请报销差旅费1000元";
// 第一步:部门经理审批(通过管道)
using var 经理管道 = new NamedPipeClientStream(".", "经理审批管道", PipeDirection.InOut);
await 经理管道.ConnectAsync();
await 经理管道.WriteAsync(Encoding.UTF8.GetBytes(报销申请));
byte[] 经理回复 = new byte[1024];
int 经理回复长度 = await 经理管道.ReadAsync(经理回复);
string 经理审批结果 = Encoding.UTF8.GetString(经理回复, 0, 经理回复长度);
if (经理审批结果.Contains("同意"))
{
// 第二步:财务审批
using var 财务管道 = new NamedPipeClientStream(".", "财务审批管道", PipeDirection.InOut);
await 财务管道.ConnectAsync();
await 财务管道.WriteAsync(Encoding.UTF8.GetBytes($"{报销申请},经理已同意"));
byte[] 财务回复 = new byte[1024];
int 财务回复长度 = await 财务管道.ReadAsync(财务回复);
string 财务审批结果 = Encoding.UTF8.GetString(财务回复, 0, 财务回复长度);
Console.WriteLine($"最终审批结果:{财务审批结果}");
}
}
// 方法2:使用消息队列(像发邮件,不需要立即回复)
public void 邮件审批流程()
{
// 员工发送审批邮件
var 邮件队列 = new MessageQueue(@".\Private$\审批队列");
var 审批邮件 = new Message
{
Label = "报销审批申请",
Body = new 审批请求
{
申请人 = "张三",
金额 = 1000,
事由 = "差旅费",
申请时间 = DateTime.Now
},
Priority = MessagePriority.Normal
};
邮件队列.Send(审批邮件);
Console.WriteLine("审批邮件已发送,请等待各部门处理");
// 各部门独立处理(不需要同步等待)
// 经理进程:从队列读取邮件,审批,发回结果队列
// 财务进程:从结果队列读取,继续审批
}
// 方法3:使用共享内存(像在共享表格里更新状态)
public void 共享表格审批()
{
// 创建共享的审批状态表
using var 共享表格 = MemoryMappedFile.CreateOrOpen("审批状态表", 4096);
// 员工填写申请
using (var 写入器 = 共享表格.CreateViewAccessor())
{
string 申请状态 = "张三申请1000元差旅费|待经理审批|待财务审批|未付款";
byte[] 状态数据 = Encoding.UTF8.GetBytes(申请状态);
写入器.WriteArray(0, 状态数据, 0, 状态数据.Length);
}
// 经理进程查看并更新
using (var 经理访问器 = 共享表格.CreateViewAccessor())
{
byte[] 当前状态 = new byte[256];
经理访问器.ReadArray(0, 当前状态, 0, 当前状态.Length);
string 状态文本 = Encoding.UTF8.GetString(当前状态).TrimEnd('\0');
// 经理审批通过
string 更新状态 = 状态文本.Replace("待经理审批", "经理已同意");
byte[] 新状态数据 = Encoding.UTF8.GetBytes(更新状态);
经理访问器.WriteArray(0, 新状态数据, 0, 新状态数据.Length);
}
// 财务进程看到更新后的状态,继续处理
}
}
进程间通信的选择指南
| 沟通场景 | 对应IPC技术 | 特点 | 就像... |
|---|---|---|---|
| 需要立即回复 | 命名管道、Socket | 实时双向通信 | 打电话 |
| 可以延迟处理 | 消息队列 | 异步、可靠、解耦 | 发邮件 |
| 频繁数据共享 | 共享内存 | 速度最快 | 共享白板 |
| 简单父子通信 | 匿名管道 | 单向、简单 | 便条传话 |
| 跨机器通信 | Socket(网络) | 可跨机器 | 视频会议 |
完整示例:公司内部通讯系统
csharp
// 主程序:模拟公司不同部门的进程间通信
public class 公司通讯系统
{
public static void Main()
{
Console.WriteLine("=== 公司内部通讯系统 ===");
Console.WriteLine("1. 研发部进程");
Console.WriteLine("2. 市场部进程");
Console.WriteLine("3. 财务部进程");
Console.Write("请选择要启动的部门:");
string 选择 = Console.ReadLine();
switch (选择)
{
case "1":
启动研发部();
break;
case "2":
启动市场部();
break;
case "3":
启动财务部();
break;
}
}
static void 启动研发部()
{
Console.WriteLine("研发部进程启动...");
// 研发部启动一个命名管道服务器,等待其他部门咨询技术问题
Task.Run(() => 启动技术咨询热线());
// 研发部也创建一个共享内存,存放技术文档
using var 技术文档库 = MemoryMappedFile.CreateOrOpen("公司技术文档", 1024 * 1024);
// 写入最新技术方案
using (var 写入器 = 技术文档库.CreateViewAccessor())
{
string 技术方案 = "项目架构:微服务 + 容器化\n数据库:MySQL + Redis\n前端:React + TypeScript";
byte[] 数据 = Encoding.UTF8.GetBytes(技术方案);
写入器.WriteArray(0, 数据, 0, 数据.Length);
Console.WriteLine("技术文档已更新到共享内存");
}
Console.ReadLine();
}
static void 启动市场部()
{
Console.WriteLine("市场部进程启动...");
// 市场部启动消息队列,接收客户需求
Task.Run(() => 处理客户需求());
// 市场部通过命名管道向研发部咨询
Task.Run(async () =>
{
using var 管道 = new NamedPipeClientStream(".", "技术咨询热线", PipeDirection.InOut);
await 管道.ConnectAsync();
string 咨询问题 = "客户需要微信小程序,我们技术可行吗?";
byte[] 问题数据 = Encoding.UTF8.GetBytes(咨询问题);
await 管道.WriteAsync(问题数据, 0, 问题数据.Length);
byte[] 回复 = new byte[1024];
int 长度 = await 管道.ReadAsync(回复, 0, 回复.Length);
string 技术回复 = Encoding.UTF8.GetString(回复, 0, 长度);
Console.WriteLine($"研发部回复:{技术回复}");
});
Console.ReadLine();
}
static void 启动财务部()
{
Console.WriteLine("财务部进程启动...");
// 财务部读取共享内存中的技术文档(了解项目情况)
using var 技术文档库 = MemoryMappedFile.CreateOrOpen("公司技术文档", 1024 * 1024);
using (var 读取器 = 技术文档库.CreateViewAccessor())
{
byte[] 缓冲区 = new byte[512];
读取器.ReadArray(0, 缓冲区, 0, 缓冲区.Length);
string 技术文档 = Encoding.UTF8.GetString(缓冲区).TrimEnd('\0');
Console.WriteLine($"从共享内存读取技术文档:\n{技术文档}");
}
// 财务部也提供一个命名管道服务,处理报销审批
Task.Run(() => 启动报销审批服务());
Console.ReadLine();
}
static async Task 启动技术咨询热线()
{
while (true)
{
using var 服务器 = new NamedPipeServerStream("技术咨询热线", PipeDirection.InOut);
Console.WriteLine("技术咨询热线等待连接...");
await 服务器.WaitForConnectionAsync();
Console.WriteLine("有部门连接技术咨询热线");
byte[] 缓冲区 = new byte[1024];
int 长度 = await 服务器.ReadAsync(缓冲区, 0, 缓冲区.Length);
string 问题 = Encoding.UTF8.GetString(缓冲区, 0, 长度);
string 回答 = $"关于'{问题}',我们评估后认为技术可行,预计需要2周开发时间";
byte[] 回答数据 = Encoding.UTF8.GetBytes(回答);
await 服务器.WriteAsync(回答数据, 0, 回答数据.Length);
}
}
static void 处理客户需求()
{
// 创建消息队列接收客户需求
if (!MessageQueue.Exists(@".\Private$\客户需求队列"))
MessageQueue.Create(@".\Private$\客户需求队列");
var 队列 = new MessageQueue(@".\Private$\客户需求队列");
队列.Formatter = new XmlMessageFormatter(new[] { typeof(string) });
Console.WriteLine("等待客户需求...");
while (true)
{
var 消息 = 队列.Receive();
Console.WriteLine($"收到客户需求:{消息.Label} - {消息.Body}");
// 处理需求,然后可以发送回复到另一个队列
}
}
static async Task 启动报销审批服务()
{
while (true)
{
using var 服务器 = new NamedPipeServerStream("报销审批服务", PipeDirection.InOut);
Console.WriteLine("报销审批服务等待连接...");
await 服务器.WaitForConnectionAsync();
byte[] 缓冲区 = new byte[1024];
int 长度 = await 服务器.ReadAsync(缓冲区, 0, 缓冲区.Length);
string 报销申请 = Encoding.UTF8.GetString(缓冲区, 0, 长度);
Console.WriteLine($"收到报销申请:{报销申请}");
// 模拟审批逻辑
string 审批结果 = DateTime.Now.Second % 2 == 0
? "审批通过,可以付款"
: "审批不通过,请补充材料";
byte[] 结果数据 = Encoding.UTF8.GetBytes(审批结果);
await 服务器.WriteAsync(结果数据, 0, 结果数据.Length);
}
}
}
总结比喻
进程间通信 = 公司各部门的沟通方式
-
共享内存:
- 像公司公共白板或共享文件夹
- 大家都能看到和修改
- 最快,但容易冲突(需要锁机制)
-
命名管道:
- 像公司内部电话系统
- 需要建立连接,实时双向通信
- 适合需要立即回复的场景
-
匿名管道:
- 像父子部门之间的便条
- 只能单向或简单的双向通信
- 只能用于有亲缘关系的进程
-
消息队列:
- 像公司邮件系统
- 异步处理,不需要立即回复
- 可靠,消息不会丢失
-
Socket(本地) :
- 像公司内部聊天软件
- 可以双向实时通信
- 更灵活,可以扩展到网络通信
关键理解:
每个进程就像公司的一个独立部门,它们有自己的办公室(内存空间),不能直接访问对方的办公室。IPC就是为它们提供各种沟通渠道,让它们能协同工作。
选择原则:
- 需要实时沟通?用管道或Socket
- 可以异步处理?用消息队列
- 需要频繁共享大量数据?用共享内存
- 只是简单通知?用信号量或事件
通过合适的IPC技术,不同的进程(部门)就能高效协作,完成复杂的任务!