Anzeige:
Seite 1 von 3 123 LetzteLetzte
Ergebnis 1 bis 15 von 43

Thema: GNU Readline & Telnet Client & Pseudo Terminal

  1. #1
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456

    Arrow GNU Readline & Telnet Client & Pseudo Terminal

    Hi Leute!

    Sorry, daß ich schon wieder 'nen neuen Thread aufmache. Ist nur, um euch den aktuellen Stand mitzuteilen und meine Probleme neuerlicherweise auf den Punkt zu bringen. Ich versuche hier nochmal alles, was ich weiß und nicht weiß zusammen zu fassen, auf daß wir in Zukunft in diesem Thread alles finden, was dieses Thema betrifft. Damit ich nicht noch mehr Threads aufmachem muss...

    Für alle, die meine anderen Threads nicht verfolgt haben, zusammenfassend:

    Ich baue in eines meiner Programme einen Telnet-Server ein, der die Fähigkeiten der Readline/History-Bibliotheken in sich vereinen soll. Quasi ein vollwertiges Terminal innerhalb meines Programms, zur Steuerung desselbigen per Telnet Client (Putty o.ä.).

    Ich habe bereits den Socket erstellt, verbinden darauf ist möglich, Eingabe von Kommandos auch. Desweiteren habe ich das Pseudo-Terminal zwischen dem Socket und der GNU Readline Funktion erstellt.

    Das ganze funktioniert in der Theorie folgendermaßen:
    Jemand verbindet sich mit einem Telnet Client auf den Socket (bei mir TCP Port 6700). Im Eingangsbereich des Sockets (Überwachung des Sockets auf eingehende Daten) wird erstmal überprüft, ob es sich bei dem eingehenden Character um ein 0xFF handelt. Ist das der Fall, werden die nächsten zwei Bytes eingelesen und als Telnet Command interpretiert (siehe entsprechendes RFC). In allen anderen Fällen werden die Character ohne Änderung an die Slave-Seite des Pseudoterminals weitergegeben.

    Auf die Eingabe auf Slave-Seite reagierend kommt das eingegebene Zeichen laut definition auf der anderen, der Master-Seite wieder heraus. Signalisiert die Master-Seite des Pseudo Terminals ein eingehendes Zeichen, wird es 1:1 an die Readline Funktion weitergegeben. Readline ist hier als Callback implementiert, da auch andere Schnittstellen ständig gepollt werden müssen und keine blockierende Funktion aufgerufen werden darf, solange keine Daten zur Verfügung stehen. Daher wird bei eingehenden Zeichen auf der Master-Seite des Pseudo Terminals auch per rl_callback_read_char das Zeichen eingelesen.

    So viel zum funktionierenden Teil meines Programms.

    Meine Probleme sind die folgenden, bei denen ich euch dringend darum bitte, mir zu helfen, weil Google sich wie in den meisten Fällen meiner Probleme massiv ausschweigt:

    Ich habe dem Telnet Client per Telnet Kommandos DO SUPPRESS-GO-AHEAD und WILL ECHO mitgeteilt, daß er jedes eingegebene Zeichen einzeln sofort nach Eingabe an den Server schicken soll. Das funktioniert noch nicht. Der Client schickt immer noch erst nach drücken der RETURN/ENTER-Taste.

    Auf einen Druck der RETURN/ENTER-Taste hin überträgt der Client den gesamten eingegebenen String inklusive der Steuerzeichen CR (0x0D) und eines NL (0x0A). Diese werden auch 1:1 an das Pseudo Terminal weitergegeben, da es sich dabei ja nicht um Telnet Steuerzeichen (0xFF) handelt. Das PTY (Pseudo Terminal) schickt diese dann 1:1 weiter an Readline. Readline scheint aber mit 0x0A nichts anfangen zu können, blockiert an der Stelle. Ich kann mir nur vorstellen, daß das PTY irgendwas mit dem 0x0A anstellt, daß es zwar rausgeht auf die Slave Seite, auf der Master Seite auch signalisiert, aber dennoch per read() nicht lesbar ist (ReadLine verwendet read()). Das gleiche gilt für Steuerzeichen, die ReadLine für die History benötigt (Cursor rauf/runter). Diese Steuerzeichen, durch das PTY an ReadLine geschickt bewirken ebenfalls, daß das read() in rl_callback_read_char() blockiert.

    Meine Fragen, die aus obigen Problemen resultieren, lauten nun wie folgt:

    Welche Kommandos muss ich zusätzlich zu DO SUPPRESS-GO-AHEAD und WILL ECHO noch an den Telnet Client schicken, bevor irgendwelche Eingaben/AUsgaben gemacht werden, bzw. auf welche Kommandos muss ich definitiv reagieren?

    Welche Einstellungen (Canonical, Echo, Terminaltype etc.) per termios/tcsetattr muss ich auf welcher Seite des PTY vornehmen, damit das beschriebene Konstrukt Socket->SlavePTY->MasterPTY->ReadLine funktioniert?

    Ist das beschriebene Konstrukt Socket->SlavePTY->MasterPTY->ReadLine überhaupt die richtige Vorgehensweise? Wie wird es sonst gemacht, wenn nicht so?



    Vielen Dank für eure Mühen!

    Grüße,
    Hendrik

  2. #2
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Gibt es da echt niemanden, der da auch nur von irgendwas davon ein bisschen Ahnung hat? Ein paar unterstützende Tips reichen mir ja vielleicht schon, um den zündenden Gedanken zu bekommen...

    Noch ein paar Informationen:

    Ich weiß nicht, ob ich das schon erwähnt habe, aber...
    ... die einzelnen Bytes, die ich vom Socket per read() lese, schreibe ich nahezu 1:1 (bis auf Telnet Kommandos) auf die Slave-Seite des Pseudo-Terminals weiter. Richtig? Slave Seite?

    ... die einzelnen Bytes, die ich auf der Masterseite des Pseudo-Terminals auslese, wandern 1:1 in die rl_callback_read_char Funktion, welche ihrerseits wohl nach einem Carriage Return die per rl_callback_install_handler eingebundene Callback Funktion aufruft und ihr den kompletten String übergibt. Schönes Konzept, wenn's funktioniert.

    ... kann es sein, daß ein Pseudo Terminal noch irgendwas mit den durch es hindurch geschleiften Zeichen anstellt???

    Ich bin hier am verzweifeln Leute, brauche wirklich dringend Hilfe. Und wenn's nur 'n Hinweis ist auf eine Seite, wo die Verwendung der GNU ReadLine mit Pseudo Terminals erklärt wird. Egal ob englisch oder deutsch... Ich brauch Informationen. Dringend! Ich gerate zusehends unter Zeitdruck und komme einfach keinen Schritt weiter. Hilfe!!!




    Gruß, Hendrik

  3. #3
    Registrierter Benutzer
    Registriert seit
    25.10.2004
    Beiträge
    819
    Was passiert, wenn du den NL 0x0a einfach nicht an readline übergibst?

    Und hast du es schonmal mit rl_instream = fdopen(SOCKETNUM, "r") versucht?
    Geändert von Joghurt (18-05-2005 um 12:12 Uhr)

  4. #4
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Dann funktioniert DAS zumindest. Aber auch andere Steuerzeichen führen zu dem blockierenden Verhalten von ReadLine. Beispielsweise Cursor rauf (0x1B). Das sind dann allerdings Steuerzeichen, die ich benötige, um mit History arbeiten zu können. TAB würde sich vermutlich genau so verhalten.


    Mannomann das sind die heftigsten Entwicklerprobleme, die ich je hatte.


    Das mit dem Readline direkt an den Socket binden hab ich auch schon versucht... das hat auch gut funktioniert... aber aus irgendeinem Grund hab ich mich dann dafür entschieden, die Daten auch noch durch ein Terminal zu schleifen.

    Wichtig ist für mich auch zu wissen, ob das ganze Konzept so überhaupt korrekt gedacht war: die Zeichen aus dem Socket direkt in die Slave Seite des PTY, dann aus der Master Seite des PTY wieder heraus in ReadLine hinein. Alle Ausgaben von Readline an die Master Seite des PTY, dann aus der Slave Seite heraus und 1:1 in den Socket hinein (also an den Telnet Client).
    Geändert von 7.e.Q (18-05-2005 um 12:51 Uhr)

  5. #5
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Irrtum: auf TAB reagiert ReadLine NICHT blockierend. Auf TAB reagiert es korrekt. Sprich, ein TAB+Enter = nix passiert, TAB+TAB+Enter = Dateiliste wird angezeigt. Also wie es sein sollte, abgesehen vom Enter, was ich immer noch nicht wegbekommen habe (Zeichenweise Übertragung).

    Das ist echt zum Mäusemelken...


    Zitat Zitat von Eddie aus Steven Kings "Der dunkle Turm"
    Kaka

  6. #6
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    So ich schmeiß mal ein bisschen mit Code um mich...

    Die Erstellung des Pseudo Terminals:
    Code:
    void CInfoServer::CreateInternalTerminal()
    {
    	if(iInternalTerminalMasterFD > 2) close(iInternalTerminalMasterFD);
    	if(iInternalTerminalSlaveFD > 2) close(iInternalTerminalSlaveFD);
    
    	iInternalTerminalMasterFD = getpt();
    	grantpt(iInternalTerminalMasterFD);
    	unlockpt(iInternalTerminalMasterFD);
    
    	iInternalTerminalSlaveFD = open(ptsname(iInternalTerminalMasterFD), O_RDWR);
    
    //	fcntl(iInternalTerminalMasterFD, F_SETFL, O_NONBLOCK);
    //	fcntl(iInternalTerminalSlaveFD, F_SETFL, O_NONBLOCK);
    
    }
    Die Injizierung von ReadLine und das Binden der Terminals ans selbige:
    Code:
    void CInfoServer::InstallReadLine()
    {
    	errno = 0;
    
    	struct termios TerminalMasterSettings;
    	struct termios TerminalSlaveSettings;
    
    
    	tcgetattr(iInternalTerminalMasterFD, &TerminalMasterSettings);
    
    	TerminalMasterSettings.c_iflag &= ~(ICANON|ECHO);
    	TerminalMasterSettings.c_cc[VMIN] = 0;
    
    	tcsetattr(iInternalTerminalMasterFD, TCSANOW, &TerminalMasterSettings);
    
    	if(errno != 0)
    	{
    		perror("TERMSETTINGS");
    	}
    
    	rl_instream = fdopen(iInternalTerminalMasterFD, "r");
    	rl_outstream = fdopen(iInternalTerminalMasterFD, "w");
    	rl_terminal_name = "VT100";
    	rl_initialize();
    
    	rl_callback_handler_install("CON: ", readline_callback);
    
    	fprintf(stderr,"Installed Terminals: %i %i\n",
    		iInternalTerminalMasterFD,
    		iInternalTerminalSlaveFD);
    
    }
    Und bitte sagt mir, was da fehlt, welche Einstellungen auf dem Terminal ich definitiv noch machen muss etc.

    Nicht vergessen:

    iInternalTerminalMasterFD ist die Seite, aus der ReadLine seine Eingaben liest und auf die ReadLine seine Ausgaben schreibt.

    iInternalTerminalSlaveFD ist die Seite des PTY, deren Ausgaben 1:1 an den Socket gehen und in die auch die Bytes aus dem Socket 1:1 hineingeschrieben werden (wie gesagt, bis auf Telnet Protokoll Kommandos).

  7. #7
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    ... und meine OnAccept Funktion, die auch die Kommandos an den Client schickt:

    Code:
    int CInfoServer::OnAccept()
    {
    
    	iClientSocket=accept(iServerSocket,NULL,NULL);
    
    	fcntl(iClientSocket, F_SETFL, O_NONBLOCK);
    
    	InstallReadLine();
    
    	SendTelnetCommand(iClientSocket, DO, 3);
    	SendTelnetCommand(iClientSocket, WILL, 1);
    
    	SendTelnetCommand(iClientSocket, WILL, 24);
    	SendTelnetSubnegotiation(iClientSocket, 24, TerminalType);
    
    //	SendTelnetCommand(iClientSocket, DO, 24);
    //	SendTelnetCommand(iClientSocket, DO, 32);
    
    	m_IsConnected=true;
    	Help("");
    	return iClientSocket;
    }

  8. #8
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    DO'H... es funktioniert... endlich Zeichen für Zeichen Übertragung... was hab ich vergessen? Na? Wer weiß es???

    Wenn mir der Client ein IAC DO SUPPRESS-GO-AHEAD schickt... was mach ich dann? Bis eben hab ich ihm pauschal ein IAC WONT SUPPRESS-GO-AHEAD geschickt... ICH DEPP!!!

    Jetzt schick ich ihm ein IAC WILL SUPPRESS-GO-AHEAD und damit funktioniert's endlich zeichenweise! Herrlich...

    ABER es gibt immer noch Probleme:

    ReadLine bleibt bei Eingabe von Cursor hoch/runter immer noch im read() (beim Lesen auf dem ReadLine zugewiesenen Terminal) innerhalb von rl_callback_read_char() stehen... Da müssen noch Probleme in den Terminaleinstellungen sein.

    Bitte da noch weiter drüber grübeln! Ich bin da nämlich noch etwas aufgeschmissen!


    Mächtig Danke für eure Lese-Geduld!!!

  9. #9
    Registrierter Benutzer
    Registriert seit
    25.10.2004
    Beiträge
    819
    Was wird denn bei einem Pfeil-oben empfangen?
    Hilft evtl. der BINARY-Modus? (Was ich allerdings bezweifle)

  10. #10
    Registrierter Benutzer
    Registriert seit
    25.10.2004
    Beiträge
    819
    Zitat Zitat von 7.e.Q
    Das mit dem Readline direkt an den Socket binden hab ich auch schon versucht... das hat auch gut funktioniert... aber aus irgendeinem Grund hab ich mich dann dafür entschieden, die Daten auch noch durch ein Terminal zu schleifen.
    Was für ein Grund war das denn? Wenn es lief, wieso dann den umweg über ein pseudo-Terminal?

    Hast du rl_prep_terminal und rl_reset_terminal aufgerufen?
    Geändert von Joghurt (18-05-2005 um 17:56 Uhr)

  11. #11
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Bei dem Pfeil nach oben wird der Character 0x1B empfangen. Zumindest auf Slave-Seite des PTY. Der wird auch so weiter an das Terminal gegeben und sollte auch so auf der anderen Seite des Terminals zur Verfügung stehen für das Auslesen per Readline. Nur genau das funktioniert eben noch nicht. Select auf dem Filedeskriptor des Terminals sagt, da sind Daten verfügbar auf dem FD des PTY, aber read blockiert beim Einleseversuch dieser Daten.

    Die beiden Funktionen rufe ich nicht auf. Werde sie aber gleich mal mit einbauen. Mal schauen, ob's was hilft.

    An den Grund, warum ich das PTY dazwischen gehängt habe, kann ich mich nicht mehr genau erinnern. Ich glaube, das war wegen eben jenen Einstellungen, ECHO etc. Es jetzt wieder auszubauen würde größeren Aufwand bedeuten, als zu versuchen, es entsprechend zu konfigurieren. Auch in Anbetracht der Gefahr, daß ich mich in Richtung ohne PTY wieder verschlechter, was die Funktionsfähigkeit angeht. Momentan geht fast alles, bis auf die Cursor-Tasten und ich bin echt froh darüber.

    Also wär klasse, wenn das noch hinzubekommen wär.

    EDIT: den Grund hab ich gerade herausgefunden, als ich versucht hab, das Terminal zwischen Readline und dem Socket auszuklinken:
    ich habe das Terminal für die Splittung von Telnet Kommandos und normalen Eingaben eingebaut. Ich lese byteweise vom Socket mit read() und überprüfe jedes Byte auf den Wert 0xFF. Ist es ein 0xFF, laufe ich in die Telnet-Kommando Funktion. Ist es kein 0xFF, gebe ich das Byte weiter an das Terminal, damit Readline es auf der anderen Seite auslesen kann. Das war der Hauptgrund, genau...
    Geändert von 7.e.Q (19-05-2005 um 07:59 Uhr) Grund: Grund gefunden

  12. #12
    Registrierter Benutzer
    Registriert seit
    25.10.2004
    Beiträge
    819
    Zitat Zitat von 7.e.Q
    Ist es ein 0xFF, laufe ich in die Telnet-Kommando Funktion. Ist es kein 0xFF, gebe ich das Byte weiter an das Terminal, damit Readline es auf der anderen Seite auslesen kann. Das war der Hauptgrund, genau...
    Ah, ok. Denk' dran! Wenn ein richtiges 0xff im Stream vorkommt, wird es als 0xff 0xff empfangen, und die 0xff sequenzen sind nicht immer gleich lang.
    Betreffs des Cursors, ich denke, readline kann mit 0x1f (oder was das war) nichts anfangen, da es eine ESC-Sequenz erwartet oder so ähnlich. Schau einfach mal nach, wie ein Pfeil-oben als VT100 Sequenz zu senden ist und konfiguriere den Telnet-client und deinen server mit den rl_reset_terminal funktionen entsprechend.
    Geändert von Joghurt (19-05-2005 um 10:17 Uhr)

  13. #13
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Also dieses 0x1B ist ja dezimal 27. Das müsste ja das Zeichen für ESC sein, oder?!

    Ergo ist alles, was danach folgt, eine Escape Sequenz.

    Jetzt muss ich genau wissen, welche Seite meines Pseudo Terminals ich wie konfigurieren muss, damit an Readline saubere Escape Sequenzen ankommen.

    Meine Güte ist das kompliziert.

  14. #14
    Registrierter Benutzer
    Registriert seit
    25.10.2004
    Beiträge
    819
    Also, wenn du Pfeil oben drückst, sendet der Client nur ein ESC? Kein Wunder, dass da nichts weitergeht.

    Ich glaube ja immer noch, dass du Readline mitteilen musst, dass es sich auf einem VT100 befindet.

  15. #15
    Registrierter Benutzer
    Registriert seit
    02.07.2004
    Beiträge
    456
    Okay, Problem war, daß er durch das zeichenweise Einlesen auch die Escape Sequenzen gestückelt hat. Das führte dazu, daß readline erst ein 0x1B (ESC) bekam, das verarbeitete und dann auf die ESC Sequenz wartete, die aber nie kam, weil mein Programm durch das blockierende Read keine Möglichkeit mehr hatte, die Sequenz vom Socket einzulesen. Ärgerlich...

    Das ganze scheint noch ein bisschen schwammig implementiert zu sein. Aber so funktioniert's jetzt einigermaßen.

    Was jetzt noch Probleme bereitet ist, daß Readline noch Schwierigkeiten mit bestimmten ESC Sequenzen hat. Beispielsweise POS1 ( ESC[[2~ ), wohingegen ENDE funktioniert. Oder mit Tasten-Kombinationen wie STRG+C.

    Wie binde ich den Kram denn mal sauber und komplett vernünftig ein? Muss ich tatsächlich alle Eingaben selbst parsen? Kann das Terminal das nicht durch irgendwelche Einstellungen selbsttätig?



    Nochwas: ich muss für Putty bei jeder Ausgabe einen Carriage Return mit einfügen, damit das nicht so blöd ausschaut. Readline tut dies aber nicht, also sind Kommando-Autocomplete und solche Sachen immer schlecht formatiert.

    Wie kriege ich es hin, daß entweder Putty keine CRs braucht (muss automatisch passieren, also ohne den Haken in den Optionen), oder daß mein Programm automatisch an jedes NL ein CR dranhängt (möglichst auch automatisch und nicht zufuß)?


    EDIT: Daß es ein VT100 ist, teile ich Readline bereits mit

    rl_terminal_name = "VT100";

    mit.


    EDIT: Verständnisfrage:

    TerminalMasterSettings.c_iflag &= ~(ICANON|ECHO);

    schalte ich damit die Flags ICANON und ECHO ein oder aus? Wo ist der Unterschied zu

    TerminalMasterSettings.c_iflag |= ICANON|ECHO;

    ?
    Geändert von 7.e.Q (19-05-2005 um 14:41 Uhr)

Lesezeichen

Berechtigungen

  • Neue Themen erstellen: Nein
  • Themen beantworten: Nein
  • Anhänge hochladen: Nein
  • Beiträge bearbeiten: Nein
  •