Cargando...

Networking en C# (TcpClient, HttpClient, llamadas REST API)

Aprende networking en C# con TcpClient, HttpClient y llamadas REST API para gestionar comunicación y datos externos.

En las aplicaciones modernas, la comunicación en red juega un papel fundamental. En C#, se puede comunicar tanto con sockets TCP de bajo nivel como con llamadas HTTP/REST API de alto nivel. .NET ofrece clases potentes para estas tareas: TcpClient, TcpListener y HttpClient. En este artículo veremos paso a paso ejemplos que van desde la programación de red básica hasta las llamadas a APIs REST.


1. ¿Qué es TCP?

TCP (Transmission Control Protocol) es un protocolo de comunicación orientado a la conexión y confiable. Los paquetes de datos se envían en orden y los paquetes con errores se retransmiten. TCP funciona con el modelo cliente–servidor: una parte escucha (TcpListener) y la otra se conecta (TcpClient).


2. Servidor simple con TcpListener

El siguiente ejemplo muestra un servidor TCP que escucha conexiones entrantes en el puerto 5000.


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpServer
{
    public static async Task StartAsync()
    {
        var listener = new TcpListener(IPAddress.Loopback, 5000);
        listener.Start();
        Console.WriteLine("Servidor iniciado. Escuchando en el puerto 5000...");

        while (true)
        {
            using TcpClient client = await listener.AcceptTcpClientAsync();
            Console.WriteLine("¡Nuevo cliente conectado!");

            using var stream = client.GetStream();
            using var reader = new StreamReader(stream, Encoding.UTF8);
            using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };

            string? message = await reader.ReadLineAsync();
            Console.WriteLine($"Mensaje recibido: {message}");

            await writer.WriteLineAsync($"Respuesta del servidor: {message?.ToUpper()}");
        }
    }

    static async Task Main() => await StartAsync();
}

El servidor lee el mensaje, lo convierte a mayúsculas y lo devuelve. IPAddress.Loopback representa solo la máquina local.


3. Cliente con TcpClient

En el lado del cliente, podemos usar TcpClient para conectarnos al servidor:


using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class TcpClientApp
{
    static async Task Main()
    {
        using var client = new TcpClient();
        await client.ConnectAsync("127.0.0.1", 5000);

        using var stream = client.GetStream();
        using var writer = new StreamWriter(stream, Encoding.UTF8) { AutoFlush = true };
        using var reader = new StreamReader(stream, Encoding.UTF8);

        Console.Write("Mensaje: ");
        string message = Console.ReadLine()!;
        await writer.WriteLineAsync(message);

        string response = await reader.ReadLineAsync() ?? "";
        Console.WriteLine($"Respuesta del servidor: {response}");
    }
}

Una vez que el servidor esté en ejecución, inicie este cliente. El mensaje enviado se devolverá en mayúsculas.


4. Llamadas Web y REST API con HttpClient

Hoy en día, la forma más común de comunicación en red se basa en servicios REST sobre HTTP. En .NET, la clase HttpClient se utiliza para este propósito. Funciona de forma asíncrona, es reutilizable y es ideal para el intercambio de datos en formato JSON.


using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;

class HttpClientExample
{
    static readonly HttpClient http = new HttpClient();

    static async Task Main()
    {
        var url = "https://jsonplaceholder.typicode.com/posts/1";
        Console.WriteLine($"GET {url}");

        var response = await http.GetAsync(url);
        string json = await response.Content.ReadAsStringAsync();

        var post = JsonSerializer.Deserialize<Post>(json);
        Console.WriteLine($"Título: {post?.Title}");
    }

    public class Post
    {
        public int Id { get; set; }
        public string? Title { get; set; }
        public string? Body { get; set; }
    }
}

Este ejemplo obtiene datos JSON de una API REST y los deserializa en una clase C# (Post). JsonSerializer es la biblioteca JSON integrada en .NET.


5. Enviar una solicitud POST

Con HttpClient, puede enviar una solicitud POST creando manualmente el contenido JSON o usando serialización.


using System.Net.Http;
using System.Text;
using System.Text.Json;

var http = new HttpClient();

var newPost = new
{
    title = "Nueva publicación",
    body = "¡Hola mundo!",
    userId = 1
};

var json = JsonSerializer.Serialize(newPost);
var content = new StringContent(json, Encoding.UTF8, "application/json");

var response = await http.PostAsync("https://jsonplaceholder.typicode.com/posts", content);
Console.WriteLine($"Estado: {response.StatusCode}");

string result = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Respuesta: {result}");

Este ejemplo demuestra la forma básica de enviar datos a una API REST. En aplicaciones reales, es importante usar el patrón async/await para mantener la capacidad de respuesta.


6. Ciclo de vida de HttpClient (Buenas prácticas)

Crear una nueva instancia de HttpClient para cada solicitud provoca un uso excesivo de recursos. El enfoque correcto es usar una sola instancia (singleton):


// Program.cs
builder.Services.AddHttpClient<IMyApiService, MyApiService>();

// Servicio
public class MyApiService : IMyApiService
{
    private readonly HttpClient _http;
    public MyApiService(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("https://api.example.com/");
    }

    public async Task<User?> GetUserAsync(int id)
    {
        return await _http.GetFromJsonAsync<User>($"users/{id}");
    }
}

En ASP.NET Core, HttpClientFactory gestiona automáticamente el agrupamiento de conexiones (connection pooling).


7. Manejo de errores y configuración de tiempo de espera

En las llamadas HTTP, se deben manejar los errores, los tiempos de espera y las excepciones inesperadas.


var http = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };

try
{
    var response = await http.GetAsync("https://api.github.com/repos/dotnet/runtime");
    response.EnsureSuccessStatusCode(); // Lanza excepción si el estado no está entre 200–299

    string content = await response.Content.ReadAsStringAsync();
    Console.WriteLine("Solicitud exitosa: " + content.Length + " bytes");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"Error HTTP: {ex.Message}");
}
catch (TaskCanceledException)
{
    Console.WriteLine("¡Tiempo de espera excedido!");
}

8. SSL, encabezados y uso de tokens

La mayoría de las API modernas utilizan el encabezado Authorization para autenticación. Puede agregar fácilmente encabezados personalizados y configuraciones especiales:


var http = new HttpClient();
http.DefaultRequestHeaders.Add("Authorization", "Bearer <token>");
http.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");

var res = await http.GetAsync("https://api.github.com/user");
Console.WriteLine(res.StatusCode);

Las conexiones HTTPS son seguras por defecto. En entornos de desarrollo (por ejemplo, certificados autofirmados), puede configurar un controlador personalizado.


9. Flujo asíncrono para grandes volúmenes de datos

Para archivos grandes o transmisiones de datos, puede leer directamente desde el Stream sin cargarlo todo en memoria:


using var http = new HttpClient();
using var response = await http.GetAsync("https://example.com/bigfile.zip", HttpCompletionOption.ResponseHeadersRead);

await using var stream = await response.Content.ReadAsStreamAsync();
using var file = File.Create("descargado.zip");

await stream.CopyToAsync(file);
Console.WriteLine("Archivo descargado.");

Este método utiliza la memoria de manera eficiente, ya que el archivo no se carga completamente en la RAM.


10. Ejemplo: Consumo de una API REST (WeatherService)

El siguiente ejemplo obtiene datos de una API de clima y los convierte en un modelo.


public class WeatherInfo
{
    public string? City { get; set; }
    public float Temperature { get; set; }
    public string? Condition { get; set; }
}

public class WeatherService
{
    private readonly HttpClient _http;
    public WeatherService(HttpClient http)
    {
        _http = http;
        _http.BaseAddress = new Uri("https://api.weatherapi.com/v1/");
    }

    public async Task<WeatherInfo?> GetAsync(string city)
    {
        var key = "<your-api-key>";
        string url = $"current.json?key={key}&q={city}&aqi=no";

        var res = await _http.GetAsync(url);
        res.EnsureSuccessStatusCode();

        using var s = await res.Content.ReadAsStreamAsync();
        using var doc = await JsonDocument.ParseAsync(s);

        return new WeatherInfo
        {
            City = city,
            Temperature = doc.RootElement.GetProperty("current").GetProperty("temp_c").GetSingle(),
            Condition = doc.RootElement.GetProperty("current").GetProperty("condition").GetProperty("text").GetString()
        };
    }
}

Esta estructura sigue los principios de la Clean Architecture y utiliza Inyección de Dependencias a través de HttpClientFactory.


11. Rendimiento y mejores prácticas


Resumen (TL;DR)

  • TcpClient/TcpListener: Proporcionan comunicación TCP de bajo nivel basada en conexión.
  • HttpClient: Cliente de alto nivel para comunicarse con APIs REST.
  • GetAsync, PostAsync: Envían solicitudes HTTP; la conversión JSON se realiza con JsonSerializer.
  • HttpClientFactory: Recomendado para la gestión de instancias y mejora del rendimiento.
  • Operaciones asíncronas: Use await, maneje Timeout y excepciones correctamente.

Artículos relacionados