Joining a server
The connection opens through
MinecraftClient and
MinecraftClientOptions.
The options set the address, the timeouts, and, if needed, a proxy.
ConnectAsync resolves the address and opens the socket. Sending the first
packets - handshaking and login - stays the job of the calling code.
Options and ConnectAsync
MinecraftClientOptions.Host is required. Port defaults to 25565.
ConnectTimeout (30 seconds by default) limits the whole ConnectAsync call,
including the SRV lookup and the socket open. LocalEndPoint binds the outgoing
socket to a specific interface and port. NoDelay (on by default) disables the
Nagle algorithm. Both fields apply only to a direct TCP connection, not through
a proxy.
var options = new MinecraftClientOptions
{
Host = "play.example.com",
ConnectTimeout = TimeSpan.FromSeconds(15),
};
await using var client = new MinecraftClient(options);
await client.ConnectAsync(cancellationToken);
A cancellation token stops the attempt before ConnectTimeout. When
ConnectAsync returns, no packets have gone out yet - the client has only
opened the connection.
Finding a server by SRV record
A server rarely listens on port 25565 on the domain itself. Usually a DNS
record, _minecraft._tcp.<host>, points to the real host and port. The vanilla
client looks up this record before connecting, and ConnectAsync does the same
when UseSrv is on (the default), Port is still 25565, and Host is not an
IP literal. The lookup is limited by SrvTimeout (5 seconds by default, no
longer than ConnectTimeout). If the record does not exist or the lookup times
out, ConnectAsync connects to Host:Port as given in the options - this is
not an error.
The same lookup is also available on its own, through
SrvResolver:
var record = await SrvResolver.ResolveAsync("play.example.com");
if (record is { } srv)
Console.WriteLine($"{srv.Target}:{srv.Port}");
SrvResult carries Target,
Port, Priority, and Weight - the four fields from RFC 2782. When several
records exist, ConnectAsync and ResolveAsync pick one on their own: first by
the lowest Priority, then by a weighted pick on Weight within that group.
Proxy
The socket does not have to open directly. MinecraftClientOptions.Proxy
accepts an IProxyClient, and ConnectAsync asks it for a stream to the
already resolved host and port instead of opening a TcpClient itself.
NoDelay and LocalEndPoint do not apply in this case - the proxy client owns
its socket and configures it on its own. Implementations come from
QuickProxyNet, a separate library
with no dependencies. It supports HTTP CONNECT and SOCKS4/4a/5, and, among newer
protocols, VLESS, VMess, and Trojan. It does not support QUIC or Shadowsocks.
var options = new MinecraftClientOptions
{
Host = "play.example.com",
Proxy = proxyClient,
};
Discovering servers on a local network
A world open on a local network broadcasts an announcement to 224.0.2.60:4445
every second and a half, in the format [MOTD]…[/MOTD][AD]…[/AD].
LanServerDetector
listens on this group and parses the announcements into
LanServer - the MOTD and the
address where the server accepts connections.
var found = await LanServerDetector.DiscoverAsync(TimeSpan.FromSeconds(3));
foreach (var server in found)
Console.WriteLine($"{server.Motd} -> {server.EndPoint}");
DiscoverAsync listens for a set time window and removes duplicate
announcements from the same world by address. ListenAsync yields announcements
as they arrive, without deduplication - for a list that must update on the fly.
What goes to the server right after connecting
The first packet out is the handshaking packet with the protocol version and the server address and port. The second is the login start request. A code example is in First bot.
One connection per client
MinecraftClient is single-use. The connected flag is set on the first
successful ConnectAsync and is cleared only when the connection attempt fails.
After a disconnect, a second ConnectAsync call on the same instance throws
InvalidOperationException. Reconnecting means a new client, and with it a new
handler - the old one still remembers the phase where the last session broke
off.
From here the server answers in login: with compression, with encryption, or with an immediate success. The packet order and the transitions between phases are in Phase and direction. Turning on encryption and compression is in Encryption and compression. The full example, including the key exchange and the first move into play, is on the First bot page.