unit DebugTCPServer; {$mode objfpc}{$H+} interface uses Classes, SysUtils, ssockets, {$IFDEF UNIX} BaseUnix, {$ENDIF} debugthread, sockets, syncobjs, FpDbgClasses, LazFglHash, lazCollections, fpjson, fgl, DebugInOutputProcessor, DebugThreadCommand; type { TFpDebugTcpServer } TFpDebugTcpConnectionThread = class; TThreadedQueueString = specialize TLazThreadedQueue; TConnectionList = specialize TFPGObjectList; TFpDebugTcpServer = class(TThread) private FPort: integer; FSensePorts: integer; FTCPConnection: TInetServer; FConnectionList: TConnectionList; FDebugThread: TFpDebugThread; FInitializationFinished: PRTLEvent; function CreateInetServer: TInetServer; procedure FTCPConnectionConnect(Sender: TObject; Data: TSocketStream); procedure FTCPConnectionAcceptError(Sender: TObject; ASocket: Longint; E: Exception; var ErrorAction: TAcceptErrorAction); procedure FTCPConnectionConnectQuery(Sender: TObject; ASocket: Longint; var Allow: Boolean); protected procedure Execute; override; public procedure WaitForInitialization(out Port: integer); procedure StopListening; constructor create(ADebugThread: TFpDebugThread; APort, ASensePorts: integer); procedure RemoveConnection(ADebugTcpConnectionThread: TFpDebugTcpConnectionThread); destructor Destroy; override; end; { TFpDebugTcpConnectionThread } TFpDebugTcpConnectionThread = class(tthread, IFpDebugListener) private FData: TSocketStream; FDebugThread: TFpDebugThread; FResponseQueue: TThreadedQueueString; FDebugTcpServer: TFpDebugTcpServer; FConnectionIdentifier: integer; FInOutputProcessor: TCustomInOutputProcessor; protected procedure Execute; override; procedure SendCommand(ACommandStr: string); public procedure SendEvent(AnEvent: TFpDebugEvent); function GetOrigin: string; constructor create(ADebugThread: TFpDebugThread; ADebugTcpServer: TFpDebugTcpServer; Data: TSocketStream); destructor Destroy; override; end; implementation { TFpDebugTcpConnectionThread } function STrToStr(AStr: string): string; var i : integer; begin result := ''; for i := 1 to length(AStr) do if ord(AStr[i])<20 then result := result +'#'+inttostr(ord(AStr[i])) else result := result + Astr[i]; end; procedure TFpDebugTcpConnectionThread.Execute; procedure WriteString(AStr: string); var i: integer; begin AStr := AStr + #10; i := FData.Write(AStr[1], length(AStr)); if i < 0 then begin if FData.LastError=32 then begin // Lost connection end else FDebugThread.SendNotification(FConnectionIdentifier, ntConnectionProblem, null, 'Error during write. Socket-error: %d', '', [FData.LastError]); Terminate; end else if i < length(AStr) then raise exception.create('Message has not been send to client entirely'); end; const InputBufferSize = 1024; var s: string; i: integer; InputBuffer: array[0..InputBufferSize-1] of char; InputStr: string; begin WriteString('Welcome to FPDebug-server.'); if not Terminated then WriteString('Your connection-idenfifier is '+IntToStr(FConnectionIdentifier)+'.'); if not Terminated then WriteString('Send "help" for more information.'); while not terminated do begin i := FData.Read(InputBuffer[0], InputBufferSize); if i > 0 then begin setlength(s,i); move(InputBuffer[0],s[1],i); s := StringReplace(s,#13#10,#10,[rfReplaceAll]); InputStr:=InputStr+s; i := pos(#10, InputStr); while i > 0 do begin s := copy(InputStr, 1, i-1); delete(InputStr,1,i); SendCommand(S); i := pos(#10, InputStr); end; end else if i < 0 then begin if FData.LastError<>35 {EAGAIN} then begin writeln('Error during write. Socket-error: '+inttostr(FData.LastError)); Terminate; end; end else if i = 0 then begin // Zero-count -> Connection closed Terminate; end; if not terminated and (FResponseQueue.PopItem(s) = wrSignaled) then begin WriteString(s); end; end; FDebugTcpServer.RemoveConnection(self); end; procedure TFpDebugTcpConnectionThread.SendCommand(ACommandStr: string); var ACommand: TFpDebugThreadCommand; begin ACommand := FInOutputProcessor.TextToCommand(ACommandStr); if assigned(ACommand) then FDebugThread.QueueCommand(ACommand); end; procedure TFpDebugTcpConnectionThread.SendEvent(AnEvent: TFpDebugEvent); var s: string; begin s := FInOutputProcessor.EventToText(AnEvent); FResponseQueue.PushItem(s); end; function TFpDebugTcpConnectionThread.GetOrigin: string; begin result := format('%d.%d.%d.%d:%d', [FData.RemoteAddress.sin_addr.s_bytes[1], FData.RemoteAddress.sin_addr.s_bytes[2],FData.RemoteAddress.sin_addr.s_bytes[3], FData.RemoteAddress.sin_addr.s_bytes[4], FData.RemoteAddress.sin_port]) end; constructor TFpDebugTcpConnectionThread.create(ADebugThread: TFpDebugThread; ADebugTcpServer: TFpDebugTcpServer; Data: TSocketStream); begin FData := data; // Set non-blocking {$IFDEF UNIX} fpfcntl(FData.Handle,F_SETFL,O_NONBLOCK); {$ENDIF} FDebugThread := ADebugThread; FDebugTcpServer := ADebugTcpServer; FResponseQueue:=TThreadedQueueString.create(100, INFINITE, 100); FConnectionIdentifier := FDebugThread.AddListener(self); FInOutputProcessor := TJSonInOutputProcessor.create(FConnectionIdentifier, @ADebugThread.SendLogMessage); inherited create(false); end; destructor TFpDebugTcpConnectionThread.Destroy; begin FInOutputProcessor.Free; FDebugThread.RemoveListener(self); FResponseQueue.Free; FData.Free; inherited Destroy; end; { TFpDebugTcpServer } procedure TFpDebugTcpServer.FTCPConnectionAcceptError(Sender: TObject; ASocket: Longint; E: Exception; var ErrorAction: TAcceptErrorAction); begin if (E is ESocketError) and (ESocketError(E).Code=seAcceptFailed) and (socketerror=53) {ECONNABORTED} then begin // The socket has stopped listening. The TCP-server is shutting down... ErrorAction:=aeaStop; end else writeln('ErrorAction a: '+e.ClassName + ' -- ',ErrorAction, '::',socketerror); end; procedure TFpDebugTcpServer.FTCPConnectionConnectQuery(Sender: TObject; ASocket: Longint; var Allow: Boolean); begin Allow:=true; end; function TFpDebugTcpServer.CreateInetServer: TInetServer; var Conn: boolean; InetServer: TInetServer; FFirstError: string; i: Integer; begin result := nil; for i := 0 to FSensePorts-1 do begin conn := false; InetServer := TInetServer.Create(FPort+i); try InetServer.Listen; Conn:=true; Break; except on E: Exception do begin InetServer.Free; if (E is ESocketError) and (ESocketError(E).Code=seBindFailed) then begin // Ignore, try next port if FFirstError='' then FFirstError:=e.Message; end else Raise; end; end; end; if conn then begin result := InetServer; FPort:=result.Port; FDebugThread.SendNotification(-1, ntListenerMessage, null, 'Listening for incoming TCP-connections on port %d', '', [FPort]) end else begin FPort:=-1; FDebugThread.SendNotification(-1, ntConnectionProblem, null, 'Failed to start listening for incoming TCP-connections: %s', '', [FFirstError]) end; end; procedure TFpDebugTcpServer.FTCPConnectionConnect(Sender: TObject; Data: TSocketStream); var AConnectionThread: TFpDebugTcpConnectionThread; begin AConnectionThread:=TFpDebugTcpConnectionThread.create(FDebugThread, Self, data); AConnectionThread.FreeOnTerminate:=true; FConnectionList.Add(AConnectionThread); end; procedure TFpDebugTcpServer.Execute; var AConnection: TInetServer; begin try FTCPConnection := CreateInetServer; RTLeventSetEvent(FInitializationFinished); if assigned(FTCPConnection) then begin try FTCPConnection.OnConnect:=@FTCPConnectionConnect; FTCPConnection.OnConnectQuery:=@FTCPConnectionConnectQuery; FTCPConnection.OnAcceptError:=@FTCPConnectionAcceptError; FTCPConnection.StartAccepting; finally AConnection:=FTCPConnection; FTCPConnection := nil; AConnection.Free; end; end Except on E: Exception do begin WriteLn('Exception: '+e.Message); end; end; end; procedure TFpDebugTcpServer.WaitForInitialization(out Port: integer); begin RTLeventWaitFor(FInitializationFinished); Port := FPort; end; procedure TFpDebugTcpServer.StopListening; begin Terminate; if assigned(FTCPConnection) then FTCPConnection.StopAccepting(true); end; constructor TFpDebugTcpServer.create(ADebugThread: TFpDebugThread; APort, ASensePorts: integer); begin FPort:=APort; if ASensePorts<1 then ASensePorts:=1; FSensePorts:=ASensePorts; FDebugThread:=ADebugThread; FConnectionList:=TConnectionList.Create(false); FInitializationFinished:=RTLEventCreate; inherited Create(false); end; procedure TFpDebugTcpServer.RemoveConnection(ADebugTcpConnectionThread: TFpDebugTcpConnectionThread); begin FConnectionList.Remove(ADebugTcpConnectionThread); end; destructor TFpDebugTcpServer.Destroy; var i: integer; begin RTLeventdestroy(FInitializationFinished); for i := 0 to FConnectionList.Count-1 do FConnectionList[i].Terminate; for i := 0 to FConnectionList.Count-1 do FConnectionList[i].WaitFor; if FConnectionList.Count<>0 then raise exception.create('Not all connections are cleared.'); FConnectionList.Free; inherited Destroy; end; end.