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 | Sí |
| MSMQ | 15 - 40 | 30 - 150 ms | Sí |
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 == 9100para 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
SslStreampara 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.