简体   繁体   English

Delphi Indy与RECON的TCP连接

[英]Delphi Indy TCP connection to RECON

I am trying to figure out how would be possible to get a connection and authentication stablished with the remote console. 我试图弄清楚如何通过远程控制台建立连接和身份验证。

This Wiki Wiki 1 and this one Wiki 2 tell me I need to build a packet and send it to the RECON, but I do not know how to do this.. 这个Wiki Wiki 1和这个Wiki 2告诉我,我需要构建一个数据包并将其发送给RECON,但是我不知道该怎么做。

I am a newbie with networking but since I was searching over there then I build this: 我是网络的新手,但由于我在那儿搜索,因此我将其构建为:

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; 

I am stucked there, where 1234 is the RECON password and the message that it return: Connection closed gracefully... 我被卡在那里,那里的1234是RECON密码及其返回的消息:连接正常关闭...

Finally, how can I log in successfully? 最后,我如何成功登录? And at least send a command "list" the next step would be receive the console log in realtime? 并且至少发送命令“列表”的下一步将是实时接收控制台日志?

Thanks 谢谢

Your code is not implementing the Source RECON protocol that the Minecraft commands run on top of. 您的代码未实现Minecraft命令在其上运行的Source RECON协议 You can't just send arbitrary data to the server, it has to be framed properly. 您不仅可以将任意数据发送到服务器,还必须对其进行适当的构架。

Try something more like this instead: 尝试类似这样的方法:

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;

Note that RCON is an asynchronous protocol. 请注意,RCON是异步协议。 Each command contains a request ID, which is echoed back in the response. 每个命令都包含一个请求ID,该ID在响应中回显。 Multiple commands can be sent to the server without waiting for their replies. 可以将多个命令发送到服务器,而无需等待它们的答复。 That is why I wrote SendRCONPacket() to return the request ID actually used, so you can save it off somewhere and match it to the response ID returned by ReadRCONPacket() . 这就是为什么我编写SendRCONPacket()返回实际使用的请求ID的原因,因此您可以将其保存在某个地方,并将其与ReadRCONPacket()返回的响应ID相匹配。 The use of a TTimer in the above code is just an example of how to receive unsolicited data from the server. 在上面的代码中使用TTimer只是如何从服务器接收未经请求的数据的示例 In production code, I would suggest using a dedicated reading thread instead of a timer, and let the thread notify the rest of your code whenever a packet arrives. 在生产代码中,我建议使用专用的读取线程而不是计时器,并在数据包到达时让该线程通知其余代码。

If you are not planning on ever having multiple commands being processed in parallel, then you could get rid of the timer altogether and do something more like this instead: 如果您不打算并行处理多个命令,那么您可以完全摆脱计时器,而做更多类似的事情:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM