Middleware

Dozent:

Prof. Dr. Michael Eichberg

Kontakt:

michael.eichberg@dhbw-mannheim.de, Raum 149B

Version:

2024-05-09

Folien:

https://delors.github.io/ds-middleware/folien.rst.html

https://delors.github.io/ds-middleware/folien.rst.html.pdf

Fehler auf Folien melden:

https://github.com/Delors/delors.github.io/issues

Einführung in Middleware

Was ist Middleware?

Ein einfacher Server mit Sockets

/* A simple TCP based server. The port number is passed as an argument */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

void error(char *msg){perror(msg); exit(1);}

int main(int argc, char *argv[]){
  int sockfd, newsockfd, portno, clilen;
  char buffer[256]; int n;
  struct sockaddr_in serv_addr, cli_addr;

  sockfd = socket(AF_INET, SOCK_STREAM, 0); // socket() returns a socket descriptor
  if (sockfd < 0)
  error("ERROR opening socket");

  bzero((char *) &serv_addr, sizeof(serv_addr)); // bzero() sets all values to zero.
  portno = atoi(argv[1]); // atoi() converts str into an integer

  ...
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(portno);

  if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
  error("ERROR on binding");
  listen(sockfd,5); // tells the socket to listen for connections
  clilen = sizeof(cli_addr);
  newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
  if (newsockfd < 0) error("ERROR on accept");

  bzero(buffer,256);
  n = read(newsockfd,buffer,255);
  if (n < 0) error("ERROR reading from socket");
  printf("Here is the message: %s\n",buffer);
  n = write(newsockfd,"I got your message",18);

  if (n < 0) error("ERROR writing to socket");

  return 0;
}

Ein einfacher Client mit Sockets

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void error(char *msg){ perror(msg);exit(0);}

int main(int argc, char *argv[]){
  int sockfd, portno, n;
  struct sockaddr_in serv_addr;
  struct hostent *server;
  char buffer[256];

  portno = atoi(argv[2]);

  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  if (sockfd < 0)
    error("ERROR opening socket");

  ...
  ...

  server = gethostbyname(argv[1]);
  bzero((char *) &serv_addr, sizeof(serv_addr));
  serv_addr.sin_family = AF_INET;
  bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
  serv_addr.sin_port = htons(portno);

  if (connect(sockfd,&serv_addr,sizeof(serv_addr)) < 0) error("ERROR connecting");

  printf("Please enter the message: ");
  bzero(buffer,256);
  fgets(buffer,255,stdin);
  n = write(sockfd,buffer,strlen(buffer));
  if (n < 0) error("ERROR writing to socket");
  bzero(buffer,256);
  n = read(sockfd,buffer,255);
  printf("%s\n",buffer);

  return 0;
}

Probleme bei der Verwendung von Sockets

Wir müssen uns kümmern um …

Middleware als Programmierabstraktion

  • Eine Softwareschicht oberhalb des Betriebssystems und unterhalb des Anwendungsprogramms, die eine gemeinsame Programmierabstraktion in einem verteilten System bietet.

  • Ein Baustein auf höherer Ebene als die vom Betriebssystem bereitgestellten APIs (z. B. Sockets)

images/middleware.svg

Middleware als Programmierabstraktion

Die von Middleware angebotenen Programmierabstraktionen verbergen einen Teil der Heterogenität und bewältigen einen Teil der Komplexität, mit der Programmierer einer verteilten Anwendung umgehen müssen:

Alte Middlewarestandards – wie zum Beispiel CORBA – waren sehr komplex und die Implementierungen verschiedener Hersteller meist nicht vollständig kompatibel.

Transparenzziele von Middleware aus Sicht der Programmierung

Middleware bietet (beim Programmieren) Transparenz in Bezug auf eine oder mehrere der folgenden Dimensionen:

Middleware ist die Software, die ein verteiltes System (DS) programmierbar macht.

Middleware als Infrastruktur

Seit Jahrzehnten kann beobachtet werden, dass Middleware immer komplexer wird bzw. wurde bis zu dem Punkt an dem die Komplexität kaum mehr beherrschbar war. Zu diesen Zeitpunkten wurden dann häufig neue Ansätze entwickelt, die die Komplexität reduzierten bis diese wiederum Eingang in komplexere Middleware-Produkten Eingang fand.

Ansätze, wie z. B. REST, haben sich als recht erfolgreich erwiesen stellen aber Entwickler vor neue Herausforderungen.

Middleware und nicht-funktionale Anforderungen

Die Infrastruktur kümmert sich um nicht-funktionale Eigenschaften, die normalerweise von Datenmodellen, Programmiermodellen und Programmiersprachen ignoriert werden:

Middleware als Infrastruktur

Middleware unterstützt zusätzliche Funktionen die die Entwicklung, Wartung und Überwachung einfacher und kostengünstiger machen (Auszug):

Konzeptionelle Darstellung (historischer) Middleware

images/historische-middleware-konzeptuell.svg

Insbesondere die explizite Erzeugung von Stubs und Skeletons durch einen IDL Compiler erfolgt so in der heutigen Zeit nicht mehr. Die Erzeugung von Stubs und Skeletons - wenn überhaupt erforderlich - erfolgt heute automatisch durch die Middleware.

Historische Entwicklung von Middleware

images/historic_middleware_technologies.svg

Entwicklung von Middleware

Middleware - High-level View

Eine Middleware stellt eine umfassende Plattform für die Entwicklung und den Betrieb komplexer verteilter Systeme zur Verfügung.

Middleware-Technologien

Remote Procedure Calls (RPCs)

Remote Procedure Call (RPC)

Schwerpunkt: verstecken der Netzkommunikation.

Ein Prozess kann eine Prozedur aufrufen deren Implementierung sich auf einem entfernten Rechner befindet:

RPCs konzeptionell (synchrone Kommunikation)

  • Ein Server ist ein Programm, das bestimmte Dienste implementiert.

  • Cients möchten diese Dienste in Anspruch nehmen:

    • Die Kommunikation erfolgt durch das Senden von Nachrichten (kein gemeinsamer Speicher, keine gemeinsamen Festplatten usw.)

    • Einige minimale Garantien müssen gegeben werden (Behandlung von Fehlern, Aufrufsemantik, usw.)

images/rpc_konzeptionell.svg

RPCs - zentrale Fragestellungen und Herausforderungen

Sollen entfernte Aufrufe transparent oder nicht transparent für den Entwickler sein?

Ein entfernter Aufruf ist etwas völlig anderes als ein lokaler Aufruf; sollte sich der Programmierer dessen bewusst sein?

Wie können Daten zwischen Maschinen ausgetauscht werden, die möglicherweise unterschiedliche Darstellungen für verschiedene Datentypen verwenden?

Komplexe Datentypen müssen linearisiert werden:

Marshalling:

der Prozess des Aufbereitens der Daten in eine für die Übermittlung in einer Nachricht geeignete Form.

Unmarshalling:

der Prozess der Wiederherstellung der Daten bei ihrer Ankunft am Zielort, um eine originalgetreue Repräsentation zu erhalten.

Wie findet und bindet man den Dienst, den man tatsächlich will, in einer potenziell großen Sammlung von Diensten und Servern?

Das Ziel ist, dass der Kunde nicht unbedingt wissen muss, wo sich der Server befindet oder sogar welcher Server den Dienst anbietet (Standorttransparenz).

Wie geht man mehr oder weniger elegant mit Fehlern um:

  • Server ist ausgefallen

  • Kommunikation ist gestört

  • Server beschäftigt

  • doppelte Anfragen ...

Je nach System ist die Reihenfolge der Bytes unterschiedlich:

High-level View auf RPC

Für Programmierer sieht ein entfernter Prozeduraufruf fast identisch aus wie ein lokaler Prozeduraufruf und funktioniert auch so - auf diese Weise wird Transparenz erreicht.

Um Transparenz zu erreichen, führte RPC viele Konzepte von Middleware-Systemen ein:

RPC - Call Semantics

Nehmen wir an, ein Client stellt eine RPC-Anfrage an einen Dienst eines bestimmten Servers. Nachdem die Zeitüberschreitung abgelaufen ist, beschließt der Client die Anfrage erneut zu senden. Das finale Verhalten hängt von der Semantik des Aufrufs (Call Semantics) ab:

Maybe (vielleicht; keine Garantie)

Die Zielmethode kann ausgeführt worden sein und die Antwortnachricht(en) ging(en) verloren oder die Methode wurde gar nicht erst ausgeführt da die Anfrage verloren ging.

XMLHTTPRequests in Webbrowsern verwenden diese Semantik.

At least once (mindestens einmal)

Die Prozedur wird ausgeführt werden solange der Server nicht endgültig versagt.

Es ist jedoch möglich, dass sie mehr als einmal ausgeführt wird wenn der Client die Anfrage nach einer Zeitüberschreitung erneut gesendet hatte.

At most once (höchstens einmal)

Die Prozedur wird entweder einmal oder gar nicht ausgeführt. Ein erneutes Senden der Anfrage führt nicht dazu, dass die Prozedur mehrmals ausgeführt wird.

Exactly once (genau einmal)

Das System garantiert die gleiche Semantik wie bei lokalen Aufrufen unter der Annahme, dass ein abgestürzter Server irgendwann wieder startet.

Verwaiste Aufrufe, d. h. Aufrufe auf abgestürzten Server-Rechnern, werden nachgehalten, damit sie später von einem neuen Server übernommen werden können.

Asynchrones RPC

Die Verbindung zwischen Client und Server in einem traditionellen RPC. Der Client wird blockiert und wartet.

images/rpcs/synchronous_rpc.svg

Die Verbindung zwischen Client und Server bei einem asynchronen RPC. Der Client wird nicht blockiert.

images/rpcs/asynchronous_rpc.svg

Ein normaler Aufruf mittels XMLHTTPRequest (JavaScript) ist auch immer asynchron.

RPC - Bewertung

Wenn man moderne Ansätze wie RESTful WebServices mit RPC vergleicht, dann fällt auf, dass RPC eine deutlich bessere Tranzparenz bietet.

Das Network File System (NFS) und SMB sind bekannte RPC-basierte Anwendungen.

Java Remote Method Invocation (RMI)

Java RMI (Remote Method Invocation)

Ermöglicht es einem Objekt, das in einer Java Virtual Machine (VM) läuft, Methoden eines Objekts aufzurufen, das in einer anderen Java VM läuft.

Java RMI vs. RPC

images/rpc_vs_rmi.svg

Java RMI ist eine spezielle Form von RPC, die in Java implementiert wurde. Der Unterschied ergibt sich im Prinzip aus dem Unterschied zwischen einem Prozeduraufruf und einem Methodenaufruf auf ein Objekt

Java RMI implementiert ein Distributed Object Model

images/java_rmi-distributed-object-model.svg

Anatomie eine Java RMI Aufrufs

images/rmi_anatomy/rmi_anatomy.svg

Der Proxy versteckt für den Client, dass es sich um einen entfernten Aufrufe handelt. Er implementiert die Remote-Schnittstelle und kümmert sich um das Marshalling und Unmarshalling der Parameter und des Ergebnisses.

Der Skeleton ist für die Entgegennahme der Nachrichten verantwortlich und leitet die Nachricht an das eigentliche Objekt weiter. Er sorgt für die Transparenz auf Serverseite.

Referenzen auf Remote Objects sind systemweit eindeutig und können frei zwischen Prozessen weitergegeben werden (z. B. als Parameter). Die Implementierung der entfernten Objektreferenzen wird von der Middleware verborgen (Opaque-Referenzen).

RMI Protocol Stack

images/rmi_anatomy/rmi_protocol_stack.svg

Einfacher RMI Dienst und Aufruf

Schnittstelle des Zeitservers

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.util.Date;

public interface Time extends Remote {
  public Date getTime() throws RemoteException;
}

Implementierung der Schnittelle durch den Zeitserver

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.Date;

public class TimeServer extends UnicastRemoteObject implements Time {
  public TimeServer() throws RemoteException {
    super();
  }

  public Date getTime() {
    return new Date();
  }
}

Registrierung des Zeitservers

import java.rmi.Naming;

public class TimeRegistrar {

  /** @param args args[0] has to specify the hostname. */
  public static void main(String[] args) throws Exception {
    String host = args[0];
    TimeServer timeServer = new TimeServer();
    Naming.rebind("rmi://" + host + "/ServerTime", timeServer);
  }
}

Client des Zeitservers

import java.rmi.Naming;
import java.util.Date;

public class TimeClient {
  public static void main(String[] args) throws Exception {
    String host = args[0];
    Time timeServer = (Time) Naming.lookup("rmi://" + host + "/ServerTime");
    System.out.println("Time on " + host + " is " + timeServer.getTime());
  }
}

Java RMI - Tidbits

Klassische Web Services und SOAP

Integration von Unternehmensanwendungen

Die Probleme unternehmensübergreifende Punkt-zu-Punkt-Integration zu ermöglichen führten zur Entwicklung der nächsten Generation von Middleware-Technologien.

images/web_services-vs-message_brokers/message-brokers_and_adapters.svg

Jedes Unternehmen verwendet(e) seinen eigenen konkreten` Message-Broker - wenn wir mit mehreren Unternehmen kommunizieren wollen, müssen wir mehrere Adapter/Lösungen implementieren und pflegen.

Web Services

Webservices are self-contained, modular business applications that have open, internet-oriented, standards-based interfaces.

—UDDI Konsortium

Web Services - konzeptionell

images/web_services-vs-message_brokers/webservices_vision.svg

Web Services - wesentliche Bestandteile

images/web_services-vs-message_brokers/komponenten.svg

Web Services - Protokoll Stack

images/ws-protocol_stack.svg

SOAP

SOAP ist eine Weiterentwicklung von XML-RPC und stand ursprünglich für Simple Object Access Protocol.

SOAP (ab Version 1.2) ist ein Standard des W3C.

Aufbau einer SOAP-Nachricht

images/soap_message.svg

Nachrichten sind Umschläge, in die die Nutzdaten der Anwendung eingeschlossen werden.

Eine Nachricht hat zwei Hauptbestandteile:

Header (optional):

Für infrastrukturelle Daten wie Sicherheit oder Zuverlässigkeit vorgesehen.

Body (obligatorisch):

Für Daten auf Anwendungsebene vorgesehen. Jeder Teil kann in Blöcke unterteilt werden.

Beispiel einer SOAP-Nachricht

  <SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />

  <SOAP-ENV:Header>
    <t:Transaction xmlns:t="ws-transactions-URI" SOAP-ENV:mustUnderstand="1">
      57539
    </t:Transaction>
  </SOAP-ENV:Header>

  <SOAP-ENV:Body>
    <m:GetLastTradePrice xmlns:m="Some-URI">
      <symbol>DEF</symbol>
    </m:GetLastTradePrice>
  </SOAP-ENV:Body>

  </SOAP-ENV:Envelope>

Beispiel eines SOAP-Aufrufs

POST /StockQuote HTTP/1.1
Host: www.stockquoteserver.com
Content-Type: text/xml; charset="utf-8"
Content-Length: nnnn
SOAPAction: "Some-URI"

<SOAP-ENV:Envelope
  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

  <SOAP-ENV:Body>
    <m:GetLastTradePrice xmlns:m="Some-URI">
      <symbol>DIS</symbol>
    </m:GetLastTradePrice>
  </SOAP-ENV:Body>

</SOAP-ENV:Envelope>

Beispiel einer SOAP-Antwort

  HTTP/1.1 200 OK
  Content-Type: text/xml; charset="utf-8"
  Content-Length: nnnn

  <SOAP-ENV:Envelope
    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />

  <SOAP-ENV:Body>
    <m:GetLastTradePriceResponse xmlns:m="Some-URI">
      <Price>34.5</Price>
    </m:GetLastTradePriceResponse>
  </SOAP-ENV:Body>

  </SOAP-ENV:Envelope>

Web Services - Standardisierung

screenshots/ws_standards.png
screenshots/ws_standards_w3c.png

Überblick

images/genealogy-of-middleware.svg

Messaging and Message-oriented Communication/Middleware

ZeroMQ

Sollte zum Beispiel der Server in Java und der Client in C geschrieben sein, dann ist ggf. das Verständnis darüber wie ein String übertragen wird unterschiedlich (z. B. mit null terminiert oder mit einer Länge versehen).

ZeroMQ - Messaging Patterns

images/zeromq/client-server.svg
images/zeromq/pub-sub.svg
images/zeromq/pipeline.svg
Client-Server:

Ermöglicht die übliche Kommunikation zwischen einem Client und einem Server. Allerdings findet ggf. eine Pufferung statt, wenn der Server nicht erreichbar ist.

Publish-Subscribe:

Ermöglicht es den Clients, sich für ein bestimmtes Thema zu registrieren und dann alle Nachrichten zu erhalten, die zu diesem Thema veröffentlicht werden. Ein Nachricht mit einem bestimmten Thema wird an alle dafür registrierten Clients gesendet.

Pipeline:

Ermöglicht die Versendung einer Aufgabe an genau einen beliebigen Worker aus einer Menge von (homogenen) Workern.

ZeroMQ - Beispiel Publish-Subscribe

import static java.lang.Thread.currentThread
import org.zeromq.SocketType;
import org.zeromq.ZMQ;
import org.zeromq.ZContext;

public class Publisher {
  public static void main(String[] args)
      throws Exception {
    try (ZContext context = new ZContext()) {
      ZMQ.Socket publisher =
          context.createSocket(SocketType.PUB);
      publisher.bind("tcp://*:5556");
      publisher.bind("ipc://" + <endpoint>);

      while (!currentThread().isInterrupted()) {
        int zipcode = <some zipcode>
        //  Send to all subscribers
        String update = String.format("%05d %s",
            zipcode, <some msg>);
        publisher.send(update, 0);
      }
} } }
import java.util.StringTokenizer;

import org.zeromq.SocketType;
import org.zeromq.ZMQ;
import org.zeromq.ZContext;

public class Subscriber{
  public static void main(String[] args) {
    try (ZContext context = new ZContext()) {
      ZMQ.Socket subscriber =
          context.createSocket(SocketType.SUB);
      subscriber.connect("tcp://localhost:5556");
      subscriber.subscribe(
          <zipcode(Str)>.getBytes(ZMQ.CHARSET));
      while(true) {
        String string = subscriber.recvStr(0);
        // e.g. take string apart:
        //   part1: zipcode
        //   part2: message
        System.out.println(string);
      }
} } }

MOM - Message Oriented Middleware

MOM - Grundlegendes Interface

Operation

Beschreibung

PUT

Legt eine Nachricht in eine bestimmte Warteschlange.

GET

Blockiert an einer bestimmten Warteschlange bis eine Nachricht verfügbar ist. Entfernt die erste Nachricht.

POLL

Prüft, ob eine Nachricht in einer bestimmten Warteschlange verfügbar ist. Entfernt ggf. die erste Nachricht. POLL blockiert niemals

NOTIFY

Registriert einen Handler (Callback) der aufgerufen wird, wenn eine Nachricht einer bestimmten Warteschlange hinzugefügt wird.

MOM - Queue Managers

images/message-queueing.svg

Queue Managers sind der zentrale Baustein von Message-queueing Systemen. Im Allgemeinen gibt es (mindestens konzeptionell) einen lokalen Queue Manager pro Prozess. Ein Queue Manager ist ein Prozess, der Nachrichten in Warteschlangen speichert und verwaltet. Bei Bedarf kann er mehrere Warteschlangen verwalten und an andere Queue Manager weiterleiten.

Übung

Asynchrone, verbindungsorientierte Kommunikation

Entwickeln Sie einen Client für einen Logging Server, der Lognachrichten an den Server sendet. Im Fehlerfall, z. B. wenn der Server nicht verfügbar ist oder es zu einer Netzwerkpartitionierung kam, sollen die Nachrichten zwischengepuffert werden und bei Serververfügbarkeit wieder zugestellt werden. Mit anderen Worten: Im Fehlerfall soll der Client nicht blockieren, sondern weiter funktionieren. Der Client stellt stattdessen die Nachrichten dann zu, wenn der Server wieder verfügbar wird.

Stellen Sie sicher, dass Nachrichten immer in der richtigen Reihenfolge am Server ankommen. D. h. stellen Sie zum Beispiel sicher, dass eine gepufferte Nachricht nie nach einer neueren Nachricht ankommt.

Verwenden Sie den Code im Anhang als Schablone.

MTAwMDAw:f4UIQXz00AbXZPPZSa7Fo/e8DF7qu/CQRId7Hf+rw0E=:XQgyth/JQbnZRj2O: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

Einfacher TCP basierter SyslogServer

import java.net.*;
import java.io.*;

public class SyslogServer {
  public static void main(String[] args) {
    BufferedReader in = null;
    try {
      ServerSocket server = new ServerSocket(9999);
      while (true) {
        try (Socket con = server.accept()) {
            in = new BufferedReader(
                new InputStreamReader(con.getInputStream()));
            System.out.println("[Logging] " + in.readLine());
        } catch (IOException e) {
            System.err.println(e);
        }
      }
    } catch (IOException e) {
        System.err.println(e);
    }
  }
}

Schablone für den Client

import java.net.*;
import java.io.*;

public class Client {

  /**
  * Versendet die Nachricht an den Server (wenn möglich).
  */
  private static void sendMsg(String msg) throws IOException{
    try (Socket s = new Socket("localhost", 9999)) {
      BufferedReader networkIn =
          new BufferedReader(
              new InputStreamReader(s.getInputStream()));
      PrintWriter networkOut =
          new PrintWriter(s.getOutputStream());
      networkOut.println(msg);
      networkOut.flush();
    }
  }

  > Datenstruktur zum Zwischenspeichern der
  > bisher nicht erfolgreich versendeten Nachrichten!

  public static void log(String msg) {
      > Schicke Nachricht an den Server (wenn möglich).
      > Blockiert nicht, wenn der Server nicht verfügbar ist.
  }

  public static void startThread() throws Exception {
      Thread.ofVirtual().start(() -> {
          while (true) {
              try {
                // Alle 5 Sekunden prüfen wir ob wir noch
                // nicht versendete Nachrichten haben:
                Thread.sleep(5000);
              } catch (InterruptedException e) { }
              > Versende Nachrichten,
              > die noch nicht versendet wurden
          }
      });
  }

  public static void main(String[] args) throws Exception {
      startThread();
      BufferedReader userIn =
          new BufferedReader(
              new InputStreamReader(System.in));
      while (true) {
          String theLine = userIn.readLine();
          if (theLine == null)
              break;
          log(theLine);
      }
  }
}