Your Browser is not longer supported

Please use Google Chrome, Mozilla Firefox or Microsoft Edge to view the page correctly
Loading...

{{viewport.spaceProperty.prod}}

Gleichzeitige Verwaltung mehrerer Verbindungen und ereignisgesteuerter Betrieb

&pagelevel(3)&pagelevel

Anhand eines Beispiels werden im Folgenden zwei wichtige Konzepte erläutert:

  • Gleichzeitige Verwaltung mehrerer Verbindungen durch den Server:

    Die im Abschnitt "Grundlagen von XTI(POSIX)" vorgestellte Server-Anwendung kann immer nur eine Verbindungsanforderung nach der anderen bearbeiten. Die Transportschnittstelle erlaubt jedoch auch die gleichzeitige Bearbeitung mehrerer Verbindungen. Dies ist z.B in folgenden Fällen sinnvoll:

    • Der Server möchte jedem Client eine Priorität zuordnen.

    • Mehrere Clients möchten eine Verbindung zu einem Server aufbauen, der gerade eine Verbindungsanforderung bearbeitet. Wenn der Server immer nur eine Verbindungsanforderung gleichzeitig bearbeiten kann, finden die Clients den Server besetzt vor. Kann der Server jedoch mehrere Verbindungsanforderungen gleichzeitig bearbeiten, finden die Clients den Server nur dann besetzt vor, wenn bereits die maximal möglichen Client-Anforderungen vom Server bearbeitet werden.

  • Programmierung eines ereignisgesteuerten Betriebs:

    Der Programmierer kann mit Hilfe der Transportschnittstelle ereignisgesteuerte Programme schreiben. Beim ereignisgesteuerten Server fragt der Prozess einen Transportendpunkt permanent ab, ob von der Transportschnittstelle Ereignisse gemeldet werden. Je nach gemeldetem Ereignis ruft der Server dann die entsprechende Schnittstellenfunktion auf.

Das nachfolgende Programmbeispiel verwendet für die lokale Verwaltung die gleichen Definitionen und Funktionsaufrufe wie das Server-Beispiel im Abschnitt "Verbindungsorientierter Dienst". Der im Beispiel verwendete Programmcode ist vollständig und zusammenhängend dargestellt im Abschnitt "Ereignisgesteuerter Server".

#include <xti.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <stropts.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define FILENAME "/etc/services"
#define NUM_FDS 1
#define MAX_CONN_IND 4
#define SRV_ADDR 0x7F000001
#define SRV_PORT 8888
int conn_fd;/* Transportendpunkt des Servers */
/* Zum Speichern der Verbindungen */
struct t_call *calls[NUM_FDS][MAX_CONN_IND];
main()
{
   struct pollfd pollfds[NUM_FDS];
   struct t_bind *bind;
   struct sockaddr_in *sin;
   int i;
   /*
    * Öffnen eines Transportendpunkts und Binden der 
    * Adresse. Es werden aber mehrere Endpunkte unterstützt.
    */
   if ((pollfds[0].fd = t_open("/dev/tcp", O_RDWR, NULL)) < 0) {
   t_error("t_open() gescheitert");
   exit(1);
   }
   if ((bind = (struct t_bind *)t_alloc(pollfds[0].fd, 
                                             T_BIND, T_ALL)) == NULL) {
      t_error("t_alloc() von t_bind-Struktur gescheitert");
      exit(2);
   }
   bind->qlen = MAX_CONN_IND;
   bind->addr.len=sizeof(struct sockaddr_in);
   sin=(struct sockaddr_in *)bind->addr.buf;
   sin->sin_family=AF_INET;
   sin->sin_port=htons(SRV_PORT);
   sin->sin_addr.s_addr=htonl(SRV_ADDR);
   if (t_bind(pollfds[0].fd, bind, bind) < 0) {
      t_error("t_bind() gescheitert");
      exit(3);
   }

Das von t_open() gelieferte Dateikennzeichen wird im ersten Element des struct pollfd -Vektors pollfds gespeichert . Der Vektor pollfds wird später bei einem Aufruf der POSIX-Funktion poll() verwendet, um ankommende Ereignisse zu bearbeiten. poll() ist eine allgemeine C-Bibliotheksfunktion, die im Abschnitt " poll() - Ein-/Ausgabe multiplexen" beschrieben ist. Zu beachten ist, dass in diesem Beispiel nur ein Transportendpunkt eingerichtet wird. Da der restliche Teil des Beispiels jedoch mehrere Verbindungen vorsieht, sind nur geringfügige Änderungen nötig, um mit diesem Programm mehrere Kommunikationsverbindungen verwalten zu können.

Wesentlich für das vorliegende Beispiel ist, dass der Server bind->qlen auf einen Wert >1 setzt und beim Aufruf von t_bind() entsprechend übergibt. Dies ermöglicht dem Server, an einem Transportendpunkt mehrere Verbindungsanforderungen zu empfangen. Bei den Beispielen im Abschnitt "Grundlagen von XTI(POSIX)" wird vom Server gleichzeitig immer nur eine Verbindung angenommen und bearbeitet. Im vorliegenden Beispiel kann der Server dagegen bis zu MAX_CONN_IND Anforderungen gleichzeitig annehmen. Allerdings kann der Transportanbieter den Wert von bind->qlen herabsetzen, wenn er die vom Server gewünschte Anzahl Verbindungen nicht bearbeiten kann.

Nachdem der Server seine Adresse bekannt gegeben hat, verfährt er wie folgt:

   pollfds[0].events = POLLIN;
   for(;;) {
      if (poll(pollfds, NUM_FDS, -1) < 0) {
         perror("poll() gescheitert");
         exit(5);
      }
      for (i = 0; i < NUM_FDS; i++) {
         switch (pollfds[i].revents) {
         default:
                 perror("poll gibt Fehlermeldung zurück");
                 exit(6);
                 break;
         case 0:
                 continue;
         case POLLIN:
                 do_event(i, pollfds[i].fd);
                 service_conn_ind(i, pollfds[i].fd);
         }
      }
   }
} 

Die Komponente events des ersten Elements des pollfd-Vektors pollfds setzt der Server auf POLLIN, damit er über alle an der Transportschnittstelle ankommenden Ereignisse informiert wird. Der Server geht dann in eine Endlosschleife. In der Endlosschleife wartet der Server mit poll() auf Ereignisse an den Transportendpunkten und bearbeitet diese Ereignisse dann entsprechend.

Der Aufruf poll() blockiert den Prozess, bis ein Ereignis eintrifft. Nach dem Ende des Aufrufs prüft der Server bei jedem Eintrag (entsprechend einem Transportendpunkt), ob dort ein Ereignis aufgetreten ist. Wenn revents auf 0 gesetzt ist, gab es an diesem Transportendpunkt kein Ereignis. Hat revents den Wert POLLIN, so ist an diesem Punkt ein Ereignis aufgetreten. In diesem Fall ruft der Server do_event() auf, um das Ereignis zu bearbeiten. Hat revents einen anderen Wert als POLLIN, so ist ein Fehler an diesem Punkt aufgetreten und das Programm wird beendet.

In jedem Schleifendurchlauf, bei dem ein Ereignis gefunden wird, ruft der Server service_conn_ind() auf, um mögliche anstehende Anforderungen zu bearbeiten. Falls beim Bearbeiten einer Anforderung eine weitere Anforderung ansteht, wird service_conn_ind sofort verlassen; die aktuelle Anforderung wird dabei gespeichert, um sie später mit der Funktion do_event() zu bearbeiten.

Die Funktion do_event() wird bei einem ankommenden Ereignis aufgerufen, um das Ereignis zu bearbeiten. Die Funktion do_event() ist wie folgt definiert:

do_event(slot, fd)
{
   struct t_discon *discon;
   int i;
   switch (t_look(fd)) {
   default:
      fprintf(stderr,"t_look: unerwartetes Ereignis \n");
      exit(7);
      break;
   case -1:
      t_error("t_look() gescheitert");
      exit(9);
      break;
   case 0:
      /* wenn POLLIN gemeldet wird, sollte dies nie geschehen */
      fprintf(stderr,"t_look() meldet kein Ereignis\n");
      exit(10);
   case T_LISTEN:
      /*
       * Suchen eines freien Elements im calls-Feld
       */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (calls[slot][i] == NULL)
                 break;
      }
      if ((calls[slot][i] = (struct t_call *)t_alloc(fd,
                                         T_CALL, T_ALL)) == NULL) {
         t_error("t_alloc() von t_call-Struktur gescheitert");
         exit(11);
      }
      if (t_listen(fd, calls[slot][i]) < 0) {
         t_error("t_listen() gescheitert");
         exit(12);
      }
      break;
   case T_DISCONNECT:
      discon = (struct t_discon *)t_alloc(fd, T_DIS, T_ALL);
      if (t_rcvdis(fd, discon) < 0) {
         t_error("t_rcvdis() gescheitert");
         exit(13);
      }
      /*
       * Im calls-Feld Anforderung finden und löschen
       */
      for (i = 0; i < MAX_CONN_IND; i++) {
         if (discon->sequence == calls[slot][i]->sequence) {
            t_free(calls[slot][i], T_CALL);
            calls[slot][i] = NULL;
         }
      }
      t_free(discon, T_DIS);
      break;
   }
}

Die Funktion do_event() hat zwei Parameter: eine Nummer slot und ein Dateikennzeichen fd. slot indiziert die Vektoren (Untermatrixen) der globalen Matrix calls, deren Elemente Zeiger auf t_call-Objekte sind. Jeder abzufragende Transportendpunkt ist durch einen Vektor der Matrix calls repräsentiert. Der Wert von slot gibt somit den zu bearbeitenden Transportendpunkt an. Die Vektorelemente zeigen auf die t_call-Objekte, in denen die ankommenden Anforderungen des zugehörigen Transportendpunkts abgespeichert werden.

Der Aufruf t_look() ermittelt das Ereignis, das an dem durch fd identifizierten Transportendpunkt aufgetreten ist. Falls ein Verbindungswunsch (T_LISTEN) oder ein Abbruchwunsch (T_DISCONNECT) angekommen ist, wird er entsprechend bearbeitet. Bei anderen Ereignissen wird eine entsprechende Fehlermeldung ausgegeben und das Programm beendet.

Bei einer Verbindungsanforderung sucht do_event() einen freien Eintrag im Feld calls. Für diesen Eintrag wird nun ein t_call-Objekt angefordert. Mit t_listen() wird die Anforderung empfangen. In dem Feld muss immer mindestens ein freier Eintrag sein, da t_bind() beim Anlegen des Feldes den maximalen Wert von gleichzeitig bearbeitbaren Anforderungen angegeben hat. Die Bearbeitung der Anforderung erfolgt später.

Ein ankommender Abbruchwunsch muss zu einer früher angekommenen Verbindungsanforderung gehören. Dieser Fall tritt ein, falls ein Client eine Verbindungsanforderung schickt und diese sofort wieder abbricht. do_event() legt eine t_discon-Struktur an, um die Informationen für den Abbruch zu erhalten.

Die Struktur t_discon ist in <xti.h> wie folgt deklariert:

struct t_discon {
   struct netbuf udata;
   int reason;
   int sequence;
};

reason gibt die protokollspezifische Ursache für den Verbindungsabbau an. sequence gibt die Nummer der Verbindungsanforderung an, die abgebrochen werden soll.

Die Funktion t_rcvdis() wird aufgerufen, um die genannten Informationen zu erhalten. *calls des Programms, in dem die Anforderungen verwaltet werden, wird dann nach der Anforderung abgesucht, die in der Komponente sequence angegeben ist. Wenn die Anforderung gefunden ist, wird der Speicher freigegeben und der Eintrag auf NULL gesetzt.

Falls irgendein Ereignis am Transportendpunkt aufgetreten ist, wird die Funktion service_conn_ind() aufgerufen, um alle an diesem Endpunkt anstehenden Anforderungen wie folgt zu bearbeiten:

service_conn_ind(slot, fd)
{
   int i;
   for (i = 0; i < MAX_CONN_IND; i++) {
      if (calls[slot][i] == NULL)
         continue;
      if ((conn_fd = t_open("/dev/tcp", O_RDWR, NULL)) < 0) {
         t_error("t_open() gescheitert");
         exit(14);
      }
      if (t_accept(fd, conn_fd, calls[slot][i]) < 0) {
         if (t_errno == TLOOK) {
            t_close(conn_fd);
            return;
         }
         t_error("t_accept() gescheitert");
         exit(16);
      }
      t_free(calls[slot][i], T_CALL);
      calls[slot][i] = NULL;
      run_service(fd);
   }
}

Für den angegebenen Endpunkt (slot) wird im Feld nach Anforderungen gesucht. Für jede Anforderung öffnet der Server einen Transportendpunkt und nimmt die Anforderung an. Sollte inzwischen ein anderes Ereignis (Verbindungsanforderung oder Verbindungsabbruch) eingetroffen sein, so scheitert der t_accept()-Aufruf, und t_errno wird auf TLOOK gesetzt.

Ein Benutzer kann keine Anforderung annehmen, falls andere Verbindungsanforderungen oder Abbruchanforderungen an diesem Transportendpunkt anstehen.

Wenn dieser Fehler auftritt, wird conn_fd sofort geschlossen und die Funktion verlassen. Die Anforderung bleibt im Feld erhalten und kann daher zu einem späteren Zeitpunkt bearbeitet werden. Damit befindet sich der Server-Prozess wieder in der Hauptschleife, und das Ereignis kann mit dem nächsten Aufruf von poll() behandelt werden. Auf diese Weise lassen sich mehrere Aufträge gleichzeitig bearbeiten.

Wenn alle Ereignisse bearbeitet sind, kann die Funktion service_conn_ind() die Verbindungen herstellen und die Funktion run_service() für die Datenübertragung aufrufen. Die Funktion run_service() ist beschrieben in Abschnitt "Verbindungsorientiertes Client/Server-Modell".