Сетевое программирование на C#: от сокетов до веб-API

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Погружение в сетевые глубины: почему C# — идеальный выбор

Когда мы говорим о сетевом программировании, многие разработчики представляют себе тонны низкоуровневого кода на C или бесконечные конфиги на Python. Но C# с его элегантным синтаксисом и мощной стандартной библиотекой превращает общение между приложениями в настоящее удовольствие. Забудьте о скучных мануалах — сегодня мы разберём, как создать полноценный TCP-чат, асинхронный клиент-сервер и даже REST API, используя лишь встроенные средства .NET.

На pythonlib.ru мы привыкли к Python, но C# в области сетевых взаимодействий даёт фору многим языкам. Его асинхронная модель (async/await) и встроенная поддержка сокетов позволяют писать код, который легко масштабируется от простого консольного приложения до высоконагруженного сервера. Готовы? Тогда запускаем Visual Studio и начинаем!

Основы: что такое сокет и как его приготовить

Сокет (socket) — это программный интерфейс для обмена данными между процессами, которые могут быть как на одной машине, так и в разных концах света. В C# работа с сокетами начинается с пространства имён System.Net.Sockets. Самая популярная пара — TCP-сокеты, обеспечивающие надёжную доставку данных.

Представьте, что сокет — это дверь. Одна сторона (сервер) открывает дверь и ждёт, пока кто-то постучит. Другая сторона (клиент) стучится, и после рукопожатия они начинают обмениваться сообщениями. В C# это выглядит так:

// Серверная часть
using System.Net;
using System.Net.Sockets;
using System.Text;

TcpListener server = new TcpListener(IPAddress.Any, 8888);
server.Start();
Console.WriteLine("Сервер запущен, жду подключений...");

TcpClient client = await server.AcceptTcpClientAsync();
NetworkStream stream = client.GetStream();

byte[] buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Получено: {message}");

// Ответ клиенту
byte[] response = Encoding.UTF8.GetBytes("Привет от сервера!");
await stream.WriteAsync(response, 0, response.Length);

Этот код — база. Он синхронный, но уже даёт представление о том, как работает сетевое программирование на C#. Обратите внимание: мы используем async и await — это ключ к производительности.

Клиент-серверное общение: первый контакт

Клиентская сторона не менее проста. Вот пример TCP-клиента, который подключается к нашему серверу:

// Клиент
TcpClient client = new TcpClient();
await client.ConnectAsync("127.0.0.1", 8888);
NetworkStream stream = client.GetStream();

byte[] data = Encoding.UTF8.GetBytes("Всем привет!");
await stream.WriteAsync(data, 0, data.Length);

byte[] buffer = new byte[1024];
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
Console.WriteLine($"Ответ: {Encoding.UTF8.GetString(buffer, 0, bytesRead)}");
  • IPAddress.Any — слушаем на всех сетевых интерфейсах.
  • Port 8888 — любой свободный порт выше 1024.
  • NetworkStream — поток для чтения/записи.

Важный нюанс: не забывайте закрывать соединения. В реальных проектах используйте using или try-finally.

Асинхронное программирование: как не повесить сервер

Синхронный код хорош для обучения, но в бою он заблокирует поток на время ожидания. Представьте, что к вашему серверу подключаются 100 клиентов — каждый будет ждать своей очереди. Решение — асинхронное сетевое программирование на C#.

Методы с суффиксом Async (например, AcceptTcpClientAsync) не блокируют поток. Они возвращают управление сразу, а когда данные приходят — возобновляют выполнение. Это позволяет обрабатывать тысячи подключений без создания тысяч потоков.

Вот пример полноценного асинхронного TCP-сервера, который может обслуживать несколько клиентов одновременно:

public class AsyncTcpServer
{
    private TcpListener _listener;
    private CancellationTokenSource _cts;

    public async Task StartAsync(int port)
    {
        _listener = new TcpListener(IPAddress.Any, port);
        _listener.Start();
        _cts = new CancellationTokenSource();
        Console.WriteLine($"Сервер запущен на порту {port}");

        while (!_cts.Token.IsCancellationRequested)
        {
            var client = await _listener.AcceptTcpClientAsync();
            _ = HandleClientAsync(client, _cts.Token); // Fire and forget
        }
    }

    private async Task HandleClientAsync(TcpClient client, CancellationToken token)
    {
        using (client)
        using (var stream = client.GetStream())
        {
            byte[] buffer = new byte[1024];
            while (!token.IsCancellationRequested)
            {
                int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, token);
                if (bytesRead == 0) break; // Клиент отключился
                string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                Console.WriteLine($"Клиент: {message}");
                // Эхо-ответ
                await stream.WriteAsync(buffer, 0, bytesRead, token);
            }
        }
    }

    public void Stop()
    {
        _cts?.Cancel();
        _listener?.Stop();
    }
}

Здесь ключевой момент — _ = HandleClientAsync. Мы не ждём завершения задачи, а запускаем её в фоне. Это настоящая магия асинхронного программирования.

Обработка ошибок и тайм-ауты

В сетевом программировании ошибки неизбежны. Клиент может отключиться, сеть — упасть, порт — быть занят. Всегда оборачивайте сетевые вызовы в try-catch:

try
{
    await client.ConnectAsync("192.168.1.1", 80);
}
catch (SocketException ex)
{
    Console.WriteLine($"Ошибка подключения: {ex.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Тайм-аут подключения");
}

Для тайм-аутов используйте CancellationTokenSource с задержкой:

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
await client.ConnectAsync(host, port, cts.Token);

Работа с HTTP: от простого запроса до REST API

Современные приложения редко используют чистые сокеты — чаще мы работаем с HTTP. В C# есть HttpClient — мощный и удобный класс для отправки запросов. Он создан для асинхронной работы и поддерживает все методы: GET, POST, PUT, DELETE.

Вот как выглядит простой GET-запрос к публичному API:

using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri("https://api.github.com");
httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("My                

Блоги

Рекомендаций по книгам