2. September 2005

Protokoll bluehands Seminar 02.09.2005

Thema: select und asynchrone Socket Kommunikation

Normalfall bei einfachen Netzwerkanwendungen sind Systemcalls in der Reihenfolge: socket, connect, write, read. Wenn man auf mehrern Verbindungen gleichzeitig komunizieren will, dann kann man dafuer 2 Threads machen, die jeweils connect, write, read machen. Das gilt fuer Server, die normalerweise viele Verbindungen haben (read/write vertauscht) oder Clients mit Verbindungen an mehrere Server. Aktuelles Beispiel: der Jabber Bot, der mehrere LLuna User simuliert.

Es gibt aber Faelle, wo Threads nicht angebracht sind. Dazu gehoeren Server mit sehr vielen Clients (bei 100 Threads hoert der Spass auf) oder Frameworks, die kene Threads haben PHP). Manche wollen auch auf Threads verzichten, weil Threads eine Synchronisationsproblematik erzeugen, die nicht alle Programmierer vollstaendig ueberblicken (kein Witz).

Es stellt sich die Frage: wie macht man "read" auf 2 sockets gleichzeitig obwohl beide blockieren. Antwort: select. Es gibt versciedene Arten von Select auf verschiedenen Betriebssystemen und Frameworks, select auf Posix, WSAsyncSelect auf Windows, socket_select in PHP, etc.

Allen gemeinsam ist, dass man eine Liste von Sockets angibt, an denen man interessiert ist und einen Timeout. Select wartet solange, bis auf mindestens einem Socket etwas passiert (z.B. Daten ankommen) oder bis der Timeout ablaeuft.

Die Anwendung von select ist einfach. Angenommen man macht eine Anwendung, die nur regelmaessig Sockets schreibt und dann immer sleep() macht:

while (1) {
write(data)
sleep(2 sec)
}

Jetzt ersetzen wir sleep() durch select(). Der Timeout bleibt und dazu kommen die Sockets, fuer die wir uns interessieren beim read():

while (1) {
write(data)
select(socketlist, 2 sec)
}

Select sagt wie viele Sockets Daten fuer uns haben. Dann koennen wir auf den angezeigten Sockets read() machen (ohne ausseres while):

n = select(socketlist, 2 sec)
if (n > 0) {
foreach (socketlist as socket) {
read(socket);
}
}

Select kommt dann zurueck, wenn der Timeout ablaeuft oder wenn mindestens ein Socket signalisiert. Es kann also sein, dass select() nicht so lange wartet, wie angegeben. In diesem Fall muss man einfach nochmal select() aufrufen und die Restzeit als Timeout angeben. Dazu misst man die Zeit, die vergangen ist im select() und den nachfolgenden read(). Das Ergebnis sieht so aus:

timeout = 2;
while (timeout > 0) {
starttime = currenttime()
n = select(socketlist, timeout)
if (n > 0) {
foreach (socketlist as socket) {
read(socket);
}
}
endtime = currenttime()
timeout -= enddtime - starttime
}

Es fehlt noch einiges fuer eine reale Anwendung. Die Zeit muss in Microsekunden verarbeitet werden. Also z.B. gettimeofday() als Zeitmessung. Fehlerbehandlung natuerlich auch.

Wenn wir schon dabei sind: man sollte auch write() nicht blockierend machen. Wann man schreiben darf, sagt auch das select(). Dazu hat das select() in Wirklichkeit nicht nur eine Socketliste, sondern mehrere. Der innere Teil sieht dann etwa so aus:

n = select(readlist, writelist, exceptionlist, timeout)
foreach (readlist as socket) {
read(socket);
}
foreach (writelist as socket) {
write(socket);
}

Das bedeutet natuerlich: alles was man write()n will, in eine Queue haengen, und select sagt dann, wann man schreiben darf.

Weitere Themen waren Netzwerkkommunikation in Verbindung mit User I/O, Windows Events, Windows: WaitForMultipleObjects(), MsgWaitForMultipleObjects(), Posix: errno

_happy_coding()

Kommentar veröffentlichen