[Richard Suchenwirth] - ''This page ("simply Tcl") is in German for even more i18n;-) It is the draft of a planned presentation. First thought of ppt or Word, but somehow I preferred the Wiki - plus allowing at least German speakers from Tclworld to discuss this while I'm writing it - best place your comments etc. at bottom of page!'' For a French course, see [http://freealter.com/fr/ProjetsLibres/tcltk/] See also [Einfach man Tcl] - [Tcl/Tk: Programmieren auf dem PocketPC]. ---- '''Tcl''' (Tool command Language, ausgesprochen "tickel") ist eine einfache, aber maechtige '''Programmiersprache'''. Da Turing-vollstaendig, kann im Prinzip jede Aufgabe in Tcl geloest werden, jedoch nicht immer optimal. Tcl wird (zusammen mit Perl, Python, VB) auch als '''Scriptingsprache''' bezeichnet. Zu "System-Programmiersprachen" wie C, Java, Assembler bestehen folgende Unterschiede: * nahezu typfrei ("Everything is a string" / intern schon getypt) * Variablen muessen nicht deklariert werden - automatische Speicherverwaltung * Ausfuehrung durch Interpreter (byte-kompilierte Prozeduren) * Interaktive Nutzung moeglich, reichhaltige Introspektionsmoeglichkeiten * Aussagekraeftige Fehlermeldungen (fast "Online-Hilfe"); Weiterarbeit moeglich * Compile-Link-Zyklus entfaellt, daher schnelle Entwicklung * Wartbarkeit durch Script-Editieren bis zur Zielmaschine * Laufzeitverhalten teilweise schlechter als vollkompilierter Code Tcl kann mit '''C/C++/Java-Code fuer laufzeitkritische Teile''' auf verschiedene Weisen verbunden werden: * Einbettung (Applikation enthaelt Tcl-Interpreter) * Erweiterung (statischer oder dynamisch gelinkter Code wird fuer Tcl zugaenglich gemacht) * ''Tcl_LinkVar'': Bindung von Tcl-Variablen an C-Variablen (int, double, boolean, string) * ''Tcl_CreateObjCommand'': Bindung eines Tcl-Kommandos an eine C-Implementation * externe Server ([exec], [open]: namenlose bidirektionale Pipes) '''Tk''' (ToolKit) ist die wichtigste Tcl-Erweiterung, ein plattformunabhaengiger GUI-Toolkit (X11, Windows, Macintosh): * extrem einfache GUI-Spezifikation * gleiche Quelldatei laeuft auf mehreren Plattformen mit "native look & feel" * Leistungsfaehige "Widgets": canvas, text, menu, scrollbar, Buttons... * Einfache aber maechtige Geometrie-Manager: pack, place, grid * Tk wird auch mit Perl und Python als GUI-Toolkit verwendet Weitere bekannte '''Erweiterungen''': * [[incr Tcl]] - OO-Unterstuetzung aehnlich C++ * Expect - Unterstuetzung von Kommunikation zwischen verschiedenen Rechnern, "Fernsteuerung" von Applikationen * BWidget - Erweiterung von Tk um komplexere "Megawidgets" in purem Tcl * TclX - Erweiterung um Posix-Systemfunktionalitaeten Tcl bietet starke Unterstuetzung von '''Internationalisierung''' mit '''Unicode''': * alle Strings intern in Unicode/UTF-8 * Stringkonstanten mit ''\uxxxx'' koennen jeden Unicode darstellen * Hin- und Herkonvertierung in viele andere internationale Encodierungen * Tk: automatische Zeichensuche, falls der aktuelle Font ein gewuenschtes Zeichen nicht enthaelt * Einfache Lokalisierung mit [msgcat] Tcl vereint Konzepte aus verschiedenen Software-Welten: * LISP/Scheme: Liste als zentrale Datenstruktur, polnische Notation, Code = Daten * C: Kontrollstrukturen (for, while, if); Arithmetik * Shells: Trennung von Parsing und Ausfuehrung; cd, pwd, glob... * SNOBOL/awk: assoziative Arrays (Hashtables) * X/...: Eventmodell, ereignisgesteuerte Verarbeitung * ?: Traces - Notifikation bei Lesen/Setzen/Loeschen einer Variablen Tcl hat die '''einfachste Syntax''' aller Scriptingsprachen: * keine Schluesselwoerter, alle Kommandos werden gleich behandelt * ''Script'': Folge von Kommandos, durch \n oder ";" getrennt * ''Kommando'': Folge von Woertern, durch Whitespace getrennt; erstes Wort ist Kommandoname * ''Wort'': whitespacelose Zeichenkette, oder gruppiert mit ".." {..} * "..": "substituiere und gruppiere mich zu einem Wort" * {..}: "gruppiere mich, aber ruehr meinen Inhalt nicht an" * Rekursiver Interpreteraufruf fuer [[..]]-eingebettete Scripts * [[..]]: "evaluiere mich zuerst zu einem String" * Variablensubstitution durch ''$name'' , Array-Elemente: ''$name($key)'' * Namensraum-Syntax: ''::foo::bar''; :: ist der globale Namensraum * Alle weitere Syntax liegt in Verantwortung der Kommandos '''Kommandos''' haben folgende Eigenschaften: * Implementation in C oder als Tcl-''proc'' * Argumente mit Defaultwert oder variabler Anzahl moeglich * jedes Kommando parst seinen Input nach eigenem Bedarf * Kommandos koennen umbenannt und ueberladen werden * Bytecompilation beim ersten Aufruf, danach Wiederverwendung des Bytecodes * Neudefinition zur Laufzeit moeglich (''proc'' ist auch nur ein Kommando) * auch Kontrollstrukturen sind "nur" Kommandos, koennen ueberladen oder erweitert werden * alle Variablen von Procs sind lokal, koennen aber mit globalen Variablen oder Variablen hoeher im Callstack verbunden werden '''Strukturierung''' von Tcl-Applikationen: * Auch in Tcl kann man unwartbaren Code schreiben, muss es aber nicht * Explizites Laden anderer Scripte mit ''source'' (''#include'' zur Laufzeit) * Automatisches Laden anderer Scripte bei Bedarf ''(auto_index)'' * Modularisierung durch ladbare ''packages'' (Script und/oder kompiliert) * Daten- und Prozedurkapselung durch Namensraeume '''Datenhaltung in Tcl''': * Listen mit automatischer Speicherverwaltung * Assoziative Arrays (Hashtables): Schluessel -> Wert * Ueber geeignete Schluessel ($i,$j) mehrdimensionale Arrays simulierbar * Formatierung von Daten aller Art (auch binaer, mit Nullbytes) in Strings * Implizite Datenwandlung Liste <-> String <-> Zahl bei Bedarf '''Ablaufsteuerung in Tcl''': * z.B. konventionell sequentiell bzw. ''if, for, while, foreach'' * Bindung von Kommandos an GUI-Ereignisse (Maus, Tastatur) * Bindung von Kommandos an Datei-Ereignisse ''(fileevent)'' * Bindung von Kommandos an Timer-Ereignisse ''(after)'' * Bindung von Kommandos an Daten-Ereignisse (Variablen-Trace) '''Die wichtigsten Kommandos''': * [set] varname value ;# Wertzuweisung * [expr] mathexpression ;# Arithmetik und Vergleiche * [proc] name arglist {body} ;# Definition einer Prozedur * [if] {condition} {body} * [while] {condition} {body} * [foreach] iterator liste {body} ;# Iteration ueber Liste * [for] {set i 0} {$i<$max} {incr i} {...} ;# analog zu C * [return] ?value? ;# kann weitgehend vermieden werden * [break] ;# aus while/foreach/for-Schleifen * catch {body} ?result? ;# faengt Fehler in ''body'' auf * [error] "deutliche Fehlerbeschreibung" '''Die wichtigsten Listen-Kommandos:''' * [list] el... ;# Anlage einer Liste aus Einzelelementen * [lappend] listname el... ;# Anhaengen weiterer Elemente * [lindex] list index ;# Extraktion eines Elements anhand Position * [lrange] list from to ;# Extraktion eines zusammenhaengenden Bereichs * [lsearch] list term ;# Suche eines Elements in einer Liste * [llength] list ;# gibt die Laenge der Liste zurueck * [split] string ?splitchars? ;# Zerlegt einen String an ''splitchars'' in Listenelemente * [join] list ?joinchars? ;# Fuegt ''joinchars'' zwischen je zwei Listenelemente, erzeugt einen String '''Die wichtigsten String-Kommandos''': * [string] length/is/compare/first... * [append] varname string.. ;# verlaengert den String in ''varname'' * [regexp] re string.. ?-> varname1 varname2..? * [regsub] re instring replacement outvarname ;# ersetze ''re'' in ''instring'' durch ''replacement'', Ergebnis geht nach ''outvarname'' '''Die wichtigsten I/O-Kommandos:''' * [gets] channel ?varname? ;# bis Zeilenende (exklusiv) * [puts] channel string ;# mit Zeilenvorschub * [open] filename ?mode? ;# gibt Channel-Id zurueck * [close] channel * [read] channel ?howmuch? ;# ohne Ruecksicht auf Zeilenenden ---- '''Welche Probleme gibt es in Tcl nicht?''' * Es gibt nicht einfache Hochkommas und doppelte Anführungszeichen, evtl. auch noch mit verschiedener Bedeutung, sondern nur doppelte Anführungszeichen * Es wird nicht je nach Kontext ein einfaches (Zuweisung) und ein doppeltes (Vergleich) Gleichzeitszeichen verwendet, sondern immer nur das doppelte. Zuweisungen geschehen nämlich ohne Gleichzeitszeichen! * Bei Unix: Man muss den Pfad zum Tcl-Interpreter nicht in die Script-Datei einbauen. Er wird immer gefunden, sofern er im Pfad (Variable PATH) enthalten ist. ---- Tcl hat eine kleine, aber qualifizierte weltweite '''Anwendergemeinschaft''', die vor allem auf folgenden oeffentlichen Wegen kommuniziert: * Newsgroup news:comp.lang.tcl ("die freundlichste Gruppe im Usenet") * Tcl'ers Wiki, bidirektionale Website mit reichhaltigem Inhalt: http://wiki.tcl.tk/ * Tcl'ers Chat (manchmal weltweites Debugging in quasi-Echtzeit) http://mini.net/cgi-bin/chat.cgi * [Tcl Developer Xchange], Download: http://www.tcl.tk * Fehlermeldungen und Patchvorschlaege an http://sourceforge.net/projects/tcl/ ---- '''Beispiel: Mittelwert einer Zahlenliste - prozedural oder funktional''' proc mean {L} { set sum 0 foreach element $L { set sum [expr {$sum+$element}] } return [expr {$sum/[llength $L]}] } #### or: #### proc mean L {expr double([join $L +]) / [llength $L]} ;# funktional Die Elemente der Liste L werden mittels ''[join]'' durch "+"-Zeichen zu einem String verbunden. Dieser wird in ''[expr]'' ausgewertet, durch ''double'' in Gleitkommazahl gewandelt (um Integer-Division zu vermeiden) und durch die Laenge der Liste geteilt. Das Ergebnis von ''[expr]'' ist implizit der Rueckgabewert von ''mean''. ---- '''Beispiel: deutsche Umlaute normieren''' proc noUmlaut s {string map {Ä AE Ö OE Ü UE ß SS ä ae ö oe ü ue} $s} ''[string] map'' wird mit einer Liste von alternierend von-nach und einem String aufgerufen nimmt Ersetzungen von ''von'' nach ''nach'' vor und gibt den geaenderten String zurueck, der auch Rueckgabewert von ''noUmlaut'' ist. ---- '''Beispiel: Zufaelliges Ziehen ohne Zuruecklegen aus einer Liste''' proc ldraw {_L} { upvar 1 $_L L set pos [expr {int(rand()*[llength $L])}] set res [lindex $L $pos] set L [lreplace $L $pos $pos] set res } Die ''rand''-Funktion von [expr] liefert eine Zufallszahl zwischen 0 und <1, die mit der Listenlaenge multipliziert und abgerundet einen gueltigen Listenindex ''pos'' ergibt. Das dortige Element wird geholt und mit [lreplace] aus der Liste entfernt, die deshalb ueber Name und [upvar] uebergeben werden muss. ---- '''Beispiel: Spiegeln einer Liste''' proc lrevert L { set i [llength $L] set res {} while {$i} {lappend res [lindex $L [incr $i -1]]} set res } % lrevert {foo bar grill test} test grill bar foo Die Schleife ueber die Elemente der Liste beginnt mit N-1 (N als Laenge der Liste), da String- und Listenindizes ab 0 zaehlen, und endet mit 0. Die entsprechenden Listenelemente werden mit [lindex] geholt und mit [lappend] an die Ergebnisliste ''res'' gehaengt. ''[set] res'' ist als letztes Kommando einer [proc] aequivalent zu ''[return] $res''. ---- roho Statt "incr $i -1" muss es "incr i -1" heissen ---- '''Beispiel: Introspektion des eigenen Proc-Namens''' proc myProcName {} {lindex [info level -1] 0} ''[info] level'' gibt Zugriff auf den Aufrufstapel (callstack), ''info level -1'' das Kommando, mit der der Aufrufer dieser Prozedur aufgerufen wurde. Das erste Element des Kommandos ([lindex] ... 0) ist definitionsgemaess der Name. ---- '''Beispiel: Zahlengenerator''' Diese Prozedur gibt bei jedem Aufruf eine andere Zahl zurueck, aufsteigend 1,2,3,.... Bei jedem Aufruf definiert sie sich neu, wobei der Body unveraendert bleibt (sie "schaut in sich selbst", aber der Defaultwert fuer den optionalen Parameter ''seed'' inkrementiert wird. proc intgen {{seed 0}} { proc intgen "{seed [incr seed]}" [info body intgen] set seed } ---- '''Beispiel: Konstanthalter''' Die Variable ''name'' wird auf den Wert ''value'' gesetzt, und zwar im Skopus des Aufrufers ([uplevel] 1). Dann wird ebenfalls dort ein Variablentrace gesetzt, der bei schreibenden Zugriffen auf ''name'' den urspruenglichen Wert zuruecksetzt: proc const {name value} { uplevel 1 [list set $name $value] uplevel 1 [list trace var $name w "set $name [list $value];#" ] } % const Pi 3.14159 % set Pi 4 3.14159 ---- '''Beispiel: Ueberladen eines Kommandos''' Jedes Tcl-Kommando, auch das elementarste [set], kann ueberladen werden, um z.B. Zusatzfunktionalitaet hinzuzufuegen (etwa auf ''stdout'' die Aktivitaet anzuzeigen). Dazu wird das Original zunaechst umbenannt, um es nicht zu verlieren: rename set _set proc set {name args} { if [info level]>1 {puts -nonewline [info level -1]:} puts [info level 0] uplevel 1 _set $name $args } So entsteht mit wenig Aufwand Debug-Funktionalitaet, die sich wieder ausschalten laesst mit rename set _; rename _set set; rename _ "" ;# loeschen ---- '''Beispiel: Klagloser Incrementor''' Das Tcl-Kommando [incr] leistet schnelle Integer-Addition bzw. Subtraktion, vorausgesetzt die Variable existiert bereits. Damit sie im anderen Fall automatisch angelegt wird (wie bei ''awk'' ueblich), faktorisieren wir die sonst erforderliche Existenzpruefung an diese proc aus: proc inc {varname {amount 1}} { upvar 1 $varname var if {![info exist var]} {set var 0} incr var $amount } Hier wird die lokale Variable ''var'' an den in ''varname'' uebergebenen Namen einer (moeglichen) Variablen (kann auch Array-Element sein) im '''Skopus des Aufrufers''' gebunden. Damit kann ''var'' geprueft, intialisiert, inkrementiert werden.. und fuer den Aufrufer ist das gleiche mit ''$varname'' geschehen. ---- '''Beispiel: Zeichenhaeufigkeit''' proc charFreq string { foreach char [split $string ""] { inc t($char) } foreach {char count} [array get t] { lappend pairs [list $char $count] } lsort -integer -decreasing -index 1 $pairs } Der ''string'' wird in Einzelzeichen gesplittet, ueber die iteriert und ein Element in dem temporaeren Array ''t'' klaglos inkrementiert wird. Der Inhalt des Arrays, eine ungeordnete flache Liste von alternierend Zeichen und Anzahl, wird in eine Liste von Paaren gewandelt, die dann bequem nach absteigender Haeufigkeit sortiert werden kann. ''String'' kann Megabytes lang und natuerlich auch koreanisch, griechisch oder arabisch sein... % charFreq Tennessee {e 4} {n 2} {s 2} {T 1} ---- '''Beispiel: Fehlertolerantes Lesen einer Textdatei''' proc readfile {filename} { if ![catch {open $filename} fp] { set res [read $fp [file size $filename]] close $fp } else {set res {}} return $res } Wenn das Oeffnen der Datei ''filename'' gelingt (der ''[catch]'' nicht anschlaegt), wird sie in ganzer Laenge in die Variable ''res'' gelesen und zurueckgegeben. Andernfalls wird ein leerer String zurueckgegeben. Moegliche Anwendung als Zeilenzaehler: proc wc-l filename {llength [split [readfile $filename] \n] } ---- '''Beispiel: Textausgabe in Datei, mit Sicherheitskopie''' proc text2file {text filename} { if [file exists $filename] { file rename -force $filename $filename.bak } set fp [open $filename w] puts $fp $text close $fp } Existiert eine Datei namens ''filename'', so wird sie in ''(filename).bak'' umbenannt. Dann wird eine neue Datei des Namens zum Schreiben geoeffnet, der uebergebene String (kann beliebig lang sein) mit abschliessendem Zeilenvorschub hineingeschrieben, und die Datei geschlossen. ---- '''Beispiel: Internet-Dateiherunterlader''' package require http http::config -proxyhost proxy -proxyport 80 puts [http::data [http::geturl [lindex $argv 0]]] Verwendet wird das mit Tcl mitgelieferte package ''http''. Mit ''http::config'' wird der hier uebliche Proxy-Rechner eingestellt. Die bei Aufruf des Scripts angegebene URL wird geladen und ihr Datenanteil (der eigentliche Inhalt) auf ''stdout'' ausgegeben, kann jedoch in eine Datei umgeleitet werden: $ tclsh ~/tcl/wwwget.tcl www.bahn.de/img/agb.gif > t.gif ---- '''Beispiel: ein kleines Zeichenprogramm''' proc doodle {w {color black}} { bind $w <1> [list doodle'start %W %x %y $color] bind $w {doodle'move %W %x %y} } proc doodle'start {w x y color} { set ::_id [$w create line $x $y $x $y -fill $color] } proc doodle'move {w x y} { eval $w coords $::_id [concat [$w coords $::_id] $x $y] } pack [canvas .c] doodle .c Fuer den Canvas werden Bindungen fuer "Mausklick links" (neue Linie beginnen, deren Anfang und Ende zusammenfallen: ''doodle'start'') und "Mausbewegung mit gedrueckter linker Taste" (aktuelle Linie, durch globale Variable ''::_id'' zugaenglich, zum Zielort der Bewegung verlaengern: ''doodle'move'') definiert. ---- '''Beispiel: Digitaluhr in 6 Zeilen''' proc every {ms body} { eval $body after $ms [list every $ms $body] } pack [label .clock -textvar time] every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]} Zunaechst wird ein einfacher, aber wiederverwendbarer Timer, ''every'', definiert, der den uebergebenen ''body'' (Tcl-Kommandos) ausfuehrt und anschliessend veranlasst, dass er nach ''ms'' Millisekunden erneut aufgerufen wird. Dies ist keine Rekursion! UI: Ein ''[Label]'' wird angelegt, an die Textvariable ''time'' gebunden, und in das Hauptfenster "gepackt". Mit ''every 1000 ...'' wird der Wert von ''time'' sekuendlich auf die aktuelle Zeit gebracht und automatisch im Label angezeigt. ---- '''Beispiel: Analoguhr in etwas mehr Zeilen''' proc drawhands w { $w delete hands set secSinceMidnight [expr {[clock seconds] - [clock scan 00:00:00]}] foreach divisor {60 3600 43200} length {45 40 30} width {1 3 7} { set angle [expr {$secSinceMidnight * 6.283185 / $divisor}] set x [expr {50 + $length * sin($angle)}] set y [expr {50 - $length * cos($angle)}] $w create line 50 50 $x $y -width $width -tags hands } } pack [canvas .c -width 100 -height 100 -bg white] every 1000 {drawhands .c} Ein [canvas] (Zeichenflaeche) mit 100*100 Pixeln Groesse wird angelegt. Jede Sekunde werden die Uhrzeiger weggeworfen und als drei ''line''-Items neu gezeichnet, wobei der Zielpunkt des Zeigers ueber Winkelfunktionen bestimmt wird. Man beachte die Mehr-Listen-Form von [foreach], die in drei Durchlaeufen die drei Iterator-Variablen aus den konstanten Listen belegt. Der Timer ''every'' aus der Digitaluhr wird wiederverwendet. ---- '''Beispiel: eine umschaltbare Uhr''' Zuletzt eine Uhr, die durch Mausklick von analog zu Digital und zurueck wechselt. Dazu ein wiederverwendbarer Umschalter, der das sichtbare von zwei Widgets unsichtbar ([pack] forget) und das andere sichtbar macht: proc toggle {w1 w2} { if [winfo ismapped $w2] { foreach {w2 w1} [list $w1 $w2] break ;# swap } pack forget $w1 pack $w2 } Man beachte das Idiom zum Vertauschen zweier Variablenwerte (w1,w2) mit [foreach]. Schliesslich legen wir die beiden Uhren an und setzen sie in Gang, "packen" zunaechst die Analoguhr und binden den linken Mausklick (<1>) im Hauptfenster (.) an die Umschaltung: canvas .analog -width 100 -height 100 -bg white every 1000 {drawhands .analog} label .digital -textvar time -font {Courier 24} every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]} pack .analog bind . <1> {toggle .analog .digital} ---- Diskussion: [JCW] - I'd like to make a few comments: * When you say ''everything is a string'', please do mention that this is only at the conceptual level. Internally, things run at the speed of typed data. * The first example, Mittelwert/average, is of course very clever, but are you sure you want to bring this across as first example? How about ''pack [[button -text "Hello world"]]''? * No mention of how trivial networking is? TclHttpd? AOL? But above all... it's great to see Tcl summarized this way! ---- [RS] (1) fixed. (2) Added procedural counter-example. (3) Know too little... ;-) ---- Sorry, I messed up all Umlauts [wdb]: repaired, don't worry ---- [MSW]: but I do know how easy networking is. (back to german) '''Beispiel: Ein einfacher Zeitserver''' proc server_handle {chan client port} { puts stderr "Neue Verbindung von $client:$port" lappend ::clients $chan fconfigure $chan -blocking 0 -buffering line } set clients [list] every 1000 { foreach c $::clients { puts $c [clock format [clock seconds] -format %H:%M:%S] } } socket -server server_handle 15000 vwait banzai Zuerst wird ein Kommando definiert, das immer dann aufgerufen wird, wenn auf einem socket neue Verbindungen anstehen. Die entsprechenden, benoetigten Daten werden dem Kommando uebergeben. Der server_handle haengt daraufhin den geoeffneten Socket an eine globale Liste; Jede Sekunde schickt der Server dann an alle sockets in dieser Liste einen Zeitstempel. Das [vwait] wird benoetigt um Tcl in die ''Eventloop'' zu versetzen, d.h. das tcl auf Ereignisse warten & reagieren kann. Zu diesem Zeitpunkt laeuft der Server und ist bereit - man kann ihn ausprobieren mit z.B. ''telnet'': $ telnet localhost 15000 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. 23:11:10 23:11:11 ... usw. Alternativ kann man natuerlich auch einen kleinen client bauen: '''Beispiel: Eine Netzwerkuhr''' proc get_time_net {server port lab} { set chan [socket $server $port] fconfigure $chan -buffering line fileevent $chan readable "trans_time from $chan to $lab" } proc trans_time {from chan to lab} { $lab configure -text [gets $chan] if {[eof $chan]} { $lab configure -text {Keine Verbindung zum Server} ; catch { close $chan } } } pack [set lab [label .l]] get_time_net localhost 15000 $lab get_time_net oeffnet einen [socket] zu besagtem server/port, und richtet einen callback ein, der aufgerufen wird, sobald Daten auf dem socket anstehen. Der callback wiederum liest die Daten, und passt den Text des labels an. Hier braucht man kein vwait - das Beispiel benutzt die ''wish'', welche automatisch in der eventloop ist. '''Anmerkung:''' Der Server vertraegt es nicht, wenn ein client seine Verbindung wieder verliert. Um den Server vor solchen Problemen zu beschuetzen baut man... '''Beispiel: Ein stabiler Zeitserver''' proc server_handle {chan client port} { puts stderr "Neue Verbindung von $client:$port." lappend ::clients $chan fconfigure $chan -blocking 0 -buffering line fileevent $chan readable "client_handle $chan $client $port" } proc client_handle {chan client port} { gets $chan if {[eof $chan]} { catch { close $chan } set ::clients [lreplace $::clients [set idx [lsearch $::clients $chan]] $idx] puts stderr "Verbindung zu $client:$port geschlossen." } } set clients [list] every 1000 { foreach c $::clients { puts $c [clock format [clock seconds] -format %H:%M:%S] } } socket -server server_handle 15000 vwait banzai Dieser Server liest alle Anfragen vom client, nur um sie wegzuschmeissen - und das tut er nur, um herauszufinden, ob die Verbindung zum Client unterbrochen ist (via [eof]) . Falls ja, loescht er ihn aus seiner Verbindungsliste. '''Beispiel: Ein tcl server''' rename exit __exit__ proc server_handle {chan client port} { append ::buf($client) [gets $chan] if {[info complete $::buf($client)]} { rename puts _puts; proc puts args "_puts $chan \[lindex \$args end\]" ;# damit alle ausgaben beim client landen proc exit args "catch { close $chan}" ;# damit der client sich, und nicht den Server mit 'exit' beendet catch {set reply [uplevel $::buf($client)]} reply rename puts {} ; rename _puts puts ; rename exit {} set ::buf($client) "" } else { puts -nonewline $chan ">> "; flush $chan } if {[catch {eof $chan}] || [eof $chan]} { catch { close $chan } set ::clients [lreplace $::clients [set idx [lsearch $::clients $chan]] $idx] puts stderr "Verbindung zu $client:$port geschlossen." } elseif {[info exists reply]} { puts -nonewline $chan " --> $reply\n% "; flush $chan } } set clients [list] socket -server server_handle 15000 vwait chaos Ein komplexeres Beispiel - der Server liest Anfragen von den clients, und prueft (mit [info] complete) ob diese Anfragen vollstaendige tcl Kommandos sind. Falls ja, fuehrt er sie aus, faengt etwaige Fehler ([catch]) ab, und schickt die Antwort an den client - wobei er auch gleich prueft ob die Verbindung zum client noch besteht. Die Funktion [exit], die normalerweise den Server beenden wuerde, beendet nun den client; [puts] wurde geaendert (via [rename]) , automatisch nur auf den socket zu schreiben, und eventuelle vorherigen argumente (wie einen anderen channel oder ein -nonewline) zu ignorieren. Wie gehabt, der Server laeuft zu diesem Zeitpunkt, und kann mit z.B. ''telnet'' getestet werden. Wie man an diesen Beispielen sehen kann, ist die Kommunikation ueber sockets (fast) genauso einfach wie ueber stdout oder in Dateien. Die gezeigten Beispiele sind jedoch sehr low-level, es gibt zahlreiche Bibliotheken, mit denen man auch auf einem hoeheren Niveau uebers Netzwerk kommunizieren kann. ---- [MSW]: Noch ein Kommentar zu den vorgestellten Erweiterungen: Viele Tcl Programmierer (die ich kenne) kennen Tk schon aus scheme (z.B. aus STK), und suchen in Tcl oft nach einer CLOS/STKlos aehnlichen Objektumgebung. [Otcl] als CLOS-like OO Erweiterung zu erwaehnen (nach incr tcl) waer glaub ich noch fein :) ---- [Category Tutorial]