rcon client supports

This commit is contained in:
2026-09-04 01:27:18 +08:00
parent 350b900aaa
commit 817ec27813
27 files changed
+412 -93

No files matched your search

+35 -2
View File
@@ -1,6 +1,6 @@
# mcping
A lightweight motd ping library for kotlin multiplatform and java,
A lightweight motd ping / rcon client library for kotlin multiplatform and java,
no dependencies in `jvm` target except `kotlin-stdlib`
native platforms required `ktor-network` and `kotlinx-io`
@@ -23,12 +23,15 @@ repositories {
}
dependencies {
implementation("cn.rtast.mcping:mcping:0.0.2")
implementation("cn.rtast.mcping:mcping:0.0.3")
implementation("cn.rtast.mcping:rconlib:0.0.3")
}
```
> Get latest version at https://repo.rtast.cn/packages/-/cn.rtast.mcping:mcping/
# MC Ping
## Kotlin example
```kotlin
@@ -69,6 +72,36 @@ void main() {
> An example of java ping json response can be found at [ping-response-example](example/java-ping-response.json) (Formatted),
> a raw response of bedrock ping response can be found at [bedrock-pinng-raw-response](example/bedrock-pinng-raw-response.txt)
# rcon client
```kotlin
fun main() {
val host = "127.0.0.1"
val port = 25575
val client = rconClient(host, port)
val authed = client.connect("123456")
if (authed) println(client.command("list")) else throw IllegalArgumentException("incorrect password")
}
```
> also supports java
```java
void main() {
String host = "127.0.0.1";
int port = 25575;
String password = "123456";
RCONClient rconClient = Rconlib.rconClient(host, port);
boolean authed = rconClient.connect(password);
if (authed) {
System.out.println(rconClient.command("list"));
} else {
throw new IllegalStateException("incorrect password");
}
}
```
# Open Source
Open source under [Apache-2.0](LICENSE)
@@ -13,15 +13,18 @@ public expect class _Buffer {
public constructor(bytes: ByteArray)
public fun writeByte(value: Byte)
public fun writeShort(value: Short)
public fun writeLong(value: Long)
public fun writeShort(value: Short, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeInt(value: Int, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeLong(value: Long, endian: ByteOrder = ByteOrder.BIG_ENDIAN)
public fun writeBytes(bytes: ByteArray)
public fun readByte(): Byte
public fun readShort(): Short
public fun readLong(): Long
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
public fun readBytes(length: Int): ByteArray
public fun toByteArray(): ByteArray
public fun close()
public val size: Int
}
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.mcping.platform
public enum class ByteOrder {
BIG_ENDIAN, LITTLE_ENDIAN
}
@@ -10,9 +10,11 @@ package cn.rtast.mcping.platform
@Suppress("CLASSNAME")
public expect class _ReadChannel {
public fun readByte(): Byte
public fun readShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short
public fun readInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int
public fun readLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long
public fun readBytes(length: Int): ByteArray
public fun readFully(out: ByteArray, start: Int = 0, end: Int = out.size)
public fun readLong(): Long
}
@Suppress("CLASSNAME")
@@ -11,4 +11,4 @@ package cn.rtast.mcping.platform
* An object class used to pass some parameters
* that exists only on native targets
*/
public expect class PingContext()
public expect class MCPingContext()
@@ -8,14 +8,14 @@
package cn.rtast.mcping.platform
@Suppress("CLASSNAME")
public expect class _Socket public constructor(host: String, port: Int, context: PingContext) {
public expect class _Socket public constructor(host: String, port: Int, context: MCPingContext) {
public fun openReadChannel(): _ReadChannel
public fun openWriteChannel(): _WriteChannel
public fun close()
}
@Suppress("CLASSNAME")
public expect class _UdpSocket public constructor(host: String, port: Int, context: PingContext) {
public expect class _UdpSocket public constructor(host: String, port: Int, context: MCPingContext) {
public fun sendAndReceive(data: ByteArray): ByteArray
public fun close()
}
@@ -24,21 +24,37 @@ public actual class _Buffer {
outStream.write(value.toInt())
}
public actual fun writeShort(value: Short) {
public actual fun writeShort(value: Short, endian: ByteOrder) {
val v = value.toInt()
if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(v shr 8)
outStream.write(v)
} else {
outStream.write(v)
outStream.write(v shr 8)
}
}
public actual fun writeLong(value: Long) {
outStream.write((value shr 56).toInt())
outStream.write((value shr 48).toInt())
outStream.write((value shr 40).toInt())
outStream.write((value shr 32).toInt())
outStream.write((value shr 24).toInt())
outStream.write((value shr 16).toInt())
outStream.write((value shr 8).toInt())
outStream.write(value.toInt())
public actual fun writeInt(value: Int, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) {
outStream.write(value shr 24)
outStream.write(value shr 16)
outStream.write(value shr 8)
outStream.write(value)
} else {
outStream.write(value)
outStream.write(value shr 8)
outStream.write(value shr 16)
outStream.write(value shr 24)
}
}
public actual fun writeLong(value: Long, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) {
for (i in 56 downTo 0 step 8) outStream.write((value shr i).toInt())
} else {
for (i in 0..56 step 8) outStream.write((value shr i).toInt())
}
}
public actual fun writeBytes(bytes: ByteArray) {
@@ -60,23 +76,9 @@ public actual class _Buffer {
return array[readOffset++]
}
public actual fun readShort(): Short {
val b1 = readByte().toInt() and 0xFF
val b2 = readByte().toInt() and 0xFF
return ((b1 shl 8) or b2).toShort()
}
public actual fun readLong(): Long {
return (readByte().toLong() and 0xFF shl 56) or
(readByte().toLong() and 0xFF shl 48) or
(readByte().toLong() and 0xFF shl 40) or
(readByte().toLong() and 0xFF shl 32) or
(readByte().toLong() and 0xFF shl 24) or
(readByte().toLong() and 0xFF shl 16) or
(readByte().toLong() and 0xFF shl 8) or
(readByte().toLong() and 0xFF)
}
public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
public actual fun readBytes(length: Int): ByteArray {
val array = ensureReadArray()
if (readOffset + length > array.size) throw IndexOutOfBoundsException("Buffer underflow")
@@ -87,6 +89,6 @@ public actual class _Buffer {
public actual fun toByteArray(): ByteArray = outStream.toByteArray()
public actual val size: Int
get() = outStream.size()
public actual fun close(): Unit = outStream.close()
public actual val size: Int get() = outStream.size()
}
@@ -12,15 +12,11 @@ import java.io.InputStream
import java.io.OutputStream
@Suppress("CLASSNAME")
public actual class _ReadChannel {
private val _inputStream: InputStream
public constructor(inputStream: InputStream) {
_inputStream = inputStream
}
public actual class _ReadChannel(private val _inputStream: InputStream) {
public actual fun readByte(): Byte = _inputStream.read().toByte()
public actual fun readBytes(length: Int): ByteArray = _inputStream.readNBytes(length)
public actual fun readFully(out: ByteArray, start: Int, end: Int) {
var bytesRead = 0
val length = end - start
@@ -31,23 +27,9 @@ public actual class _ReadChannel {
}
}
public actual fun readLong(): Long {
val bytes = ByteArray(8)
var read = 0
while (read < 8) {
val count = _inputStream.read(bytes, read, 8 - read)
if (count == -1) throw EOFException()
read += count
}
return ((bytes[0].toLong() and 0xFF shl 56) or
(bytes[1].toLong() and 0xFF shl 48) or
(bytes[2].toLong() and 0xFF shl 40) or
(bytes[3].toLong() and 0xFF shl 32) or
(bytes[4].toLong() and 0xFF shl 24) or
(bytes[5].toLong() and 0xFF shl 16) or
(bytes[6].toLong() and 0xFF shl 8) or
(bytes[7].toLong() and 0xFF))
}
public actual fun readShort(endian: ByteOrder): Short = readBytes(2).toShort(endian)
public actual fun readInt(endian: ByteOrder): Int = readBytes(4).toInt(endian)
public actual fun readLong(endian: ByteOrder): Long = readBytes(8).toLong(endian)
}
@Suppress("CLASSNAME")
@@ -9,4 +9,4 @@ package cn.rtast.mcping.platform
/**
* No Context for jvm targets
*/
public actual class PingContext
public actual class MCPingContext
@@ -0,0 +1,40 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.mcping.platform
internal fun ByteArray.toShort(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Short {
val b1 = this[0].toInt() and 0xFF
val b2 = this[1].toInt() and 0xFF
return if (endian == ByteOrder.BIG_ENDIAN) {
((b1 shl 8) or b2).toShort()
} else {
((b2 shl 8) or b1).toShort()
}
}
internal fun ByteArray.toInt(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Int {
val b1 = this[0].toInt() and 0xFF
val b2 = this[1].toInt() and 0xFF
val b3 = this[2].toInt() and 0xFF
val b4 = this[3].toInt() and 0xFF
return if (endian == ByteOrder.BIG_ENDIAN) {
(b1 shl 24) or (b2 shl 16) or (b3 shl 8) or b4
} else {
(b4 shl 24) or (b3 shl 16) or (b2 shl 8) or b1
}
}
internal fun ByteArray.toLong(endian: ByteOrder = ByteOrder.BIG_ENDIAN): Long {
var result = 0L
if (endian == ByteOrder.BIG_ENDIAN) {
for (b in this) result = (result shl 8) or (b.toLong() and 0xFF)
} else {
for (i in 7 downTo 0) result = (result shl 8) or (this[i].toLong() and 0xFF)
}
return result
}
@@ -12,7 +12,7 @@ import java.net.InetSocketAddress
import java.net.Socket as JvmSocket
@Suppress("CLASSNAME")
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
public actual class _Socket public actual constructor(host: String, port: Int, context: MCPingContext) {
private val socket = JvmSocket(host, port)
public actual fun openReadChannel(): _ReadChannel = _ReadChannel(socket.getInputStream())
@@ -21,7 +21,7 @@ public actual class _Socket public actual constructor(host: String, port: Int, c
}
@Suppress("CLASSNAME")
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: MCPingContext) {
private val socket = DatagramSocket().apply {
soTimeout = 3000
connect(InetSocketAddress(host, port))
@@ -6,6 +6,7 @@
package cn.rtast.mcping.platform
import io.ktor.utils.io.bits.*
import kotlinx.io.Buffer
import kotlinx.io.readByteArray
@@ -22,16 +23,41 @@ public actual class _Buffer {
}
public actual fun writeByte(value: Byte): Unit = _delegateBuf.writeByte(value)
public actual fun writeShort(value: Short): Unit = _delegateBuf.writeShort(value)
public actual fun writeLong(value: Long): Unit = _delegateBuf.writeLong(value)
public actual fun writeShort(value: Short, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeShort(value)
else _delegateBuf.writeShort(value.reverseByteOrder())
}
public actual fun writeInt(value: Int, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeInt(value)
else _delegateBuf.writeInt(value.reverseByteOrder())
}
public actual fun writeLong(value: Long, endian: ByteOrder) {
if (endian == ByteOrder.BIG_ENDIAN) _delegateBuf.writeLong(value)
else _delegateBuf.writeLong(value.reverseByteOrder())
}
public actual fun writeBytes(bytes: ByteArray): Unit = _delegateBuf.write(bytes)
public actual fun readByte(): Byte = _delegateBuf.readByte()
public actual fun readShort(): Short = _delegateBuf.readShort()
public actual fun readLong(): Long = _delegateBuf.readLong()
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
public actual fun readShort(endian: ByteOrder): Short {
val v = _delegateBuf.readShort()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readInt(endian: ByteOrder): Int {
val v = _delegateBuf.readInt()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readLong(endian: ByteOrder): Long {
val v = _delegateBuf.readLong()
return if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readBytes(length: Int): ByteArray = _delegateBuf.readByteArray(length)
public actual fun toByteArray(): ByteArray = _delegateBuf.peek().readByteArray()
public actual fun close(): Unit = _delegateBuf.close()
public actual val size: Int
get() = _delegateBuf.size.toInt()
@@ -7,22 +7,31 @@
package cn.rtast.mcping.platform
import io.ktor.utils.io.*
import io.ktor.utils.io.bits.reverseByteOrder
import kotlinx.coroutines.runBlocking
@Suppress("CLASSNAME")
public actual class _ReadChannel {
private val _readChannel: ByteReadChannel
public constructor(readChannel: ByteReadChannel) {
_readChannel = readChannel
}
public actual class _ReadChannel(private val _readChannel: ByteReadChannel) {
public actual fun readByte(): Byte = runBlocking { _readChannel.readByte() }
public actual fun readBytes(length: Int): ByteArray = runBlocking { _readChannel.readByteArray(length) }
public actual fun readFully(out: ByteArray, start: Int, end: Int): Unit =
runBlocking { _readChannel.readFully(out, start, end) }
public actual fun readLong(): Long = runBlocking { _readChannel.readLong() }
public actual fun readShort(endian: ByteOrder): Short = runBlocking {
val v = _readChannel.readShort()
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readInt(endian: ByteOrder): Int = runBlocking {
val v = _readChannel.readInt()
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
public actual fun readLong(endian: ByteOrder): Long = runBlocking {
val v = _readChannel.readLong()
if (endian == ByteOrder.BIG_ENDIAN) v else v.reverseByteOrder()
}
}
@Suppress("CLASSNAME")
@@ -12,7 +12,7 @@ import io.ktor.network.selector.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
public actual class PingContext actual constructor() {
public actual class MCPingContext actual constructor() {
internal var _selectorManager: SelectorManager = SelectorManager(Dispatchers.IO)
internal var _autoCloseSelectorManager: Boolean = false
@@ -12,7 +12,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.io.readByteArray
@Suppress("CLASSNAME")
public actual class _Socket public actual constructor(host: String, port: Int, context: PingContext) {
public actual class _Socket public actual constructor(host: String, port: Int, context: MCPingContext) {
private val ctx = context
private val socket = runBlocking { aSocket(ctx._selectorManager).tcp().connect(host, port) }
@@ -27,7 +27,7 @@ public actual class _Socket public actual constructor(host: String, port: Int, c
}
@Suppress("CLASSNAME")
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: PingContext) {
public actual class _UdpSocket public actual constructor(host: String, port: Int, context: MCPingContext) {
private val ctx = context
// use bind to create an unconnected socket
+1 -1
View File
@@ -4,4 +4,4 @@ kotlin.native.enableKlibsCrossCompilation=true
kotlin.daemon.jvmargs=-Xmx2048M
org.gradle.jvmargs=-Xmx3g -Dfile.encoding=UTF-8
libVersion=0.0.2
libVersion=0.0.3
@@ -8,12 +8,12 @@
package cn.rtast.mcping.bedrock
import cn.rtast.mcping.PingResponse
import cn.rtast.mcping.platform.PingContext
import cn.rtast.mcping.platform.MCPingContext
import cn.rtast.mcping.platform._UdpSocket
import cn.rtast.mcping.platform.wrap
import kotlin.time.Clock
internal fun pingBedrockServer(host: String, port: Int, context: PingContext): PingResponse {
internal fun pingBedrockServer(host: String, port: Int, context: MCPingContext): PingResponse {
val socket = _UdpSocket(host, port, context)
return try {
val sendTime = Clock.System.now().toEpochMilliseconds()
@@ -8,11 +8,11 @@
package cn.rtast.mcping.java
import cn.rtast.mcping.PingResponse
import cn.rtast.mcping.platform.PingContext
import cn.rtast.mcping.platform.MCPingContext
import cn.rtast.mcping.platform._Socket
import kotlin.time.Clock
internal fun pingJavaServer(host: String, port: Int, context: PingContext): PingResponse {
internal fun pingJavaServer(host: String, port: Int, context: MCPingContext): PingResponse {
val socket = _Socket(host, port, context)
val receiveChannel = socket.openReadChannel()
val sendChannel = socket.openWriteChannel()
@@ -11,7 +11,7 @@ package cn.rtast.mcping
import cn.rtast.mcping.bedrock.pingBedrockServer
import cn.rtast.mcping.java.pingJavaServer
import cn.rtast.mcping.platform.PingContext
import cn.rtast.mcping.platform.MCPingContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
@@ -20,7 +20,7 @@ public fun mcping(
host: String,
port: Int,
type: ServerType = ServerType.Java,
context: PingContext = PingContext(),
context: MCPingContext = MCPingContext(),
): PingResponse = when (type) {
ServerType.Java -> pingJavaServer(host, port, context)
ServerType.Bedrock -> pingBedrockServer(host, port, context)
@@ -9,7 +9,7 @@ package test
import cn.rtast.mcping.ServerType
import cn.rtast.mcping.mcping
import cn.rtast.mcping.platform.PingContext
import cn.rtast.mcping.platform.MCPingContext
import io.ktor.network.selector.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
@@ -22,7 +22,7 @@ class TestMingwPing {
fun `test ping java on mingw with selectorManager`() = runTest {
val sm = SelectorManager(Dispatchers.IO)
val response =
mcping(host = "org.mc-complex.com", port = 25565, type = ServerType.Java, context = PingContext(sm))
mcping(host = "org.mc-complex.com", port = 25565, type = ServerType.Java, context = MCPingContext(sm))
println(response)
}
@@ -30,7 +30,7 @@ class TestMingwPing {
fun `test ping bedrock on mingw with selectorManager`() = runTest {
val sm = SelectorManager(Dispatchers.IO)
val response =
mcping(host = "play.wildnetwork.net", port = 19132, type = ServerType.Bedrock, context = PingContext(sm))
mcping(host = "play.wildnetwork.net", port = 19132, type = ServerType.Bedrock, context = MCPingContext(sm))
println(response)
println(response.toBedrockResponse())
}
+4
View File
@@ -16,6 +16,10 @@ kotlin {
implementation(project(":common"))
}
jvmMain.dependencies {
}
commonTest.dependencies {
implementation(kotlin("test"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.11.0")
@@ -0,0 +1,12 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.mcping.rconlib
import cn.rtast.mcping.platform._Buffer
internal fun _Buffer.writeNull() = writeByte(0x00)
@@ -0,0 +1,83 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.mcping.rconlib
import cn.rtast.mcping.platform.ByteOrder
import cn.rtast.mcping.platform._Buffer
import cn.rtast.mcping.platform._ReadChannel
import cn.rtast.mcping.platform._WriteChannel
internal abstract class Packet {
abstract val requestId: Int
abstract val type: Int
abstract val payload: String
protected val payloadBytes: ByteArray by lazy { payload.encodeToByteArray() }
val payloadLength: Int
get() = payloadBytes.size
open val length: Int
get() = 4 + 4 + payloadLength + 2
fun writePayload(buf: _Buffer) {
buf.writeInt(length, ByteOrder.LITTLE_ENDIAN)
buf.writeInt(requestId, ByteOrder.LITTLE_ENDIAN)
buf.writeInt(type, ByteOrder.LITTLE_ENDIAN)
buf.writeBytes(payloadBytes)
buf.writeNull()
buf.writeNull()
}
companion object Codec {
fun decode(channel: _ReadChannel): ResponsePacket {
val length = channel.readInt(ByteOrder.LITTLE_ENDIAN)
val requestId = channel.readInt(ByteOrder.LITTLE_ENDIAN)
val type = channel.readInt(ByteOrder.LITTLE_ENDIAN)
val payloadBuffer = _Buffer()
while (true) {
val b = channel.readByte()
if (b == 0x00.toByte()) break
payloadBuffer.writeByte(b)
}
channel.readByte() // consume last null byte
return ResponsePacket(length, requestId, type, payloadBuffer.toByteArray().decodeToString())
}
}
}
internal data class AuthPacket(
override val payload: String,
override val requestId: Int = 1,
) : Packet() {
override val type: Int = PacketType.AUTH
}
internal data class ExecCommandPacket(
override val payload: String,
override val requestId: Int,
) : Packet() {
override val type: Int = PacketType.EXEC_COMMAND
}
internal data class ResponsePacket(
override val length: Int,
override val requestId: Int,
override val type: Int,
override val payload: String,
) : Packet()
internal fun _WriteChannel.sendPacket(packet: Packet) {
val buf = _Buffer()
packet.writePayload(buf)
writeFully(buf.toByteArray())
flush()
}
internal fun _ReadChannel.readPacket(): ResponsePacket = Packet.decode(this)
@@ -0,0 +1,14 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package cn.rtast.mcping.rconlib
internal object PacketType {
const val AUTH = 3
const val EXEC_COMMAND = 2
const val RESPONSE = 1
}
@@ -4,6 +4,51 @@
* Date: 2026/9/4
*/
@file:JvmName("Rconlib")
package cn.rtast.mcping.rconlib
import cn.rtast.mcping.platform.MCPingContext
import cn.rtast.mcping.platform._ReadChannel
import cn.rtast.mcping.platform._Socket
import cn.rtast.mcping.platform._WriteChannel
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
public class RCONClient internal constructor(
private val host: String,
private val port: Int,
private val context: MCPingContext,
) : AutoCloseable {
private var socket: _Socket? = null
private var readChannel: _ReadChannel? = null
private var writeChannel: _WriteChannel? = null
private var currentRequestId = 1
public fun connect(password: String): Boolean {
socket = _Socket(host, port, context)
readChannel = socket!!.openReadChannel()
writeChannel = socket!!.openWriteChannel()
val authPacket = AuthPacket(password, currentRequestId)
writeChannel!!.sendPacket(authPacket)
return readChannel!!.readPacket().requestId != -1
}
public fun command(command: String): String {
val reqId = currentRequestId++
val commandPacket = ExecCommandPacket(command, reqId)
writeChannel!!.sendPacket(commandPacket)
return readChannel!!.readPacket().payload
}
public override fun close() {
socket?.close()
socket = null
readChannel = null
writeChannel = null
}
}
@JvmOverloads
public fun rconClient(host: String, port: Int, context: MCPingContext = MCPingContext()): RCONClient =
RCONClient(host, port, context)
@@ -0,0 +1,23 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package test
import cn.rtast.mcping.rconlib.rconClient
import kotlin.test.Test
class TestRCON {
@Test
fun `test rcon client`() {
val host = "127.0.0.1"
val port = 25575
val client = rconClient(host, port)
val authed = client.connect("123456")
if (authed) println(client.command("list")) else throw IllegalArgumentException("incorrect password")
}
}
@@ -0,0 +1,29 @@
/*
* Copyright © 2026 RTAkland
* Author: RTAkland
* Date: 2026/9/4
*/
package test;
import cn.rtast.mcping.rconlib.RCONClient;
import cn.rtast.mcping.rconlib.Rconlib;
import org.junit.Test;
public class TestRconClientOnJava {
@Test
public void testRconClient() {
String host = "127.0.0.1";
int port = 25575;
String password = "123456";
RCONClient rconClient = Rconlib.rconClient(host, port);
boolean authed = rconClient.connect(password);
if (authed) {
System.out.println(rconClient.command("list"));
} else {
throw new IllegalStateException("incorrect password");
}
}
}