[英]Delphi Indy TCP connection to RECON
我試圖弄清楚如何通過遠程控制台建立連接和身份驗證。
這個Wiki Wiki 1和這個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密碼及其返回的消息:連接正常關閉...
最后,我如何成功登錄? 並且至少發送命令“列表”的下一步將是實時接收控制台日志?
謝謝
您的代碼未實現Minecraft命令在其上運行的Source RECON協議 。 您不僅可以將任意數據發送到服務器,還必須對其進行適當的構架。
嘗試類似這樣的方法:
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,該ID在響應中回顯。 可以將多個命令發送到服務器,而無需等待它們的答復。 這就是為什么我編寫SendRCONPacket()
返回實際使用的請求ID的原因,因此您可以將其保存在某個地方,並將其與ReadRCONPacket()
返回的響應ID相匹配。 在上面的代碼中使用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;
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.