| 1 |
root |
1.1 |
=encoding utf-8 |
| 2 |
|
|
|
| 3 |
|
|
=head1 AnyEvent - Eine Welt ohne Religion |
| 4 |
|
|
|
| 5 |
|
|
In diesem Vortrag möchte ich die neuesten Entwicklungen in Sachen |
| 6 |
|
|
AnyEvent vorstellen (asynchrones IPv4/IPv6, asynchrones DNS, TLS uvm.), sowie |
| 7 |
|
|
einige Hintergrundinformationen zu bestehenden Event-Modulen auf CPAN, |
| 8 |
|
|
sowie Benchmarks, vorstellen. |
| 9 |
|
|
|
| 10 |
|
|
Doch zuerst: der Titel klingt sicherlich etwas seltsam - was bedeutet er? |
| 11 |
|
|
|
| 12 |
|
|
Nun, AnyEvent ist, wie im Namensteil "Event" steht, ein Event-Model |
| 13 |
|
|
bzw. eine Event-Bibliothek. Event-Module oder -Bibliotheken stellen |
| 14 |
|
|
im allgemeinen Funktionalität bereit, mit derer man auf Ereignisse |
| 15 |
|
|
(Events), warten kann, speziell Ereignisse der Art "Zeitpunkt soundso |
| 16 |
|
|
ist erreicht" und "File Descriptor soundso kann jetzt nichtblockierend |
| 17 |
|
|
gelesen/geschrieben werden." |
| 18 |
|
|
|
| 19 |
|
|
Derartige Event-Bibliotheken gibt es zuhauf (Tk, Glib, Event, EV uvm.), |
| 20 |
|
|
doch trotz der offensichtlichen Vorteile Event-basierter Programmierung |
| 21 |
|
|
(z.B. beinahe automatische Parallelisierbarkeit) werden sie kaum in |
| 22 |
|
|
CPAN-Modulen eingesetzt. |
| 23 |
|
|
|
| 24 |
|
|
Meiner Meinung nach liegt der Grund schlicht darin, daß alle diese |
| 25 |
|
|
Event-Bibliotheken exklusiv sind: sie dulden niemanden neben sich. Wie |
| 26 |
|
|
bei den meisten Religionen auch, muss man sich einem dieser Modelle |
| 27 |
|
|
ausliefern. Konkret bedeutet dies für Modul-Autoren: benutzt man |
| 28 |
|
|
z.B. Glib in seinem Modul, muss auch jeder Benutzer dieses Modules Glib |
| 29 |
|
|
benutzen, was natürlich eine starke Einschränkung darstellt, vor allem, |
| 30 |
|
|
wenn das Modul selbst eigentlich nur "irgendwelche" Events benötigt, die |
| 31 |
|
|
im Prinzip jede beliebige Event-Bibliothek liefert. |
| 32 |
|
|
|
| 33 |
|
|
Einige Module auf CPAN haben versucht, aus dieser Not eine Tugend zu |
| 34 |
|
|
machen: POE z.B. kann andere Event-Module als Implementation benutzen (in |
| 35 |
|
|
der Praxis funktioniert das zwar nur mit einigen wenigen, aber die Idee |
| 36 |
|
|
zählt). Lieder verschlimmert dies das Problem nur: POE z.B. verlangt |
| 37 |
|
|
beinahe religiöse Glaubensbekenntnisse und ist kein bisschen kompatibler, |
| 38 |
|
|
im Gegenteil: obwohl POE Backends für Gtk+ und Event besitzt, kann man |
| 39 |
|
|
POE-Module nicht in einem Gtk+ oder Event-basiertem Programm benutzen: |
| 40 |
|
|
setzt man POE in seinem Modul ein, so muss auch jeder Benutzer des Moduls |
| 41 |
|
|
POE benutzen, sowie auch das Hauptprogramm selbst. |
| 42 |
|
|
|
| 43 |
|
|
AnyEvent dagegen ist I<grundsätzlich> anders: Im Stile von anderen |
| 44 |
|
|
C<Any*>-Modulen ist es lediglich eine Schnittstelle zu diesen. Dabei passt |
| 45 |
|
|
es sich an jedes unterstützte Event-Modell an, ohne daß sich die API |
| 46 |
|
|
ändert. Das bedeutet, daß man getrost Module benutzen kann, die AnyEvent |
| 47 |
|
|
benötigen und intern auch benutzen, aber der Benutzer des Moduls sieht |
| 48 |
|
|
davon erstmal nichts und kann jede beliebige Event-Bibliothek benutzen. |
| 49 |
|
|
|
| 50 |
|
|
AnyEvent ist 100% Pure-Perl, und kommt mit eigener, schneller, |
| 51 |
|
|
Event-Bibliothek, so daß auch auf Systemen, wo kein anderes Event-Modell |
| 52 |
|
|
verfügbar ist eventbasiert Programmiert werden kann. |
| 53 |
|
|
|
| 54 |
|
|
=head2 AnyEvent am Beispiel AnyEvent::CouchDB |
| 55 |
|
|
|
| 56 |
|
|
Ein gutes Beispiel dafür ist C<AnyEvent::CouchDB>. Außer im Namen, |
| 57 |
|
|
kommt AnyEvent in der API dieses Moduls nicht vor. Eine einfache |
| 58 |
|
|
Datenbankabfrage sieht dementsprechend auch ganz langweilig aus: |
| 59 |
|
|
|
| 60 |
|
|
my $data = $db->view('users/all', { key => 'b' })->recv |
| 61 |
|
|
|
| 62 |
|
|
Obwohl die Abfrage selbst Ereignisgesteuert ist, muss man als Benutzer |
| 63 |
|
|
des Moduls kaum darunter leiden, lediglich die Zweiteilung der Aufgabe in |
| 64 |
|
|
"Start-Phase" (C<view>-Methode) und "Resultat-Phase" (C<recv>-Methode) ist |
| 65 |
|
|
typisch. Daß intern AnyEvent werkelt, fällt nicht auf, außer, daß die |
| 66 |
|
|
Abfrage in einem laufenden Tk/Gtk usf.-Programm dieses nicht anhält. |
| 67 |
|
|
|
| 68 |
|
|
Die Vorteile erscheinen, wenn man mehrere Abfragen "gleichzeitig" |
| 69 |
|
|
(genauer: miteinander verflochten) ausführt: |
| 70 |
|
|
|
| 71 |
|
|
my @data = map $_->recv, |
| 72 |
|
|
map $db->view ('users/al', { key => $_ }), |
| 73 |
|
|
qw(key1 key2 key3 key4); |
| 74 |
|
|
|
| 75 |
|
|
Auch hierbei muss man nicht darauf achten, welches Event-Modul benutzt |
| 76 |
|
|
wird (oder ob man überhaupt ein spezielles benutzt) - es funktioniert |
| 77 |
|
|
einfach. |
| 78 |
|
|
|
| 79 |
|
|
Richtig Spaß macht es, wenn man realisiert, daß man praktisch alle |
| 80 |
|
|
AnyEvent-benutzenden Module beliebig kombinieren kann, und Aufgaben |
| 81 |
|
|
quasi "im Hintergrund" erledigen kann, z.B. mit C<AnyEvent::CouchDB> und |
| 82 |
|
|
C<AnyEvent::HTTP>: |
| 83 |
|
|
|
| 84 |
|
|
$db->view('users/all', { key => 'b' })->cb (sub { |
| 85 |
|
|
my $data = shift->revc; |
| 86 |
|
|
# daten erhalten |
| 87 |
|
|
}); |
| 88 |
|
|
|
| 89 |
|
|
AnyEvent::HTTP::http_get "http://www.porttracker.co.uk", sub { |
| 90 |
|
|
my $html = $_[1]; |
| 91 |
|
|
# daten erhalten |
| 92 |
|
|
}; |
| 93 |
|
|
|
| 94 |
|
|
# hier kann man weitere Dinge erledigen |
| 95 |
|
|
|
| 96 |
|
|
Und all das kann man in den eigenen Modulen nutzen, ohne sich irgendwie |
| 97 |
|
|
auf ein bestimmtes Event-Modell festzulegen. |
| 98 |
|
|
|
| 99 |
|
|
|
| 100 |
|
|
=head2 AnyEvent - Grundlegendes |
| 101 |
|
|
|
| 102 |
|
|
AnyEvent selbst wurde letztes Jahr an dieser Stelle ausführlich |
| 103 |
|
|
vorgestellt, daher möchte ich hier nur eine kurze Übersicht |
| 104 |
|
|
bzw. Einführung in AnyEvent als rein Event-Bibliothek geben: |
| 105 |
|
|
|
| 106 |
|
|
Wie viele anderen Bibliotheken auch, benutzt AnyEvent das Konzept eines |
| 107 |
|
|
"Watchers", einem Objekt, daß wie ein Wachhund auf ein bestimmtes |
| 108 |
|
|
Ereignis reagiert (watch/watcher = Wache/Beobachter) und bei eintreten |
| 109 |
|
|
eine Aktion auslöst, z.B. aufrufen eines Callbacks. |
| 110 |
|
|
|
| 111 |
|
|
Diese Watcher gibt es für die folgenden Ereignistypen: |
| 112 |
|
|
|
| 113 |
|
|
=over 4 |
| 114 |
|
|
|
| 115 |
|
|
=item Zeit - timer |
| 116 |
|
|
|
| 117 |
|
|
my $timer = AnyEvent->timer (after => 7, cb => sub { |
| 118 |
|
|
warn "Sieben Sekundne sind abgelaufen!"; |
| 119 |
|
|
}); |
| 120 |
|
|
|
| 121 |
|
|
Timer können nach einer gewissen Zeit ablaufen, oder wiederholt (z.B. |
| 122 |
|
|
alle 3s) Ereignisse auslösen und Callbacks aufrufen. |
| 123 |
|
|
|
| 124 |
|
|
=item I/O auf File Handles - io |
| 125 |
|
|
|
| 126 |
|
|
my $watcher = AnyEvent->io (fh => *STDIN, poll => 'r', cb => sub { |
| 127 |
|
|
my $line = <STDIN>; |
| 128 |
|
|
# zeile von stdin gelesen |
| 129 |
|
|
}); |
| 130 |
|
|
|
| 131 |
|
|
I/O-Watcher beobachten einen File Handle und können einen Callback |
| 132 |
|
|
aufrufen, wenn gelesen oder geschrieben werden kann. |
| 133 |
|
|
|
| 134 |
|
|
=item Signale - signal |
| 135 |
|
|
|
| 136 |
|
|
my $quit = AnyEvent->condvar; |
| 137 |
|
|
|
| 138 |
|
|
my $term = AnyEvent->signal (signal => "TERM", cb => sub { |
| 139 |
|
|
$quit->send; |
| 140 |
|
|
}); |
| 141 |
|
|
|
| 142 |
|
|
... |
| 143 |
|
|
|
| 144 |
|
|
$quit->recv |
| 145 |
|
|
|
| 146 |
|
|
Mit Signal-Watchers kann man synchron auf das Auftreten von Signalen |
| 147 |
|
|
warten. |
| 148 |
|
|
|
| 149 |
|
|
Das obige Beispiel benutzt eine "condition variable" um mit dem |
| 150 |
|
|
Hauptprogramm zu kommunizieren (mehr info s.u.). |
| 151 |
|
|
|
| 152 |
|
|
=item Kindprozesse sterben sehen - child |
| 153 |
|
|
|
| 154 |
|
|
my $w = AnyEvent->child (pid => $pid, cb => sub { |
| 155 |
|
|
warn "pid $pid hat sich beendet\n"; |
| 156 |
|
|
}); |
| 157 |
|
|
|
| 158 |
|
|
Eher selten benötigt, aber sehr schwer zu implementieren (wenn man es |
| 159 |
|
|
richtig tun möchte :) sind Child-Watcher: mit diesen kann man auf das |
| 160 |
|
|
Beenden von ge-fork-ten Prozessen warten. |
| 161 |
|
|
|
| 162 |
|
|
=back |
| 163 |
|
|
|
| 164 |
|
|
=head3 Wenn man mal nichts tun möchte |
| 165 |
|
|
|
| 166 |
|
|
Event-basierte APIs haben eines gemein: Im allgemeinen werden Vorgänge |
| 167 |
|
|
nur gestartet und laufen dann quasi-parallel im Hintergrund ab. |
| 168 |
|
|
|
| 169 |
|
|
Aber wenn man nun genügend solche Vorgänge gestartet hat, möchte man ja |
| 170 |
|
|
irgendwann auch mal auf die Ergebnisse warten. |
| 171 |
|
|
|
| 172 |
|
|
Dies erledigt entweder das Hauptprogramm, indem es die entsprechenden |
| 173 |
|
|
Funktionen der Event-Bibliothek aufruft (z.B. C<Event::loop> oder C<main |
| 174 |
|
|
Gtk>), oder man benutzt sogenannte "condition variables", mit denen man |
| 175 |
|
|
auf das eintreten eines bestimmten Zustands warten kann. |
| 176 |
|
|
|
| 177 |
|
|
"Condition Variables" sind so etwas wie "Rendezvouspunkte" zwischen dem |
| 178 |
|
|
Erzeuger von Daten oder einem Ereignis (z.B. C<AnyEvent::CouchDB->view> |
| 179 |
|
|
oder C<AnyEvent::HTTP::http_get>, aber auch genauso C<< AnyEvent->timer |
| 180 |
|
|
>>) und dem Konsumenten (der Benutzer dieser Funktionen, der die Daten |
| 181 |
|
|
haben oder einfach auf das Ereignis warten möchte). |
| 182 |
|
|
|
| 183 |
|
|
=head1 Neues! |
| 184 |
|
|
|
| 185 |
|
|
Auf zu neuen Ufern - im letzten Jahr hat sich viel getan, sowohl in |
| 186 |
|
|
AnyEvent selbst, als auch in Sachen "andere Module die AnyEvent benutzen". |
| 187 |
|
|
|
| 188 |
|
|
=head2 Altes! |
| 189 |
|
|
|
| 190 |
|
|
Die wichtigsten Eigenschaften von AnyEvent sind allerdings beim |
| 191 |
|
|
alten geblieben: |
| 192 |
|
|
|
| 193 |
|
|
=over 4 |
| 194 |
|
|
|
| 195 |
|
|
=item * Die API hat sich kaum verändert, bzw. ist 100% |
| 196 |
|
|
rückwärtskompatibel. |
| 197 |
|
|
|
| 198 |
|
|
=item * AnyEvent ist nach wie vor "Pure-Perl". |
| 199 |
|
|
|
| 200 |
|
|
=item * Der Event-Teil ist nach wie vor getrennt von den anderen Teilen, |
| 201 |
|
|
daß Event-Modul ist klein und wenn man bezahlt nur für daß, was man |
| 202 |
|
|
benutzt. |
| 203 |
|
|
|
| 204 |
|
|
=back |
| 205 |
|
|
|
| 206 |
|
|
Aber nun zu den Neuigkeiten: |
| 207 |
|
|
|
| 208 |
|
|
|
| 209 |
|
|
=head2 AnyEvent::Strict |
| 210 |
|
|
|
| 211 |
|
|
Aus Geschwindigkeitsgründen macht AnyEvent selbst keine |
| 212 |
|
|
Parameterprüfung, ganz einfach deshalb, weil die Prüfung meistens mehr |
| 213 |
|
|
Zeit in Anspruch nimmt als der Rest der Funktion. |
| 214 |
|
|
|
| 215 |
|
|
Für einen erfahrenen AnyEvent-Benutzer ist das in der Praxis kein |
| 216 |
|
|
Problem, aber wenn man die API noch nicht genau kennt, können sich so |
| 217 |
|
|
Fehler einschleichen (z.B. alte Event-Hasen benutzen gerne C<fd> statt |
| 218 |
|
|
C<fh> um einen I/O-Watcher zu erzeugen). |
| 219 |
|
|
|
| 220 |
|
|
Daher bietet AnyEvent optional eine strenge Prüfung aller |
| 221 |
|
|
Parameter an. Die kann man ganz einfach aktivieren, in dem man die |
| 222 |
|
|
Environment-Variable C<PERL_ANYEVENT_STRICT> auf C<1> setzt, was auf allen |
| 223 |
|
|
meinen Entwicklungsrechnern der Fall ist. |
| 224 |
|
|
|
| 225 |
|
|
Falls man diese Prüfung grundsätzlich wünscht (was ich ganz dringend |
| 226 |
|
|
I<nicht> empfehle, siehe aber den Punkt "keine Religion"...), kann man diese |
| 227 |
|
|
auch immer aktivieren: |
| 228 |
|
|
|
| 229 |
|
|
use AnyEvent; |
| 230 |
|
|
use AnyEvent::Strict; |
| 231 |
|
|
|
| 232 |
|
|
# jetzt wird brutal geprüft. |
| 233 |
|
|
|
| 234 |
|
|
Ich gebe zu, daß war sicher noch nicht der Brüller, aber sehen wir |
| 235 |
|
|
weiter: |
| 236 |
|
|
|
| 237 |
|
|
=head2 Anyevent::Util |
| 238 |
|
|
|
| 239 |
|
|
Dies ist meine Grabbelkiste für nützliche aber eklige Funktionen: |
| 240 |
|
|
|
| 241 |
|
|
=over 4 |
| 242 |
|
|
|
| 243 |
|
|
=item fork_call BLOCK $callback |
| 244 |
|
|
|
| 245 |
|
|
Führt den angegebenen BLOCK in einem separaten Prozess aus und ruft |
| 246 |
|
|
danach den $callback mit den Resultatwerten auf. Ist ideal, um lange |
| 247 |
|
|
Berechnungen in einen separaten Prozess zu verlagern: |
| 248 |
|
|
|
| 249 |
|
|
fork_call { |
| 250 |
|
|
find_primes_till 1e9 |
| 251 |
|
|
} sub { |
| 252 |
|
|
warn "alle primzahen bis 10**9: ", join " ", @_; |
| 253 |
|
|
}; |
| 254 |
|
|
|
| 255 |
|
|
Die Funktion leaked allerdings unter Windows, aber ich habe bisher noch |
| 256 |
|
|
keinen weg gefunden, unter einem native-win32-Perl zu forken und das Kind |
| 257 |
|
|
sauber zu beenden (ich glaube, es gibt keinen)... |
| 258 |
|
|
|
| 259 |
|
|
=item my $guard = guard BLOCK |
| 260 |
|
|
|
| 261 |
|
|
Führt den angegebenen BLOCK aus, wenn C<$guard> zerstört wird. Ist |
| 262 |
|
|
bei ereignisgesteuerter Programmierung total hilfreich, um Sachen |
| 263 |
|
|
aufzuräumen. |
| 264 |
|
|
|
| 265 |
|
|
Wenn es vorhanden ist, benutzt AnyEvent das C<Guard>-Modul (ja, das ist |
| 266 |
|
|
Schleichwerbung). |
| 267 |
|
|
|
| 268 |
|
|
=item fh_nonblocking $fh, $nonblocking |
| 269 |
|
|
|
| 270 |
|
|
Einen File Handle non-blocking zu machen, ist nicht ganz einfach (Windows, |
| 271 |
|
|
Windows, kaputt, kaputt...). C<AnyEvent::Util> erledigt dies mit |
| 272 |
|
|
furchtbaren Hacks unter Windows und elegant überall sonst. |
| 273 |
|
|
|
| 274 |
|
|
=back |
| 275 |
|
|
|
| 276 |
|
|
Immer noch nicht überzeugt? Ok, dann machen wir mal mit |
| 277 |
|
|
Netzwerkprogrammierung weiter: |
| 278 |
|
|
|
| 279 |
|
|
|
| 280 |
|
|
=head2 AnyEvent::Socket |
| 281 |
|
|
|
| 282 |
|
|
Sockets in Perl haben ein paar gravierende Nachteile: non-blocking |
| 283 |
|
|
connects sind extrem umständlich, IPv6 und IPv4 brauchen völlig |
| 284 |
|
|
inkompatible APIs, DNS blockiert den gesamtem Prozess und liefert darüber |
| 285 |
|
|
hinaus auch nur altmodische IP-Adressen. |
| 286 |
|
|
|
| 287 |
|
|
Anders mit AnyEvent::Socket: |
| 288 |
|
|
|
| 289 |
|
|
tcp_connect "www.google.com", "80", sub { |
| 290 |
|
|
my ($fh) = @_ |
| 291 |
|
|
or die "www.google.com: $!"; |
| 292 |
|
|
|
| 293 |
|
|
... |
| 294 |
|
|
}; |
| 295 |
|
|
|
| 296 |
|
|
Dieser Aufruf löst zuerst C<www.google.com> auf - im Hintergrund, ohne |
| 297 |
|
|
das Programm anzuhalten - und baut dann, ebenfalls im Hintergrund, eine |
| 298 |
|
|
TCP-Verbindung dorthin auf. |
| 299 |
|
|
|
| 300 |
|
|
Es versteht sich von selbst, daß Multi-Homed Hosts genauso unterstützt |
| 301 |
|
|
werden wie IPv6, und das ohne zusätzliche XS-Module. |
| 302 |
|
|
|
| 303 |
|
|
C<tcp_connect> kann aber noch mehr. Z.b. auf Jabber-server connecten, was |
| 304 |
|
|
mit Perl alleine fast unmöglich ist, da man mit Perl keine SRV-Records |
| 305 |
|
|
auflösen kann: |
| 306 |
|
|
|
| 307 |
|
|
tcp_connect "jabber.org", "xmpp-server=5269", sub { |
| 308 |
|
|
# Die pseudo-XML-Kacke parsed ihr bitte selbst... |
| 309 |
|
|
}, 60; # <- timeout |
| 310 |
|
|
|
| 311 |
|
|
Der Name C<tcp_connect> ist leider etwas naiv, tatsächlich werden auch |
| 312 |
|
|
andere Protokolle unterstützt, z.B. local unix sockets: |
| 313 |
|
|
|
| 314 |
|
|
tcp_connect "unix/", "/tmp/.X11-unix/X0", sub { ... |
| 315 |
|
|
|
| 316 |
|
|
Listening-Sockets funktionieren prinzipiell genauso einfach, z.B. hier |
| 317 |
|
|
für einen TCPv6-Port: |
| 318 |
|
|
|
| 319 |
|
|
tcp_server "::", 80, sub { |
| 320 |
|
|
my ($fh, $host, $port) = @_; |
| 321 |
|
|
|
| 322 |
|
|
# jede neue Verbindung führt zu einem Aufruf dieses Callbacks |
| 323 |
|
|
}; |
| 324 |
|
|
|
| 325 |
|
|
Oder einen beliebigen TCPv4-Port: |
| 326 |
|
|
|
| 327 |
|
|
tcp_server "0", undef, sub { |
| 328 |
|
|
my ($fh, $host, $port) = @_; |
| 329 |
|
|
... |
| 330 |
|
|
}, sub { |
| 331 |
|
|
my ($thishost, $thisport) = @_; |
| 332 |
|
|
|
| 333 |
|
|
warn "bound to port $thisport\n"; |
| 334 |
|
|
}; |
| 335 |
|
|
|
| 336 |
|
|
Leider muss man sich selbst darum kümmern, ob IPv6-Sockets auch auf IPv4 |
| 337 |
|
|
hören - üblicherweise ja, aber die BSDs haben natürlich mal wieder |
| 338 |
|
|
verbockt und halten sich nicht ans RFC. |
| 339 |
|
|
|
| 340 |
|
|
Komplizierteres ist immer möglich - anderes als z.B. C<IO::Socket> hat |
| 341 |
|
|
man volle Kontrolle über die eigentliche Socket und braucht nicht für |
| 342 |
|
|
jeden etwas spezielleren Wunsch extra Support. |
| 343 |
|
|
|
| 344 |
|
|
Und anders als bei anderen Lösungen funktionieren diese Funktionen auch |
| 345 |
|
|
unter Windows korrekt (modulo Tk, daß wie üblich zu kaputt ist, um |
| 346 |
|
|
non-blocking connects stabil zu kriegen, aber AnyEvent arbeitet auch um |
| 347 |
|
|
diese Bugs herum). |
| 348 |
|
|
|
| 349 |
|
|
Neben diesen beiden Funktionen gibt es in AnyEvent::Socket eine Unmenge an |
| 350 |
|
|
Utility-Funktionen zum parsen/anzeigen von IP-Adressen, Socket-Adressen |
| 351 |
|
|
und DNS: |
| 352 |
|
|
|
| 353 |
|
|
$ipn = parse_ipv4 $dotted_quad |
| 354 |
|
|
$ipn = parse_ipv6 $textual_ipv6_address |
| 355 |
|
|
$ipn = parse_address $text |
| 356 |
|
|
$text = AnyEvent::Socket::aton $ipn |
| 357 |
|
|
($host, $service) = parse_hostport $string[, $default_service] |
| 358 |
|
|
$sa_family = address_family $ipn |
| 359 |
|
|
$text = format_address $ipn |
| 360 |
|
|
$text = AnyEvent::Socket::ntoa $ipn |
| 361 |
|
|
inet_aton $name_or_address, $cb->(@addresses) |
| 362 |
|
|
$sa = AnyEvent::Socket::pack_sockaddr $service, $host |
| 363 |
|
|
($service, $host) = AnyEvent::Socket::unpack_sockaddr $sa |
| 364 |
|
|
resolve_sockaddr $node, $service, $proto, $family, $type, $cb->([$family, $type, $proto, $sockaddr], ...) |
| 365 |
|
|
|
| 366 |
|
|
Hier verweise ich auf die Dokumentation. Einzig C<parse_hostport> |
| 367 |
|
|
möchte ich genauer erklären: Selbst die Aufgabe, einen Hostnamen und |
| 368 |
|
|
einen Port anzugeben, ist nicht ganz einfach wenn man es richtig machen |
| 369 |
|
|
will. C<parse_hostport> nimmt ein oder zwei Strings und zerlegt diesen in |
| 370 |
|
|
Host und Port, z.B. diese: |
| 371 |
|
|
|
| 372 |
|
|
www.linux.org |
| 373 |
|
|
www.x.de:443 |
| 374 |
|
|
www.x.de:https=443 |
| 375 |
|
|
127.1:22 |
| 376 |
|
|
::1 |
| 377 |
|
|
affe::1 |
| 378 |
|
|
[10.0.1]:80 |
| 379 |
|
|
[www.x.org] 17 |
| 380 |
|
|
10.0.0.1 smtp |
| 381 |
|
|
|
| 382 |
|
|
Richtig, IPv6 macht das ganze deutlich komplexer. Und IPv6 ist die |
| 383 |
|
|
Zukunft, ob man das nun gut findet oder nicht :/ |
| 384 |
|
|
|
| 385 |
|
|
Ach ja, Stichwort IPv6 - es kann auch furchtbar nervig sein, wenn |
| 386 |
|
|
Programme grundsätzlich die IPv6-Adresse bevorzugen, die gerade nicht |
| 387 |
|
|
erreichbar ist. Der Standard bei AnyEvent ist zur Zeit IPv4 den |
| 388 |
|
|
Vorzug zu geben, was sich allerdings über die Environment-Variable |
| 389 |
|
|
C<PERL_ANYEVENT_PROTOCOLS> beeinflussen lässt. |
| 390 |
|
|
|
| 391 |
|
|
|
| 392 |
|
|
=head2 AnyEvent::DNS |
| 393 |
|
|
|
| 394 |
|
|
Nur am Rande möchte ich AnyEvent::DNS erwähnen. Ursprünglich war es nur |
| 395 |
|
|
dazu gedacht, die Adressen für C<AnyEvent::Socket> zu resolven. Das Modul |
| 396 |
|
|
ist aber ein kompletter Stub-Resolver, der beliebige DNS-Record-Typen, |
| 397 |
|
|
EDNS0, IPv6 und virtual circuit mode supported. |
| 398 |
|
|
|
| 399 |
|
|
Das Modul ist in der Praxis ca. halb so schnell wie z.b. EV::ADNS (braucht |
| 400 |
|
|
natürlich viel mehr Speicher, blockiert aber im Gegensatz zu EV::ADNS |
| 401 |
|
|
nie). |
| 402 |
|
|
|
| 403 |
|
|
Es gibt Hilfsunktionen (hier nur eine Auswahl): |
| 404 |
|
|
|
| 405 |
|
|
AnyEvent::DNS::a $domain, $cb->(@addrs) |
| 406 |
|
|
AnyEvent::DNS::mx $domain, $cb->(@hostnames) |
| 407 |
|
|
AnyEvent::DNS::srv $service, $proto, $domain, $cb->(@srv_rr) |
| 408 |
|
|
AnyEvent::DNS::ptr $domain, $cb->(@hostnames) |
| 409 |
|
|
AnyEvent::DNS::reverse_lookup $ipv4_or_6, $cb->(@hostnames) |
| 410 |
|
|
AnyEvent::DNS::reverse_verify $ipv4_or_6, $cb->(@hostnames) |
| 411 |
|
|
|
| 412 |
|
|
Und eine Resolver-Klasse: |
| 413 |
|
|
|
| 414 |
|
|
use Data::Dumper; |
| 415 |
|
|
use AnyEvent::DNS; |
| 416 |
|
|
AnyEvent::DNS::resolver->resolve ( |
| 417 |
|
|
"google.com", "*", my $cv = AnyEvent->condvar); |
| 418 |
|
|
warn Dumper [$cv->recv]; |
| 419 |
|
|
|
| 420 |
|
|
# gekürztes Ergebnis: |
| 421 |
|
|
# [ |
| 422 |
|
|
# [ 'google.com', 'soa', 'in', 'ns1.google.com', 'dns-admin.google.com', |
| 423 |
|
|
# 2008052701, 7200, 1800, 1209600, 300 ], |
| 424 |
|
|
# [ |
| 425 |
|
|
# 'google.com', 'txt', 'in', |
| 426 |
|
|
# 'v=spf1 include:_netblocks.google.com ~all' |
| 427 |
|
|
# ], |
| 428 |
|
|
# [ 'google.com', 'a', 'in', '64.233.187.99' ], |
| 429 |
|
|
# [ 'google.com', 'mx', 'in', 10, 'smtp2.google.com' ], |
| 430 |
|
|
# [ 'google.com', 'ns', 'in', 'ns2.google.com' ], |
| 431 |
|
|
# ] |
| 432 |
|
|
|
| 433 |
|
|
|
| 434 |
|
|
=head2 AnyEvent::Handle |
| 435 |
|
|
|
| 436 |
|
|
Nun zum ehrgeizigsten Teil der Neuerungen: C<AnyEvent::Handle>. |
| 437 |
|
|
|
| 438 |
|
|
Die Probleme, die dieses Modul lösen möchte, kommen in der Praxis |
| 439 |
|
|
häufig vor: Formatted-I/O, Pipelining und TLS-Verschlüsselung. |
| 440 |
|
|
|
| 441 |
|
|
Früher habe ich immer ad-hoc Lösungen für diese Probleme implementiert, |
| 442 |
|
|
da mir schlicht die Erfahrung gefehlt hat, wie man alle diese Features in |
| 443 |
|
|
ein allgemeines Perl-Modul "gießt". |
| 444 |
|
|
|
| 445 |
|
|
C<AnyEvent::Handle> versucht diese Lösung zu |
| 446 |
|
|
sein: AnyEvent::Handle-Objekte sind Wrapper um existierende File |
| 447 |
|
|
Handles. Im folgenden beschreibe ich die Lösung, die AnyEvent::Handle |
| 448 |
|
|
für die oben beschriebenen Probleme bietet: |
| 449 |
|
|
|
| 450 |
|
|
=over 4 |
| 451 |
|
|
|
| 452 |
|
|
=item Formatted I/O |
| 453 |
|
|
|
| 454 |
|
|
Um auf ein AnyEvent::Handle-Objekt zu schreiben, benutzt man |
| 455 |
|
|
C<push_write>. Das C<push> deutet darauf hin, daß es einen Puffer für |
| 456 |
|
|
die Daten gibt, und C<push_write> hängt die Daten einfach an diesen an: |
| 457 |
|
|
|
| 458 |
|
|
$hdl->push_write ("GET / HTTP/1.0\015\012\015\012"); |
| 459 |
|
|
|
| 460 |
|
|
Mit mehr als einem Argument fordert man zusätzliche Formatierungen an, |
| 461 |
|
|
die meisten dieser Formatierungen gibt es sowohl für die Schreib- als |
| 462 |
|
|
auch die Leseseite: |
| 463 |
|
|
|
| 464 |
|
|
$hdl->push_write (netstring => "xyz"); # djb netsrings |
| 465 |
|
|
$hdl->push_write (packstring => "w", "data"); # benutzt pack() |
| 466 |
|
|
$hdl->push_write (json => [1, 2, 3, 4]); # benutzt JSON |
| 467 |
|
|
$hdl->push_write (storable => $ref); # benutzt Storable |
| 468 |
|
|
|
| 469 |
|
|
Schreiben ist einfach, Lesen ist schwieriger. Anders als beim Schreiben |
| 470 |
|
|
enthält der Puffer keine Daten, sondern die Callbacks, die der Reihe nach |
| 471 |
|
|
aufgerufen werden, wenn Daten eintreffen. |
| 472 |
|
|
|
| 473 |
|
|
Die Flexibilität entsteht dadurch, daß man Callbacks ans Ende des Puffers anhängen kann: |
| 474 |
|
|
|
| 475 |
|
|
$hdl->push_read (json => sub { my $data = $_[1] }); |
| 476 |
|
|
|
| 477 |
|
|
Aber auch vor dem Anfang einschieben kann: |
| 478 |
|
|
|
| 479 |
|
|
$hdl->unshift_read (json => sub { my $data = $_[1] }); |
| 480 |
|
|
|
| 481 |
|
|
Interessant ist dies vor allem, weil man hierdurch recht einfach auch |
| 482 |
|
|
komplexe Protokolle mit Pipelining implementieren kann. |
| 483 |
|
|
|
| 484 |
|
|
Man kann jederzeit eigene Typen global für alle Handles hinzufügen, die |
| 485 |
|
|
existierenden Typen sind neben denselben wie für die Schreibrichtung: |
| 486 |
|
|
|
| 487 |
|
|
$hdl->push_read (line => $eol, $cb); # zeilen |
| 488 |
|
|
$hdl->push_Read (regex => $accept[, $reject[, $skip]); # uiuiuiui |
| 489 |
|
|
|
| 490 |
|
|
=item Pipelining |
| 491 |
|
|
|
| 492 |
|
|
Pipelining bedeutet, daß man auf einer bestehenden Verbindung mehrere |
| 493 |
|
|
Anfragen gleichzeitig losschickt. Die Herausforderung liegt darin, daß |
| 494 |
|
|
man beim Empfang der Antworten flexibel auf Ausnahmen reagieren können |
| 495 |
|
|
muss. |
| 496 |
|
|
|
| 497 |
|
|
Bei HTTP beispielsweise weiß man erst nach Parsen des Headers, wie lang |
| 498 |
|
|
der Body wird. |
| 499 |
|
|
|
| 500 |
|
|
AnyEvent::Handle erledigt dies mit einer Kombination aus C<push_read> und |
| 501 |
|
|
C<unshift_read>: |
| 502 |
|
|
|
| 503 |
|
|
# sende requests |
| 504 |
|
|
for (@urls) { |
| 505 |
|
|
$hdl->push_write ("GET $_ HTTP/1.0\015\012\015\012"); |
| 506 |
|
|
} |
| 507 |
|
|
|
| 508 |
|
|
# parse antworten |
| 509 |
|
|
for (@urls) { |
| 510 |
|
|
# lese header |
| 511 |
|
|
$hdl->push_read (line => "\015\012\015\012", sub { |
| 512 |
|
|
my ($hdl, $header) = @_; |
| 513 |
|
|
|
| 514 |
|
|
my $content_length = extract "Content-Length", $header; |
| 515 |
|
|
|
| 516 |
|
|
$hdl->unshift_read (chunk => $content_length, sub { |
| 517 |
|
|
my ($hdl, $body) = @_; |
| 518 |
|
|
|
| 519 |
|
|
# fertig |
| 520 |
|
|
}); |
| 521 |
|
|
}); |
| 522 |
|
|
} |
| 523 |
|
|
|
| 524 |
|
|
Der springende Punkt ist, daß C<unshift_read> erst I<nach> dem Empfang |
| 525 |
|
|
des Headers ausgeführt wird. Dadurch kann man z.B. verschiedene Arten von |
| 526 |
|
|
Antworten parsen, oder auf unerwartete Fehler reagieren, obwohl vielleicht |
| 527 |
|
|
schon Antworten auf die nächsten Requests empfangen wurden. |
| 528 |
|
|
|
| 529 |
|
|
=item TLS/SSL |
| 530 |
|
|
|
| 531 |
|
|
TLS ist der Horror. Nicht nur, aber vor allem auch wegen der grauenvollen |
| 532 |
|
|
OpenSSL-API (GnuTLS ist geringfügig besser, doch sucht man vergebens nach |
| 533 |
|
|
einem guten Perl-Modul dafür). Der Beweis dafür ist, daß es praktisch |
| 534 |
|
|
kein Modul gibt, daß non-blocking TLS connects korrekt und wirklich |
| 535 |
|
|
non-blocking durchführt. |
| 536 |
|
|
|
| 537 |
|
|
Es hilft auch nicht gerade, daß Net::SSLeay nur einen Bruchteil der |
| 538 |
|
|
OpenSSL-API umsetzt. |
| 539 |
|
|
|
| 540 |
|
|
AnyEvent::Handle (to boldly go...) versucht einen ganz neuen Ansatz für |
| 541 |
|
|
diese Problematik, bei der OpenSSL vom eigentlichen File Handle komplett |
| 542 |
|
|
getrennt wird, d.h. gar keinen Zugriff auf diesen hat. Dadurch wird |
| 543 |
|
|
garantiert, daß OpenSSL auf gar keinen Fall blockieren kann. |
| 544 |
|
|
|
| 545 |
|
|
=back |
| 546 |
|
|
|
| 547 |
|
|
Mit AnyEvent::Handle wird so manches einfach. Hier ist z.B. ein (ganz |
| 548 |
|
|
primitiver) HTTPS-Client (übrigens das komplette Programm): |
| 549 |
|
|
|
| 550 |
|
|
use AnyEvent; |
| 551 |
|
|
use AnyEvent::Socket; |
| 552 |
|
|
use AnyEvent::Handle; |
| 553 |
|
|
|
| 554 |
|
|
my $cv = AnyEvent->condvar; |
| 555 |
|
|
|
| 556 |
|
|
tcp_connect "www.google.com", "443", sub { |
| 557 |
|
|
my ($fh) = @_ or die; |
| 558 |
|
|
|
| 559 |
|
|
my $hdl; $hdl = new AnyEvent::Handle |
| 560 |
|
|
fh => $fh, |
| 561 |
|
|
timeout => 60, |
| 562 |
|
|
tls => "connect", |
| 563 |
|
|
on_eof => sub { |
| 564 |
|
|
undef $hdl; # gibt handle frei |
| 565 |
|
|
$cv->send; # isch hab' ferdisch |
| 566 |
|
|
}, |
| 567 |
|
|
on_read => sub { |
| 568 |
|
|
my ($hdl) = @_; |
| 569 |
|
|
print $hdl->{rbuf}; |
| 570 |
|
|
$hdl->{rbuf} = ""; |
| 571 |
|
|
}; |
| 572 |
|
|
|
| 573 |
|
|
$hdl->push_write ("GET / HTTP/1.0\015\012\015\012"); |
| 574 |
|
|
}; |
| 575 |
|
|
|
| 576 |
|
|
$cv->recv; |
| 577 |
|
|
|
| 578 |
|
|
=back |
| 579 |
|
|
|
| 580 |
|
|
|
| 581 |
|
|
=head2 Benchmarks |
| 582 |
|
|
|
| 583 |
|
|
=head2 Ausblick auf CPAN |