Delphi Indy TCP 连接到 RECON
Delphi Indy TCP connection to RECON
我正在尝试弄清楚如何使用远程控制台建立连接和身份验证。
这个 Wiki Wiki 1 and this one Wiki 2 告诉我我需要构建一个数据包并将其发送到 RECON,但我不知道该怎么做..
我是网络新手,但由于我在那里搜索,所以我构建了这个:
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Host:= '127.0.0.1';
IdTCPClient1.Port:= 20001;
IdTCPClient1.Connect;
IdTcpClient1.IOHandler.Writeln('1234');
ShowMessage(IdTcpClient1.IOHandler.ReadLn);
end;
我卡在那里了,1234 是 RECON 密码和消息return:连接正常关闭...
最后,如何才能登录成功呢?并且至少发送一个命令 "list" 下一步将实时接收控制台日志?
谢谢
您的代码未实现 Minecraft 命令 运行 之上的 Source RECON protocol。您不能只向服务器发送任意数据,它必须被正确地框起来。
尝试更像这样的东西:
const
SERVERDATA_AUTH = 3;
SERVERDATA_AUTH_RESPONSE = 2;
SERVERDATA_EXECCOMMAND = 2;
SERVERDATA_RESPONSE_VALUE = 0;
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;
IdTCPClient1.Connect;
SendRECONLogin('1234');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPClient1.Disconnect;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
SendRECONCommand('list');
end;
procedure TForm1.IdTCPClient1Connect(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure TForm1.IdTCPClient1Disconnect(Sender: TObject);
begin
Timer1.Enabled := False;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
RespID: Int32;
PktType: Int32;
Payload: string;
begin
try
if not IdTCPClient1.Connected then
Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
IdTCPClient1.IOHandler.CheckForDataOnSource(0);
IdTCPClient1.IOHandler.CheckForDisconnect(True, False);
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
end;
RespID := ReadRECONPacket(PktType, Payload);
case PktType of
SERVERDATA_AUTH_RESPONSE: begin
if RespID = -1 then begin
// authentication failed...
IdTCPClient1.Disconnect;
end else begin
// authentication successful...
end;
end;
SERVERDATA_RESPONSE_VALUE: begin
// match RespID to previously sent ReqID
// and handle Payload as needed...
end;
end;
except
IdTCPClient1.Disconnect;
end;
end;
var
gReqID: Int32 = 0;
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
var
Bytes: TIdBytes;
begin
Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
try
if gReqID < MaxInt then Inc(gReqID)
else gReqID := 1;
Result := gReqID;
IdTCPClient1.IOHandler.WriteBufferOpen;
try
IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
IdTCPClient1.IOHandler.Write(Result, False);
IdTCPClient1.IOHandler.Write(PktType, False);
IdTCPClient1.IOHandler.Write(Bytes);
IdTCPClient1.IOHandler.Write(UInt16(0), False);
IdTCPClient1.IOHandler.WriteBufferClose;
except
IdTCPClient1.IOHandler.WriteBufferCancel;
raise;
end;
except
IdTCPClient1.Disconnect;
raise;
end;
end;
function TForm1.SendRECONLogin(const Password: String): Int32;
begin
Result := SendRECONPacket(SERVERDATA_AUTH, Password);
end;
function TForm1.SendRECONCommand(const Cmd: String): Int32;
begin
Result := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
end;
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
var
Len: Int32;
begin
try
Len := IdTCPClient1.IOHandler.ReadInt32(False);
Result := IdTCPClient1.IOHandler.ReadInt32(False);
PktType := IdTCPClient1.IOHandler.ReadInt32(False);
Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
IdTCPClient1.IOHandler.Discard(2);
except
IdTCPClient1.Disconnect;
raise;
end;
end;
请注意,RCON 是一个异步协议。每个命令都包含一个请求 ID,它会在响应中回显。可以将多个命令发送到服务器,而无需等待它们的回复。这就是为什么我将 SendRCONPacket()
写到 return 实际使用的请求 ID,因此您可以将其保存在某处并将其与 ReadRCONPacket()
编辑的响应 ID return 相匹配。上面代码中 TTimer
的使用只是一个 示例 如何从服务器接收未经请求的数据。在生产代码中,我建议使用专用的读取线程而不是计时器,并让线程在数据包到达时通知其余代码。
如果您不打算并行处理多个命令,那么您可以完全摆脱计时器并做更多类似的事情:
const
SERVERDATA_AUTH = 3;
SERVERDATA_AUTH_RESPONSE = 2;
SERVERDATA_EXECCOMMAND = 2;
SERVERDATA_RESPONSE_VALUE = 0;
procedure TForm1.Button1Click(Sender: TObject);
var
Reply: string;
begin
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;
IdTCPClient1.Connect;
SendRECONLogin('1234');
ShowMessage('Conectado exitosamente');
Reply := SendRECONCommand('say Hello');
// use Reply as needed...
ShowMessage(Reply);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPClient1.Disconnect;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
Reply: string;
begin
Reply := SendRECONCommand('list');
// use Reply as needed...
ShowMessage(Reply);
end;
var
gReqID: Int32 = 0;
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
var
Bytes: TIdBytes;
begin
Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
try
if gReqID < MaxInt then Inc(gReqID)
else gReqID := 1;
Result := gReqID;
IdTCPClient1.IOHandler.WriteBufferOpen;
try
IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
IdTCPClient1.IOHandler.Write(Result, False);
IdTCPClient1.IOHandler.Write(PktType, False);
IdTCPClient1.IOHandler.Write(Bytes);
IdTCPClient1.IOHandler.Write(UInt16(0), False);
IdTCPClient1.IOHandler.WriteBufferClose;
except
IdTCPClient1.IOHandler.WriteBufferCancel;
raise;
end;
except
IdTCPClient1.Disconnect;
raise;
end;
end;
procedure TForm1.SendRECONLogin(const Password: String);
var
ReqID, RespID, PktType: Int32;
Reply: String;
begin
{
From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#SERVERDATA_AUTH_RESPONSE:
When the server receives an auth request, it will respond with an empty SERVERDATA_RESPONSE_VALUE,
followed immediately by a SERVERDATA_AUTH_RESPONSE indicating whether authentication succeeded or
failed. Note that the status code is returned in the packet id field, so when pairing the response with the
original auth request, you may need to look at the packet id of the preceeding SERVERDATA_RESPONSE_VALUE.
}
// in testing, there is no empty SERVERDATA_RESPONSE_VALUE sent before SERVERDATA_AUTH_RESPONSE!
ReqID := SendRECONPacket(SERVERDATA_AUTH, Password);
RespID := ReadRECONPacket(PktType, Reply);
if PktType = SERVERDATA_RESPONSE_VALUE then
begin
if RespID <> ReqID then
raise Exception.Create('Received unexpected packet');
RespID := ReadRECONPacket(PktType, Reply);
end;
if PktType <> SERVERDATA_AUTH_RESPONSE then
raise Exception.Create('Received unexpected packet');
if RespID <> ReqID then
begin
if RespID <> -1 then
raise Exception.Create('Received unexpected packet');
raise Exception.Create('Authentication failed');
end;
end;
function TForm1.SendRECONCommand(const Cmd: String): string;
var
ReqID, TermReqID, RespID, PktType: Int32;
Reply: string;
begin
{
From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses:
Most responses are small enough to fit within the maximum possible packet size of 4096 bytes.
However, a few commands such as cvarlist and, occasionally, status produce responses too
long to be sent in one response packet. When this happens, the server will split the response
into multiple SERVERDATA_RESPONSE_VALUE packets. Unfortunately, it can be difficult to
accurately determine from the first packet alone whether the response has been split.
One common workaround is for the client to send an empty SERVERDATA_RESPONSE_VALUE
packet after every SERVERDATA_EXECCOMMAND request. Rather than throwing out the
erroneous request, SRCDS mirrors it back to the client, followed by another RESPONSE_VALUE
packet containing 0x0000 0001 0000 0000 in the packet body field. Because SRCDS always
responds to requests in the order it receives them, receiving a response packet with an empty
packet body guarantees that all of the meaningful response packets have already been received.
Then, the response bodies can simply be concatenated to build the full response.
}
// in testing, there is no mirrored SERVERDATA_RESPONSE_VALUE! The sent SERVERDATA_RESPONSE_VALUE
// is responded with a single SERVERDATA_RESPONSE_VALUE that says 'Unknown request' in its payload!
ReqID := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
TermReqID := SendRECONPacket(SERVERDATA_RESPONSE_VALUE, '');
repeat
RespID := ReadRECONPacket(PktType, Reply);
if PktType <> SERVERDATA_RESPONSE_VALUE then
raise Exception.Create('Received unexpected packet');
if RespID <> ReqID then
begin
if RespID <> TermReqID then
raise Exception.Create('Received unexpected packet');
{
RespID := ReadRECONPacket(PktType, Reply);
if (PktType <> SERVERDATA_RESPONSE_VALUE) or (RespID <> TermReqID) then
raise Exception.Create('Received unexpected packet');
}
Break;
end;
Result := Result + Reply;
until False;
end;
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
var
Len: Int32;
begin
try
Len := IdTCPClient1.IOHandler.ReadInt32(False);
Result := IdTCPClient1.IOHandler.ReadInt32(False);
PktType := IdTCPClient1.IOHandler.ReadInt32(False);
Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
IdTCPClient1.IOHandler.Discard(2);
except
IdTCPClient1.Disconnect;
raise;
end;
end;
我正在尝试弄清楚如何使用远程控制台建立连接和身份验证。
这个 Wiki Wiki 1 and this one Wiki 2 告诉我我需要构建一个数据包并将其发送到 RECON,但我不知道该怎么做..
我是网络新手,但由于我在那里搜索,所以我构建了这个:
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Host:= '127.0.0.1';
IdTCPClient1.Port:= 20001;
IdTCPClient1.Connect;
IdTcpClient1.IOHandler.Writeln('1234');
ShowMessage(IdTcpClient1.IOHandler.ReadLn);
end;
我卡在那里了,1234 是 RECON 密码和消息return:连接正常关闭...
最后,如何才能登录成功呢?并且至少发送一个命令 "list" 下一步将实时接收控制台日志?
谢谢
您的代码未实现 Minecraft 命令 运行 之上的 Source RECON protocol。您不能只向服务器发送任意数据,它必须被正确地框起来。
尝试更像这样的东西:
const
SERVERDATA_AUTH = 3;
SERVERDATA_AUTH_RESPONSE = 2;
SERVERDATA_EXECCOMMAND = 2;
SERVERDATA_RESPONSE_VALUE = 0;
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;
IdTCPClient1.Connect;
SendRECONLogin('1234');
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPClient1.Disconnect;
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
SendRECONCommand('list');
end;
procedure TForm1.IdTCPClient1Connect(Sender: TObject);
begin
Timer1.Enabled := True;
end;
procedure TForm1.IdTCPClient1Disconnect(Sender: TObject);
begin
Timer1.Enabled := False;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
RespID: Int32;
PktType: Int32;
Payload: string;
begin
try
if not IdTCPClient1.Connected then
Exit;
if IdTCPClient1.IOHandler.InputBufferIsEmpty then
begin
IdTCPClient1.IOHandler.CheckForDataOnSource(0);
IdTCPClient1.IOHandler.CheckForDisconnect(True, False);
if IdTCPClient1.IOHandler.InputBufferIsEmpty then Exit;
end;
RespID := ReadRECONPacket(PktType, Payload);
case PktType of
SERVERDATA_AUTH_RESPONSE: begin
if RespID = -1 then begin
// authentication failed...
IdTCPClient1.Disconnect;
end else begin
// authentication successful...
end;
end;
SERVERDATA_RESPONSE_VALUE: begin
// match RespID to previously sent ReqID
// and handle Payload as needed...
end;
end;
except
IdTCPClient1.Disconnect;
end;
end;
var
gReqID: Int32 = 0;
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
var
Bytes: TIdBytes;
begin
Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
try
if gReqID < MaxInt then Inc(gReqID)
else gReqID := 1;
Result := gReqID;
IdTCPClient1.IOHandler.WriteBufferOpen;
try
IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
IdTCPClient1.IOHandler.Write(Result, False);
IdTCPClient1.IOHandler.Write(PktType, False);
IdTCPClient1.IOHandler.Write(Bytes);
IdTCPClient1.IOHandler.Write(UInt16(0), False);
IdTCPClient1.IOHandler.WriteBufferClose;
except
IdTCPClient1.IOHandler.WriteBufferCancel;
raise;
end;
except
IdTCPClient1.Disconnect;
raise;
end;
end;
function TForm1.SendRECONLogin(const Password: String): Int32;
begin
Result := SendRECONPacket(SERVERDATA_AUTH, Password);
end;
function TForm1.SendRECONCommand(const Cmd: String): Int32;
begin
Result := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
end;
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
var
Len: Int32;
begin
try
Len := IdTCPClient1.IOHandler.ReadInt32(False);
Result := IdTCPClient1.IOHandler.ReadInt32(False);
PktType := IdTCPClient1.IOHandler.ReadInt32(False);
Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
IdTCPClient1.IOHandler.Discard(2);
except
IdTCPClient1.Disconnect;
raise;
end;
end;
请注意,RCON 是一个异步协议。每个命令都包含一个请求 ID,它会在响应中回显。可以将多个命令发送到服务器,而无需等待它们的回复。这就是为什么我将 SendRCONPacket()
写到 return 实际使用的请求 ID,因此您可以将其保存在某处并将其与 ReadRCONPacket()
编辑的响应 ID return 相匹配。上面代码中 TTimer
的使用只是一个 示例 如何从服务器接收未经请求的数据。在生产代码中,我建议使用专用的读取线程而不是计时器,并让线程在数据包到达时通知其余代码。
如果您不打算并行处理多个命令,那么您可以完全摆脱计时器并做更多类似的事情:
const
SERVERDATA_AUTH = 3;
SERVERDATA_AUTH_RESPONSE = 2;
SERVERDATA_EXECCOMMAND = 2;
SERVERDATA_RESPONSE_VALUE = 0;
procedure TForm1.Button1Click(Sender: TObject);
var
Reply: string;
begin
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 20001;
IdTCPClient1.Connect;
SendRECONLogin('1234');
ShowMessage('Conectado exitosamente');
Reply := SendRECONCommand('say Hello');
// use Reply as needed...
ShowMessage(Reply);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
IdTCPClient1.Disconnect;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
Reply: string;
begin
Reply := SendRECONCommand('list');
// use Reply as needed...
ShowMessage(Reply);
end;
var
gReqID: Int32 = 0;
function TForm1.SendRECONPacket(PktType: Int32; const Payload: string = ''): Int32;
var
Bytes: TIdBytes;
begin
Bytes := IndyTextEncoding_ASCII.GetBytes(Payload);
try
if gReqID < MaxInt then Inc(gReqID)
else gReqID := 1;
Result := gReqID;
IdTCPClient1.IOHandler.WriteBufferOpen;
try
IdTCPClient1.IOHandler.Write(Int32(Length(Bytes)+10), False);
IdTCPClient1.IOHandler.Write(Result, False);
IdTCPClient1.IOHandler.Write(PktType, False);
IdTCPClient1.IOHandler.Write(Bytes);
IdTCPClient1.IOHandler.Write(UInt16(0), False);
IdTCPClient1.IOHandler.WriteBufferClose;
except
IdTCPClient1.IOHandler.WriteBufferCancel;
raise;
end;
except
IdTCPClient1.Disconnect;
raise;
end;
end;
procedure TForm1.SendRECONLogin(const Password: String);
var
ReqID, RespID, PktType: Int32;
Reply: String;
begin
{
From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#SERVERDATA_AUTH_RESPONSE:
When the server receives an auth request, it will respond with an empty SERVERDATA_RESPONSE_VALUE,
followed immediately by a SERVERDATA_AUTH_RESPONSE indicating whether authentication succeeded or
failed. Note that the status code is returned in the packet id field, so when pairing the response with the
original auth request, you may need to look at the packet id of the preceeding SERVERDATA_RESPONSE_VALUE.
}
// in testing, there is no empty SERVERDATA_RESPONSE_VALUE sent before SERVERDATA_AUTH_RESPONSE!
ReqID := SendRECONPacket(SERVERDATA_AUTH, Password);
RespID := ReadRECONPacket(PktType, Reply);
if PktType = SERVERDATA_RESPONSE_VALUE then
begin
if RespID <> ReqID then
raise Exception.Create('Received unexpected packet');
RespID := ReadRECONPacket(PktType, Reply);
end;
if PktType <> SERVERDATA_AUTH_RESPONSE then
raise Exception.Create('Received unexpected packet');
if RespID <> ReqID then
begin
if RespID <> -1 then
raise Exception.Create('Received unexpected packet');
raise Exception.Create('Authentication failed');
end;
end;
function TForm1.SendRECONCommand(const Cmd: String): string;
var
ReqID, TermReqID, RespID, PktType: Int32;
Reply: string;
begin
{
From https://developer.valvesoftware.com/wiki/Source_RCON_Protocol#Multiple-packet_Responses:
Most responses are small enough to fit within the maximum possible packet size of 4096 bytes.
However, a few commands such as cvarlist and, occasionally, status produce responses too
long to be sent in one response packet. When this happens, the server will split the response
into multiple SERVERDATA_RESPONSE_VALUE packets. Unfortunately, it can be difficult to
accurately determine from the first packet alone whether the response has been split.
One common workaround is for the client to send an empty SERVERDATA_RESPONSE_VALUE
packet after every SERVERDATA_EXECCOMMAND request. Rather than throwing out the
erroneous request, SRCDS mirrors it back to the client, followed by another RESPONSE_VALUE
packet containing 0x0000 0001 0000 0000 in the packet body field. Because SRCDS always
responds to requests in the order it receives them, receiving a response packet with an empty
packet body guarantees that all of the meaningful response packets have already been received.
Then, the response bodies can simply be concatenated to build the full response.
}
// in testing, there is no mirrored SERVERDATA_RESPONSE_VALUE! The sent SERVERDATA_RESPONSE_VALUE
// is responded with a single SERVERDATA_RESPONSE_VALUE that says 'Unknown request' in its payload!
ReqID := SendRECONPacket(SERVERDATA_EXECCOMMAND, Cmd);
TermReqID := SendRECONPacket(SERVERDATA_RESPONSE_VALUE, '');
repeat
RespID := ReadRECONPacket(PktType, Reply);
if PktType <> SERVERDATA_RESPONSE_VALUE then
raise Exception.Create('Received unexpected packet');
if RespID <> ReqID then
begin
if RespID <> TermReqID then
raise Exception.Create('Received unexpected packet');
{
RespID := ReadRECONPacket(PktType, Reply);
if (PktType <> SERVERDATA_RESPONSE_VALUE) or (RespID <> TermReqID) then
raise Exception.Create('Received unexpected packet');
}
Break;
end;
Result := Result + Reply;
until False;
end;
function TForm1.ReadRECONPacket(var PktType: Integer; var Payload: String): Int32;
var
Len: Int32;
begin
try
Len := IdTCPClient1.IOHandler.ReadInt32(False);
Result := IdTCPClient1.IOHandler.ReadInt32(False);
PktType := IdTCPClient1.IOHandler.ReadInt32(False);
Payload := IdTCPClient1.IOHandler.ReadString(Len-10, IndyTextEncoding_ASCII);
IdTCPClient1.IOHandler.Discard(2);
except
IdTCPClient1.Disconnect;
raise;
end;
end;