Перейти к основному содержимому

Вход на сервер

Соединение открывается через MinecraftClient и MinecraftClientOptions. Опции задают адрес, тайминги и, при необходимости, прокси; ConnectAsync резолвит адрес и открывает сокет. Отправка первых пакетов - handshaking и login

  • остаётся за вызывающим кодом.

Опции и ConnectAsync

MinecraftClientOptions.Host обязателен, Port по умолчанию 25565. ConnectTimeout (30 секунд по умолчанию) ограничивает весь ConnectAsync целиком, включая SRV-поиск и открытие сокета. LocalEndPoint привязывает исходящий сокет к конкретному интерфейсу и порту, NoDelay (включён по умолчанию) отключает алгоритм Нейгла. Оба поля действуют только на прямом TCP-соединении, не через прокси.

var options = new MinecraftClientOptions
{
Host = "play.example.com",
ConnectTimeout = TimeSpan.FromSeconds(15),
};

await using var client = new MinecraftClient(options);
await client.ConnectAsync(cancellationToken);

Токен отмены останавливает попытку раньше ConnectTimeout. По завершении ConnectAsync пакеты ещё не отправлены - клиент только открыл соединение.

Поиск сервера по SRV-записи

Сервер редко слушает порт 25565 на самом домене: обычно там DNS-запись _minecraft._tcp.<host>, которая указывает на настоящие хост и порт. Ванильный клиент ищет эту запись перед подключением, и ConnectAsync делает то же самое, когда UseSrv включён (по умолчанию да), Port остался равен 25565 и Host не IP-литерал. Поиск ограничен SrvTimeout (5 секунд по умолчанию, не длиннее ConnectTimeout); если записи нет или тайм-аут истёк, ConnectAsync подключается к Host:Port, как они заданы в опциях, - это не ошибка.

Тот же поиск доступен отдельно, через SrvResolver:

var record = await SrvResolver.ResolveAsync("play.example.com");
if (record is { } srv)
Console.WriteLine($"{srv.Target}:{srv.Port}");

SrvResult несёт Target, Port, Priority и Weight - четыре поля из RFC 2782. Когда записей несколько, ConnectAsync и ResolveAsync выбирают одну сами: сначала по наименьшему Priority, затем взвешенным выбором по Weight внутри этой группы.

Прокси

Сокет можно открыть не напрямую: MinecraftClientOptions.Proxy принимает IProxyClient, и ConnectAsync просит у него поток к уже резолвленным хосту и порту вместо того, чтобы открывать TcpClient самому. NoDelay и LocalEndPoint в этом случае не действуют - прокси-клиент владеет своим сокетом и настраивает его сам. Реализации берутся из QuickProxyNet - отдельной библиотеки без зависимостей. Она понимает HTTP CONNECT и SOCKS4/4a/5, а из современного - VLESS, VMess и Trojan; QUIC и Shadowsocks не умеет.

var options = new MinecraftClientOptions
{
Host = "play.example.com",
Proxy = proxyClient,
};

Поиск серверов в локальной сети

Открытый мир в локальной сети раз в полторы секунды рассылает широковещательное объявление на 224.0.2.60:4445 в формате [MOTD]…[/MOTD][AD]…[/AD]. LanServerDetector слушает эту группу и разбирает объявления в LanServer - MOTD и адрес, на котором сервер принимает соединения.

var found = await LanServerDetector.DiscoverAsync(TimeSpan.FromSeconds(3));
foreach (var server in found)
Console.WriteLine($"{server.Motd} -> {server.EndPoint}");

DiscoverAsync слушает заданное окно времени и убирает повторы одного мира по адресу. ListenAsync отдаёт объявления по мере поступления, без дедупликации - для списка, который должен обновляться на лету.

Что уходит на сервер сразу после соединения

Первым уходит handshaking-пакет с протокольной версией, адресом и портом сервера, вторым - заявка на вход в login. Пример с кодом - в «Первом боте».

Одно соединение на один клиент

MinecraftClient одноразовый. Флаг подключения взводится при первом успешном ConnectAsync и снимается только тогда, когда подключиться не удалось; после разрыва повторный ConnectAsync на том же экземпляре бросает InvalidOperationException. Переподключение - это новый клиент, а вместе с ним и новый обработчик: старый помнит фазу, в которой оборвалась прошлая сессия.

Дальше сервер отвечает уже в login: сжатием, шифром или сразу успехом. Порядок пакетов и переходы между фазами - в «Фазах протокола», включение шифра и сжатия - в «Сжатии и шифровании». Пример целиком, вместе с ключевым обменом и первым выходом в play, - на странице «Первый бот».