Comunicación entre procesos en C#: ocho mecanismos con código de ejemplo

Cuando varios ejecutables .NET deben intercambiar datos, la plataforma ofrece opciones que van desde canales locales hasta stacks de red completos. A continuación se presentan ocho alternativas, cada una con una implementación mínima y su caso de uso natural.

1. Pipes con nombre (Named Pipes)

Permiten flujo bidireccional dantro de la misma máquina con latencia baja.

Servidor

using System.IO.Pipes;
using System.Text;

using var servidor = new NamedPipeServerStream("CanalDemo", PipeDirection.InOut);
Console.WriteLine("Esperando cliente...");
servidor.WaitForConnection();

byte[] recibido = new byte[256];
int leidos = servidor.Read(recibido, 0, recibido.Length);
string mensaje = Encoding.UTF8.GetString(recibido, 0, leidos).TrimEnd('\0');
Console.WriteLine($"Recibido: {mensaje}");

string respuesta = "Saludos desde el servidor";
byte[] envio = Encoding.UTF8.GetBytes(respuesta);
servidor.Write(envio, 0, envio.Length);

Cliente

using System.IO.Pipes;
using System.Text;

using var cliente = new NamedPipeClientStream(".", "CanalDemo", PipeDirection.InOut);
cliente.Connect();
Console.WriteLine("Conectado al servidor");

string consulta = "Hola desde el cliente";
byte[] envio = Encoding.UTF8.GetBytes(consulta);
cliente.Write(envio, 0, envio.Length);

byte[] respuesta = new byte[256];
int leidos = cliente.Read(respuesta, 0, respuesta.Length);
Console.WriteLine(Encoding.UTF8.GetString(respuesta, 0, leidos));

2. Archivos mapeados en memoria (Memory-Mapped Files)

Comparten un bloque de memoria entre procesos, evitando copias intermedias.

Proceso escritor

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

using var mmf = MemoryMappedFile.CreateOrOpen("MemoriaCompartida", 4096);
using var vista = mmf.CreateViewAccessor();

string texto = "Mensaje desde otro proceso";
byte[] payload = Encoding.UTF8.GetBytes(texto);

vista.Write(0, (ushort)payload.Length);
vista.WriteArray(2, payload, 0, payload.Length);

Proceso lector

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

using var mmf = MemoryMappedFile.OpenExisting("MemoriaCompartida");
using var vista = mmf.CreateViewAccessor();

ushort longitud = vista.ReadUInt16(0);
byte[] buffer = new byte[longitud];
vista.ReadArray(2, buffer, 0, longitud);

string contenido = Encoding.UTF8.GetString(buffer);
Console.WriteLine(contenido);

3. Servicio WCF

Útil cuando se requiere un contrato fuerte y puede aprovechar transportes como named pipes o TCP.

Contrato y host

using System.ServiceModel;

[ServiceContract]
public interface IProcesoRemoto
{
   [OperationContract]
   string ObtenerSaludo(string nombre);
}

public class ServicioProceso : IProcesoRemoto
{
   public string ObtenerSaludo(string nombre) => $"Hola {nombre} desde el servicio";
}

// Host
using var host = new ServiceHost(typeof(ServicioProceso),
   new Uri("net.pipe://localhost/DemoIPC"));
host.AddServiceEndpoint(typeof(IProcesoRemoto),
   new NetNamedPipeBinding(), "Saludos");
host.Open();
Console.ReadLine();

Cliente WCF

using System.ServiceModel;

var direccion = new EndpointAddress("net.pipe://localhost/DemoIPC/Saludos");
using var fabrica = new ChannelFactory<IProcesoRemoto>(new NetNamedPipeBinding(), direccion);
var proxy = fabrica.CreateChannel();
Console.WriteLine(proxy.ObtenerSaludo("equipo"));

4. Sockets TCP

Brindan comunicación entre procesos locales o remotos y permiten definir protocolos propios.

Servidor TCP

using System.Net;
using System.Net.Sockets;
using System.Text;

var escucha = new TcpListener(IPAddress.Loopback, 9100);
escucha.Start();

using var conexion = await escucha.AcceptTcpClientAsync();
using var flujo = conexion.GetStream();

byte[] buffer = new byte[1024];
int leidos = await flujo.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, leidos));

string respuesta = "Respuesta TCP";
byte[] envio = Encoding.UTF8.GetBytes(respuesta);
await flujo.WriteAsync(envio, 0, envio.Length);
escucha.Stop();

Cliente TCP

using System.Net.Sockets;
using System.Text;

using var cliente = new TcpClient();
await cliente.ConnectAsync("127.0.0.1", 9100);
using var flujo = cliente.GetStream();

string consulta = "Consulta desde cliente";
byte[] envio = Encoding.UTF8.GetBytes(consulta);
await flujo.WriteAsync(envio, 0, envio.Length);

byte[] buffer = new byte[1024];
int leidos = await flujo.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, leidos));

5. MSMQ (Message Queuing)

Cola de mensajes persistente ideal para comunicación asíncrona y desacoplada.

Emisor

using System.Messaging;

string ruta = @".\Private$\ColaPedidos";
if (!MessageQueue.Exists(ruta)) MessageQueue.Create(ruta);

using var cola = new MessageQueue(ruta);
cola.Send("Pedido número 42", MessageQueueTransactionType.Single);

Receptor

using System.Messaging;

string ruta = @".\Private$\ColaPedidos";
using var cola = new MessageQueue(ruta);
cola.Formatter = new XmlMessageFormatter(new[] { typeof(string) });

cola.ReceiveCompleted += (s, e) =>
{
   var msg = cola.EndReceive(e.AsyncResult);
   Console.WriteLine($"Recibido: {msg.Body}");
   cola.BeginReceive();
};
cola.BeginReceive();
Console.ReadLine();

6. Vigilancia de archivos (FileSystemWatcher)

Suficiente para sincronización básica usando el sistema de archivos como intermediario.

Escritor

File.WriteAllText("compartido.txt", $"Actualización {DateTime.Now:HH:mm:ss}");

Observador

var vigilante = new FileSystemWatcher
{
   Path = AppDomain.CurrentDomain.BaseDirectory,
   Filter = "compartido.txt",
   NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
};

vigilante.Changed += (s, e) =>
{
   string texto = File.ReadAllText(e.FullPath);
   Console.WriteLine($"Archivo actualizado: {texto}");
};
vigilante.EnableRaisingEvents = true;
Console.ReadLine();

7. Mensajes Win32 (WM_COPYDATA)

Permiten enviar bloques de datos a una ventana específica mediante el API de Windows.

Emisor

using System.Runtime.InteropServices;
using System.Text;

const uint WM_COPYDATA = 0x004A;

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr FindWindow(string? lpClassName, string? lpWindowName);

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, ref DATOSCOPIA lParam);

[StructLayout(LayoutKind.Sequential)]
struct DATOSCOPIA
{
   public IntPtr dwData;
   public int cbData;
   public IntPtr lpData;
}

IntPtr hWnd = FindWindow(null, "VentanaDestino");
string mensaje = "Datos desde otro proceso";
IntPtr puntero = Marshal.StringToHGlobalAnsi(mensaje);

var datos = new DATOSCOPIA
{
   dwData = new IntPtr(1),
   cbData = Encoding.Default.GetByteCount(mensaje) + 1,
   lpData = puntero
};

SendMessage(hWnd, WM_COPYDATA, IntPtr.Zero, ref datos);
Marshal.FreeHGlobal(puntero);

Receptor en una aplicación Windows Forms

using System.Runtime.InteropServices;

const uint WM_COPYDATA = 0x004A;

[StructLayout(LayoutKind.Sequential)]
struct DATOSCOPIA
{
   public IntPtr dwData;
   public int cbData;
   public IntPtr lpData;
}

protected override void WndProc(ref Message m)
{
   if (m.Msg == WM_COPYDATA)
   {
       var datos = (DATOSCOPIA)m.GetLParam(typeof(DATOSCOPIA));
       string texto = Marshal.PtrToStringAnsi(datos.lpData) ?? string.Empty;
       Console.WriteLine(texto);
   }
   base.WndProc(ref m);
}

8. Mutex entre procesos

Sirve para coordinar el acceso a un recurso compartido y evitar que dos procesos entren simultáneamente a una sección crítica.

using var mutex = new Mutex(false, "Global\\MutexDemo");

mutex.WaitOne();
try
{
   // Acceso exclusivo al recurso compartido
}
finally
{
   mutex.ReleaseMutex();
}

Criterios de selección

Escenario Opción recomendada Razón
Comunicación local bidireccional Pipes con nombre Baja latencia y poca sobrecarga
Volúmenes grandes de datos Memoria mapeada Acceso directo sin copias intermedias
Procesos en distitnas máquinas TCP o WCF net.tcp Soporte de red y contratos
Mensajería asíncrona confiable MSMQ Persistencia y desacoplamiento
Sincronización simple Vigilante de archivos Fácil de implementar
Interacción entre ventanas WM_COPYDATA Envío directo a HWND
Exclusión mutua Mutex global Control de sección crítica

Comparación aproximada

Mecanismo Rendimiento (MB/s) Latencia Admite red
Pipes con nombre 150 - 250 0,05 - 0,5 ms No
Memoria mapeada 600 - 1000 < 0,01 ms No
TCP 100 - 180 0,5 - 3 ms
MSMQ 15 - 40 30 - 150 ms

Consejos de depuración

  • Pipes: use Process Explorer o Resource Monitor para comprobar que el canal está abierto y sin bloqueos.
  • TCP: en Wireshark filtre por tcp.port == 9100 para revisar el flujo de paquetes.
  • Archivos mapeados: utilice VMMap para observar la sección de memoria compartida.
  • FileSystemWatcher: emplee Process Monitor para detectar eventos de escritura duplicados o retrasados.

Aplicaciones avanzadas

  • Canal híbrido: usar TCP para señalización y memoria mapeada para el intercambio masivo de datos.
  • Seguridad en TCP: envolver el socket en un SslStream para cifrado TLS.
  • Protocolo propio: definir un encabezado de longitud en cada mensaje para evitar lecturas parciales.
  • MSMQ transaccional: agrupar envíos en una transacción para garantizar coherencia.

Etiquetas: C# .NET IPC Named Pipes Memory-Mapped Files

Publicado el 7-2 01:19