2016-10-21 8 views
1

Я пытаюсь выяснить, как можно получить соединение и аутентификацию, установленную с удаленной консоли.Delphi Indy TCP-соединение с RECON

Эта вики 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; 

Я stucked там, где 1234 пароль RECON и сообщение, что он вернется: Соединение закрыто грациозно ...

Наконец, как я могу войти успешно? И, по крайней мере, отправить команду «list», следующий шаг будет получать консольный журнал в реальном времени?

Благодаря

ответ

5

Ваш код не реализации Source RECON protocol что Minecraft команды запуска на вершине. Вы не можете просто отправлять произвольные данные на сервер, он должен быть правильно оформлен.

Попробуйте что-то больше, как это вместо:

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 является асинхронный протокол. Каждая команда содержит идентификатор запроса, который возвращается в ответ. Несколько команд могут быть отправлены на сервер, не дожидаясь их ответов. Вот почему я написал SendRCONPacket(), чтобы вернуть действительно используемый идентификатор запроса, чтобы вы могли его сэкономить и сопоставить с идентификатором ответа, возвращаемым . Использование 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; 
+0

Большое спасибо, очень ussefull ваша помощь , немного ошибочная ошибка: ** PckType ** вместо ** PktType ** в ** ReadRECONPacket ** процедура. Теперь я попытаюсь получить ответы и напечатать их в памятке. – Martini002

+0

По какой-то причине он не считывает пакеты с другого хоста, который не является моим локальным хостом. Он подключается, но не может успешно работать даже на другом компьютере в моей локальной сети (192.168.1.101) – Martini002

+0

Выполнение некоторых тестов, которые я получил после 'SendRECONLogin()' ошибки 10053. – Martini002