freeX: Netzwerkverbindungen mit Perl, Teil 4
|
Perl hier, Perl da, Perl ist immer da!
| |
Die Sprache Perl ist sehr flexibel und eignet sich daher für viele
Aufgaben. In den meisten Fällen, insbesondere wenn Textdateien
verarbeitet oder Informationen aus ihnen gewonnen werden sollen, ist
Perl wahrscheinlich die beste Wahl.
|
|
1. Normale Arrays |
Nachdem die Grundzüge von Perl in den letzten Folgen dieser Artikelserie vorgestellt wurden, soll es in dieser Folge daran gehen, das erlernte zusammen mit neuem an einem konkreten Beispiel anzuwenden. Als Beispiel dient ein Mechanismus, News von einem Webserver, der irgendwo im Internet steht, zu extrahieren und anzuzeigen.
Normale Arrays
Eine Unterschied von Perl gegenüber C, dem besonderes Augenmerk gewidmet werden soll, befindet sich in der Behandlung von Arrays und Variablen. Wer mit Perl programmiert, muß sich keine Gedanken mehr über die Größe von Arrays und Variablen machen. Perl verlängert ein Array immer dann, wenn weitere Elemente hinzugefügt werden. String-Variablen (Zeichenketten) werden ebenfalls immer dann verlängert, wenn neue Zeichen hinzugefügt werden.
Auf einzelne Elemente eines Arrays wird wie in C mit einem numerischen Index in eckigen Klammern zugegriffen:
$foo = $array[0]; $bar = $array[3];
So wird der Variablen »$foo« der Wert des 0ten Elements des Arrays »@array« und der Variablen »$bar« der Wert des 3ten Elements des Arrays zugewiesen. Die Numerierung beginnt, wie man sieht, bei 0 und nicht bei 1. Die Länge eines Arrays erhält man mit »$#array«, auch hier beginnt die Numerierung bei 0, ein leeres Array hat daher die Länge »-1«. Das gesamte Array, inklusive aller Elemente, wird mit »@array« bezeichnet.
Um neue Elemente zum Array hinzuzufügen, wird an der entsprechenden Position einfach ein Wert eingetragen, so z.B.:
$array[100] = 10;
Dem 100sten Element des Arrays »@array« wird so der Wert »10« zugewiesen. Wenn das Array bisher noch keine 101 Elemente bessessen hat, bringt Perl es mit dieser Operation automatisch auf die erforderliche Länge. Der Wert von »$#array« wäre daher mindestens 100 (zurückgegeben wird jeweils der höchste Index; 0..100 macht 101 Elemente).
Um ein Element zu einem vorhandenen Array hinzuzufügen gibt es zwei Möglichkeiten. Zum einen kann man den Index des jeweils nächsten Elements berechnen und diesem Element einen Wert zuweisen:
$array[$#array+1] = "Neues Element";
Alternativ dazu wird das Array als Stapel (Stack) aufgefaßt und das neue Element oben auf den Stapel gelegt:
push(@array, "Neues Element");
Push und Pop sind klassische Stack-Operationen, daher gibt es auch in Perl die "inverse" Funktion »pop(@array)«, die das jeweils oberste Element vom Stack bzw. Array herunterholt und zurückliefert. Bei dieser Operation wird das Array gleichzeitig um dieses Element verkürzt.
Abbildung 1: Zusammenspiel Push und Pop bei Arrays
Bemerkenswert bei der Verwendung von Arrays ist die Funktion »shift()«. Sie hat ähnliche Auswirkungen wie die Routine »pop()«. Es wird ebenfalls ein Element aus dem Array zurückgegeben und das Array dabei um dieses verkürzt. Im Gegensatz zu »pop()« wird hier jedoch nicht das "oberste" oder zuletzt angehängte Element zurückgegeben, sondern das erste oder vorderste Element des Arrays. Aufgrund dieses Verhaltens wird »shift()« gerne für die sequenzielle Verarbeitung von Array-Elementen verwendet, wenn eine »for«-Schleife nicht adäquat ist, z.B. wenn mehr als nur ein Array-Element gleichzeitig verarbeitet werden soll.
Oftmals wird ein Array im Programm dazu benutzt, Zahlen in zugehörige Worte umzuwandeln, z.B. um aus den numerisch angegebenen Monaten eines Datums die ausgeschriebene Monatsnamen zu erhalten. In einem solchen Fall würde man ein Array initialisieren, das an der n-ten Position den Namen des n-ten Monats gespeichert hat. Es ist jedoch nicht nötig dafür zwölf einzelne Zuweisungen zu schreiben. Stattdessen initialisiert man in einem solchen Fall das Array in einem Stück mit den gewünschten Werten.
Innerhalb von Perl wird ein komplettes Array wie folgt initialisiert:
("Januar","Februar","März","April","Mai","Juni")
Dieses Array enthält sechs Elemente, und kann direkt einer Array-Variablen zugewiesen oder in Schleifen verwendet werden:
@array = ("Januar","Februar","März","April","Mai","Juni")
Wenn der erste numerische Monat nicht »0« sondern »1« ist, muß zusätzlich ein nicht verwendetes Element zum Array hinzugefügt werden. Damit sieht die Initialisierung wie folgt aus:
@array = ("","Januar","Februar","März","April","Mai","Juni")
Assoziative Arrays
Eine spezielle Form von Arrays stellen Assoziative Arrays dar. Im Gegensatz zu normalen Arrays ist der Index bei Assoziativen Arrays keine Zahl, sondern eine beliebige Zeichenkette, ein sogenannter Schlüssel oder "key". Zu jedem Schlüssel gibt es genau einen Dateneintrag im Assoziativen Array. Schlüssel können daher nicht mehrfach verwendet werden.
Als Schlüssel für ein Datenfeld kann eine Zahl (»10«) genauso verwendet werden wie ein Wort (»Montag«) oder ein ein zusammengesetzter Text (»heute morgen«). Perl verwendet intern Zeichenketten (Strings) und wandelt den angegebenen Schlüssel bei Bedarf automatisch um.
Assoziative Arrays sind weder sortiert noch sortierbar. Intern verwendet Perl einen Hash-Algorithmus, um die zu einem Schlüssel gehörenden Datenfelder schnell zu finden. Daher werden Assoziative Arrays oft auch als "Hash" oder "Hashes" bezeichnet. Wenn ein Assoziatives Array sortiert werden soll, muß aus allen Schlüsseln ein Array erstellt werden, das sortiert wird, und über das auf die Datenfelder zugegriffen wird. Dazu folgt weiter unten ein Beispiel.
Um Assoziative von normalen Arrays zu unterscheiden, werden für die Indizierung keine eckigen sondern geschweifte Klammern verwendet. Um auf ein Datenfeld in einem Assoziativen Array zuzugreifen, schreibt man daher:
$foo = $assoc{10};
Als Schlüssel wird in diesem Beispiel die Zahl »10« verwendet. Da Perl Zahlen automatisch in das richtige Format konvertiert, ist diese Zuweisung gleichwertig mit folgender:
$foo = $assoc{"10"};
Assoziative Arrays lassen sich wie normale Arrays auch mit einer einzigen Anweisung initialisieren. Ein gesamtes Assoziatives Array wird mit »%assoc« referenziert. Die Initialisierung könnte dann wie folgt aussehen:
%assoc = ('Januar', '1','Februar','2');
Um Assoziative Arrays bei der Initialisierung leichter von normalen Arrays zu unterscheiden und um die Zuweisungen deutlicher zu machen, wird oft eine andere Schreibweise für die Initialisierung verwendet. Aus dieser geht klar hervor, was der Schlüssel und was der Wert ist. Im obigen Beispiel würde man sich auch bei einer längeren Liste schon fragen, was der Schlüssel ist und was die eigentlichen Daten: die Monatsnummer oder der Monatsname?
Bei der deutlich sichtbaren Zuweisung von Schlüsseln und Werten steht zwischen beiden ein doppelter Pfeil »=>«. Die Paare werden weiterhin durch ein Komma voneinander getrennt. Das Assoziative Array von oben würde dann wie folgt initialisiert werden:
%assoc = ('Januar' => '1',
'Februar' => '2');
Das Assoziative Array wurde in diesem Beispiel nur aus Gründen der Übersichtlichkeit in zwei Zeilen aufgeteilt, Perl erlaubt es auch, alles in eine einzige Zeile zu schreiben. Insbesondere bei längeren Zuweisungen ist diese Form sowie die mehrzeilige Auflistung während der Initialisierung erheblich sinnvoller als die schlichte Auflistung der Schlüssel-Werte-Paare. So behält man viel leichter den Überblick über die im Programm verwendeten und vorab mit Werten belegten Assoziativen Arrays, insbesondere wenn man den Code später begutachten oder erweitern soll.
Sortierte Ausgabe
Die sortierte Ausgabe eines Assoziativen Arrays erfolgt zweistufig. Im ersten Schritt werden die Schlüssel sortiert und im zweiten Schritt zusammen mit den zugehörigen Werten ausgegeben. In Perl programmiert sieht das so aus:
%assoc = ('Januar' => '1',
'Februar' => '2',
'März' => '3',
'April' => '4');
print "Unsortierte Ausgabe:\n";
foreach $key (keys (%assoc)) {
printf " %s - %s\n", $key, $assoc{$key};
}
print "Sortierte Ausgabe:\n";
foreach $key (sort (keys (%assoc))) {
printf " %s - %s\n", $key, $assoc{$key};
}
| |
| Sortierte und unsortierte Ausgabe eines Hashes | |
Dieses Beispiel verdeutlicht, daß Perl Assoziative Arrays intern nicht willkührlich in der angegebenen Reihenfolge speichert, aber auch nicht alphabetisch sortiert, sondern einen speziellen Hash-Algorithmus verwendet.
Objekte in Perl
Viele zusätzliche Module, die mit dem Befehl »use« eingebunden werden, bieten dem Entwickler zusätzliche Funktionalität für Perl-Programme. Sehr viele Schnittstellen und Algorithmen sind bereits in Perl-Modulen implementiert und müssen daher nicht neu geschrieben werden. Zum Teil stellen die Module normale Funktionen zur Verfügung, die die benötigte Aufgabe erledigen. Teilweise werden jedoch spezielle Objektklassen zur Verfügung gestellt, die Funktionen und Variablen vereinigen. In diesem Fall haben die Funktionen immer einen Bezug zu dem Objekt, über das sie aufgerufen werden und beziehen sich auf dieses.
Vor der Benutzung von Objekten müssen diese erzeugt werden. Das geschieht mit dem Befehl »new«. Bei der Erzeugung muß angegeben werden, von welcher Klasse das Objekt sein soll. Dazu hier ein Rückblick auf das Beispiel aus der letzten Folge.
Beispiel: CGI
In der letzten Folge wurde auf diese Weise bereits ein Objekt der Klasse CGI angelegt. Dort geschah es mit den Zeilen:
use CGI; $cgi = new CGI;
Für die Klasse CGI sieht die Erzeugung des Objekts sehr einfach aus. Dort werden keine Parameter für die Einrichtung benötigt. Überlegt man sich, wie CGI funktioniert, dann wird schnell klar, weshalb. Ein CGI-Programm wird programmseitig nicht parametrisiert. Es werden keine Optionen an den Web-Server übertragen. Im Gegenteil, der Webserver übergibt seinerseits Parameter an das CGI-Programm: nämlich die CGI-Variablen.
Als Methoden werden z.B. »header()« und »param()« zur Verfügung gestellt. Sie werden nur in Verbindung mit dem jeweiligen Objekt verwendet. Im Beispiel aus dem vorherigen Heft geschieht dieses z.B. wie folgt:
print $cgi->header();
printf "foo ist %s.", $cgi->param('foo');
Mit dem ersten Befehl wird der HTTP-Header geschrieben, der vom Web-Server benötigt wird, um die Antwort zu erkennen sowie vom Browser, damit dieser weiß, wie er das empfangene Dokument darstellen soll: als normalen Text, HTML, Grafik oder über einen externen Betrachter (z.B. für Filme, Audio-Dateien etc.). Mit dem zweiten Befehl wird der Wert des Parameters »foo« ausgegeben.
CGI-Objekttypen
Die CGI-Bibliothek ist jedoch flexibel gestaltet. Das zurückgegebene HTTP-Objekt muß kein Text sein, sondern kann aus beliebigen Daten bestehen. Wenn Video-, Audio-Daten oder Grafiken zurückgeliefert werden, sollte der Typ entsprechend angepaßt werden. Normalerweise wird als Typ »text/html« zurückgeliefert. Dieser Typ ist jedoch nur für HTML-Code adäquat, nicht jedoch für Grafiken oder Video-Daten.
Wird vom CGI-Programm kein HTML-Code zurückgeliefert, dann sollte der Typ entsprechend angepaßt werden. Für diesen Fall und viele andere Anpassungen unterstützt die Methode »header()« die Übergabe von Parametern. Diese werden als Assoziatives Array angegeben, um ein Maximum an Flexibilität zu erhalten. Die Methoden des CGI-Moduls unterstützen bis zu 20 verschiedene Argumente. Auf diese Weise können alle nicht benötigten weggelassen werden.
Exemplarisch wird an dieser Stelle nur der Typ des zurückgegebenen Objektes geändert. Die weiteren Optionen sind in der Dokumentation beschrieben und werden mit dem Befehl »perldoc CGI« gelesen. Der Typ wird mit dem Element »-type« geändert. Soll zum Beispiel eine Grafik vom Typ PNG (Portable Network Graphic) zurückgegeben werden, so wäre der zugehörige Typ »image/png«. Mit dem folgenden Befehl wird der Typ wie gewünscht angepaßt:
print $cgi->header("-type" => "image/png");
In diesem speziellen Fall dürfen sogar die Hochkommas beim Schlüssel wegfallen. Als weiteren Spezialfall interpretiert die Methode »header()« das erste Argument als Typen, wenn es nicht mit einem Bindestrich beginnt. Wer sich Tipparbeit ersparen möchte, schreibt daher:
print $cgi->header("image/png");
Parameter beim »new()«-Konstruktor
Beim CGI können als Spezialfall jedoch auch Parameter übergeben werden. Mit dieser Methode können CGI-Variablen vorab initialisiert werden, insbesondere auch dann, wenn das verwendete Formular diese nicht enthält und sie sonst undefiniert wären. Das ist immer dann sinnvoll, wenn mit den Werten gerechnet wird, und sichergestellt werden soll, daß die Variablen mit Werten besetzt sind.
Zurück zum Beispiel dieser Folge. Es soll eine Netzwerkverbindung zu einem Webserver aufgebaut werden. Als Basis für Netzwerkverbindungen aller Art dient das Modul IO und im Speziellen IO::Socket.
Netzwerkverbindungen unter Unix
Unter Unix werden Verbindungen zu einem anderen Rechner über das Netzwerk mit Sockets abgewickelt. Wann immer in einem Programm auf einen anderen Rechner zugegriffen wird, sind Sockets im Spiel. Teilweise bleibt deren Benutzung jedoch verborgen, wenn dem Programmierer mit Hilfe einer komfortablen Schnittstelle intelligente Methoden zur Verfügung stehen.
Das ist nicht nur in C, sondern auch in Perl so. Netzwerkverbindungen werden in Perl mit der Socket-Bibliothek (IO::Socket) verwaltet. Diese Bibliothek stellt einfache Funktionen zur Verfügung, mit denen die Verbindungen aufgebaut werde und mit denen das Programm Daten schreibt bzw. liest. Die Dokumentation zu diesem Perl-Modul wird mit dem Befehl »perldoc IO::Socket« angezeigt.
Das Modul IO::Socket
Mit diesem Modul erhält man eine komfortable Bibliothek, um Netzwerkverbindungen aufzubauen, Daten auf diese Netzwerkverbindung zu schreiben sowie von ihr zu lesen und die Verbindung wieder abzubauen. Die geschriebenen und gelesenen Daten werden in Perl genauso behandelt wie beliebige andere Daten auch und können daher beliebig manipuliert werden.
Diese Bibliothek ist mehrere Abschnitte unterteilt. Ein Teil wird von der allgemeineren Bibliothek IO::Handle (Methoden und Objekte für Eingabe/Ausgabe-Operationen (englisch abgekürzt I/O) jeglicher Art) importiert. IO::Handle bildet die Basis für alle anderen IO::-Module. Der für Netzwerke relevante Teil der Bibliothek (Sockets vom Typ »AF_INET«) befindet sich in in der Unterklasse INET.
Für jede Netzwerkverbindung wird ein eigenes Perl-Objekt erzeugt. Die Kommunikation über das Netzwerk erfolgt mit Hilfe der mit dem Objekt verbundenen Methoden bzw. genereller Socket-Funktionen. Anders als beim CGI-Objekt, müssen bei der Erzeugung von Internet-Sockets jedoch Parameter angegeben werden, welche angeben, zu welcher IP-Adresse eine Verbindung aufgebaut werden soll, welcher Port benutzt werden soll etc.
| Parameter des Konstruktors | |
| Listen | Anzahl der vom Objekt zu speichernden Verbindungen, bis das Programm die Verbindung annimmt; falls Programm als Server agiert |
| LocalAddr | Lokale Adresse der zu öffnenden Verbindung, sowohl für Server als auch für Clients |
| LocalPort | Lokaler Port für die Verbindung; sowohl für Server auch für Clients |
| PeerAddr | Adresse des entfernten Rechners, zu dem eine Verbindung aufgebaut werden soll; nur bei Clients |
| PeerPort | Port auf dem entfernten Rechner; nur bei Clients |
| Proto | Zu verwendendes Protokoll: »tcp« oder »udp« |
| Reuse | SO_REUSEADDR vor Öffnen der Verbindung setzen |
| Timeout | Bestimmt den zeitbedingten Abbruch bei verschiedenen Operationen, wenn die Antwort ausbleibt |
| Type | Typ der Verbindung: SOCK_STREAM, SOCK_DGRAM etc. |
Bei der Erzeugung einer Netzwerkverbindung müssen nicht alle Parameter angegeben werden, weshalb auch ein flexibler Mechanismus verwendet wird, ähnlich wie bei der Methode »header()« (s.o.). Abhängig davon, welche Art der Verbindung aufgebaut werden soll, müssen die einen oder anderen Parameter angegeben werden. Die folgende Übersicht gibt Aufschluß darüber:
- Netzwerkverbindung als Client
- Wenn der Parameter Listen nicht gesetzt wird, nimmt die Bibliothek eine Client-Verbindung zu einem Server an. Daher müssen »PeerAddr« und »PeerPort« gesetzt werden, sonst weiß der Konstruktor nicht, zu welchem Rechner die Verbindung aufgebaut werden soll.
- Netzwerkverbindung als Server
- Soll das Perl-Skript den Port als Server öffnen und anderen Programmen (Clients) eine Verbindung zu diesem erlauben, dann muß der Parameter »Listen« gesetzt werden. Angegeben wird die Anzahl der Verbindungen, die die Bibliothek zwischenspeichern soll, bis das Perl-Skript sie übernimmt und damit arbeitet. Zusätzlich muß der lokale Port »LocalPort« angegeben werden.
In beiden Fällen darf eine lokale Adresse mit dem Parameter »LocalAddr« angegeben werden. Die Bibliothek baut dann eine Verbindung explizit von dieser Adresse auf. Das ist dann wichtig, wenn einem Rechner mehrere IP-Nummern zugewiesen worden sind. Bei einer Verbindung als Server hat eine solche Einstellung zur Folge, daß neue Verbindungen nur auf dieser IP-Nummer angenommen werden, nicht auf den anderen. Bei einer Verbindung als Client zu einem Server würde bei Angabe von »LocalAddr« ebenfalls genau diese IP-Nummer verwendet werden. Wenn mit den Adressen unterschiedliche Leitungen verbunden sind, kann so der Pfad vorgegeben werden, den die IP-Pakete nehmen.
Auch der Parameter »LocalPort« darf angegeben werden, wenn eine Netzwerkverbindung zu einem entfernten Server aufgebaut werden soll. Perl versucht dann, diesen Port lokal zu verwenden. Das klappt jedoch nur, wenn er noch nicht benutzt wird. Normalerweise wird kein lokaler Port angegeben. Die Socket-Bibliothek stützt sich dann auf die System-Bibliothek ab und läßt sie einen freien Port finden, der anschließend benutzt wird.
Für den Server und den Port dürfen anstatt der numerischen Werte auch symbolische Werte wie »www.debian.org« oder »www« angegeben werden. Symbolische Werte für Ports müssen in der Datei »/etc/services« stehen, damit sie in numerische umgewandelt werden können.
Wichtig ist, daß bei beiden Arten das Protokoll mit dem Parameter »Proto« angegeben wird. Hier sind als Werte »tcp« und »udp« möglich. Damit wird festgelegt, ob eine verbindungsorientiertes TCP oder verbindungsloses UDP für die Netzwerkverbindung verwendet wird. Wird dieser Parameter weggelassen, dann versucht Perl, das Protokoll anhand der Datei »/etc/services« zu ermitteln, klappt auch das nicht, wird »tcp« angenommen.
Aufbau einer Verbindung
Fügt man die Parameter mit dem Konstruktor »new()« zusammen, dann erhält man schließlich den Befehl zum Aufbau einer Netzwerkverbindung. Der folgende Befehl baut eine Verbindung zum Webserver vom Debian-Projekt auf, von dem Informationen extrahiert werden sollen.
$connection = new IO::Socket::INET (Proto=>"tcp",
PeerAddr=>"www.debian.org",
PeerPort=>"www");
print "Verbindungsaufbau fehlgeschlagen\n" if (!$connection);
| |
| Aufbau einer HTTP-Verbindung zu www.debian.org | |
Die lokale Adresse ist freilassen, es wird also die hauptsächliche IP-Nummer (die zu eth0 gehörende) verwendet. Der lokale Port wurde ebenfalls nicht angegeben, daher wird ein beliebiger Port verwendet, der frei ist. Die zweite Anweisung prüft nach, ob der Verbindungsaufbau erfolgreich war. An der Stelle sollte in einem Programm ein Fehler ausgegeben oder das Programm beendet werden.
Schreiben und Lesen
Mit der Funktion »send()« wird auf einen Socket geschrieben und mit »read()« von einem gelesen. Wird ein Socket bzw. allgemeiner ein Handle nicht mehr benötigt, so wird er mit den Befehl »close()« geschlossen und kann von dem Zeitpunkt an nicht mehr benutzt werden.
Um Daten von einem Webserver zu erhalten, muß der HTTP-Befehl »GET« oder »HEAD« zusammen mit einer URL und der Protokoll-Version abgesetzt werden. Der Befehl muß mit zwei Newline-Zeichen abgeschlossen werden. Schaut man sich die HTTP-Spezifikation genau an, so stellt man fest, daß neben dem einfachen »GET«- oder »HEAD«-Befehl weitere Parameter übertragen werden können, z.B. um den Namen des Browsers zu übermitteln. Damit der Server weiß, wann der Befehl vollständig gelesen wurde, werden zwei Zeilenumbrüche erwartet.
Der folgende Befehl fragt beim Webserver nach der Hauptseite an:
send($connection, "GET / HTTP/1.0\n\n", 0);
Der dritte Parameter enthält Flags, spezielle Parameter für diese Verbindung, die jedoch in diesem Fall nicht benötigt werden. In diesem speziellen Fall reicht es jedoch nicht, nur das Hauptverzeichnis zu verlangen, sondern es muß die gesamte URL angegeben werden, da es sich um einen Webserver mit mehreren virtuellen Adressen handelt, die anhand des Namens unterschieden werden. Damit sieht der Befehl wie folgt aus:
send($connection, "GET http://www.debian.org/ HTTP/1.0\n\n", 0);
Als nächstes muß von der Netzwerkverbindung gelesen werden, damit die Antwort vom Server verarbeitet werden kann. Das geschieht mit dem Befehl »read()«, der neben dem Socket eine lokale Variable benötigt, in der das gelesene gespeichert werden soll, sowie die maximale Anzahl zu lesender Zeichen. Im folgenden Beispiel soll maximal ein Megabyte gelesen werden, gespeichert wird das gelesene in der Variablen »$page«.
read($connection, $page, 1024*1024);
Da laut Spezifikation ein WWW-Server keine Unix-üblichen Zeilenumbrüche zurückliefert, wird das Ergebnis der Anfrage, das in »$page« zur Verfügung steht, konvertiert. Anstatt normaler Unix-üblicher Zeilenumbrüche (»\n«) werden die von DOS her bekannten (»\r\n«) verwendet. Das zusätzliche Zeichen soll nun gelöscht werden, was die folgende Zeile erledigt:
$page =~ s/\r//g;
Mit »print $page;« wird nun das gelesene und konvertierte auf dem Terminal ausgegeben. Dort sieht man schnell, daß die Antwort im Prinzip zweiteilig ist. Nach einem HTTP-Header und zwei Leerzeilen folgt erst die HTML-Seite. Da der HTTP-Header meistens nicht interessiert, wird er abgeschnitten. Das erledigt folgendes Code-Fragment:
@foo = split(/\n\n/, $page);
shift (@foo);
$page = join("\n\n", @foo);
Mit der ersten Zeile wird die lange Zeichenkette »$page« in ein Array umgewandelt, wobei die Zeichenkette an den Stellen aufgetrennt wird, an denen sie zwei Zeilenumbrüche aufweist. Die zweite Zeile schmeißt mit »shift()« das erste Element weg (siehe Anfang des Artikels), das nur aus dem HTTP-Header besteht. Mit der dritten Zeile wird das resultierende Array wieder in eine lange Zeichenkette umgewandelt, wobei zwischen den Array-Elementen wieder zwei Zeilenumbrüche eingefügt werden.
Informationen extrahieren
Von der Debian-Website sollen die aktuellen News-Einträge extrahiert und angezeigt werden. Dazu muß man sich die Struktur der Website ansehen. Relevant für diese Aufgabe wird die erhaltene Seite erst nach der Zeile »
News
«, und zwar sechs Zeilen lang. Sich auf die Anzahl der News-Verweise zu verlassen, ist nicht sinnvoll, da sich das Debian-Projekt entscheiden könnte, mehr oder weniger News auf die Hauptseite zu schreiben. Daher soll direkt die Struktur untersucht werden. Jeder News-Verweis hat folgende Struktur, wobei in der ersten Zeile zusätzlich ein »« steht.
<tt>[datum]</tt> <strong><a href="url">titel</a></strong><br>
Das folgende Code-Fragment wandelt die gelesene Seite zur leichteren Verarbeitung zuerst in das Array »@lines« um, das anschließend analysiert wird. Bis zum News-Bereich werden alle Zeilen ignoriert (siehe »next until«). Von dort an werden die Zeilen ein ein zweites Array »@news« kopiert, die auf das obige Muster passen. Damit das Beispiel noch in eine Zeitschriftenspalte paßt, wurde nicht das genaue Muster, sondern nur ein stark vereinfachtes (nämlich »^<tt>\[.*<\/strong><br>«) verwendet. Zum Schluß wird das neue Array »@news« ausgegeben.
@lines = split (/\n/, $page);
while ($line = shift (@lines)) {
next until ($line =~ /^<H2>News<\/H2>/);
while ($line = shift (@lines)) {
$line =~ s/^<p>//i;
if ($line =~ /^<tt>\[.*<\/strong><br>/) {
push (@news, $line);
}
}
}
print join ("\n", @news);
So könnten diese News-Artikel direkt in eigene Webseiten eingebunden werden. Wenn dieses nicht die Hauptseite von Debian ist, sollten jedoch die URLs angepaßt werden. Dafür sorgt der folgende Code anstelle der letzten Zeile von oben:
foreach $news (@news) {
$news =~ s,href=",href="http://www.debian.org/,;
print $news . "\n";
}
| Vollständiges Beispiel | |
use IO::Socket;
$connection = new IO::Socket::INET (Proto=>"tcp",
PeerAddr=>"www.debian.org",
PeerPort=>"www");
if (!$connection) {
print "Verbindungsaufbau gescheitert.\n";
exit (-1);
}
if (!send($connection, "GET http://www.debian.org/ HTTP/1.0\n\n", 0)) {
print "Konnte nicht schreiben.\n";
exit (-1);
}
if (!read($connection, $page, 1024*1024)) {
print "Konnte nicht lesen.\n";
exit (-1);
}
close ($connection);
$page =~ s/\r//g;
@foo = split(/\n\n/, $page);
shift (@foo);
$page = join("\n\n", @foo);
@lines = split (/\n/, $page);
while ($line = shift (@lines)) {
next until ($line =~ /^<H2>News<\/H2>/);
while ($line = shift (@lines)) {
$line =~ s/^//i;
if ($line =~ /^<tt>\[.*<\/strong><br>/) {
push (@news, $line);
}
}
}
print join ("\n", @news);
| |