Unlimited WordPress themes, graphics, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Web Development
Code

HTTP kurz: HTTP-Verbindungen

by
Difficulty:IntermediateLength:LongLanguages:
This post is part of a series called HTTP Succinctly.
HTTP Succinctly: HTTP Messages
HTTP Succinctly: HTTP Web Architecture

German (Deutsch) translation by Katharina Nevolina (you can also view the original English article)

Im letzten Artikel haben wir uns HTTP-Nachrichten angesehen und Beispiele für die Textbefehle und -codes gesehen, die vom Client zum Server und zurück in einer HTTP-Transaktion fließen.  Aber wie bewegen sich die Informationen in diesen Nachrichten durch das Netzwerk?  Wann sind die Netzwerkverbindungen geöffnet?  Wann sind die Verbindungen geschlossen?  Das sind einige der Fragen, die dieser Artikel beantworten wird, wenn wir uns HTTP aus einer untergeordneten Perspektive ansehen.  Aber zuerst müssen wir einige der Abstraktionen unterhalb von HTTP verstehen.

Eine Wirbelwind-Tour der Vernetzung

Um HTTP-Verbindungen zu verstehen, müssen wir nur ein wenig darüber wissen, was in den Schichten unter HTTP passiert.  Die Netzwerkkommunikation besteht wie viele Anwendungen aus Schichten.  Jede Schicht in einem Kommunikationsstapel ist für eine bestimmte und begrenzte Anzahl von Verantwortlichkeiten verantwortlich.

Zum Beispiel nennen wir HTTP ein Anwendungsschichtprotokoll, weil es zwei Anwendungen ermöglicht, über ein Netzwerk zu kommunizieren.  Häufig ist eine der Anwendungen ein Webbrowser und die andere Anwendung ist ein Webserver wie IIS oder Apache.  Wir haben gesehen, wie HTTP-Nachrichten es dem Browser erlauben, Ressourcen vom Server anzufordern.  Aber die HTTP-Spezifikationen sagen nichts darüber aus, wie die Nachrichten tatsächlich das Netzwerk durchqueren und den Server erreichen - das ist die Aufgabe von Protokollen der unteren Schicht.  Eine Nachricht von einem Webbrowser muss eine Reihe von Schichten durchlaufen, und wenn sie beim Webserver ankommt, bewegt sie sich durch eine Reihe von Schichten, um den Web-Service-Prozess zu erreichen.

Figure 4: Protocol layers
Protokollschichten

Die Schicht unter HTTP ist ein Transportschichtprotokoll.  Fast der gesamte HTTP-Verkehr läuft über TCP (kurz für Transmission Control Protocol), obwohl das von HTTP nicht benötigt wird.  Wenn ein Benutzer eine URL in den Browser eingibt, extrahiert der Browser zuerst den Hostnamen aus der URL (und gegebenenfalls der Portnummer) und öffnet einen TCP-Socket, indem er die Serveradresse (abgeleitet vom Hostnamen) und den Port angibt (die Standard ist 80).

Sobald eine Anwendung über einen offenen Socket verfügt, kann sie damit beginnen, Daten in den Socket zu schreiben.  Das einzige, worüber der Browser sich Gedanken machen muss, ist das Schreiben einer korrekt formatierten HTTP-Anfrage in den Socket.  Die TCP-Schicht akzeptiert die Daten und stellt sicher, dass die Nachricht an den Server gesendet wird, ohne verloren oder dupliziert zu werden.  TCP sendet automatisch alle Informationen, die während der Übertragung verloren gehen könnten, weshalb TCP als zuverlässiges Protokoll bekannt ist.  Neben der Fehlererkennung bietet TCP auch eine Flusskontrolle.  Die Flusssteuerungsalgorithmen in TCP stellen sicher, dass der Sender Daten nicht zu schnell sendet, damit der Empfänger die Daten verarbeiten kann.  Die Flusskontrolle ist in dieser Welt der verschiedenen Netzwerke und Geräte wichtig.

Kurz gesagt, TCP stellt Dienste bereit, die für die erfolgreiche Zustellung von HTTP-Nachrichten wesentlich sind, aber dies geschieht auf transparente Weise, so dass die meisten Anwendungen sich nicht um TCP kümmern müssen.  Wie die vorherige Abbildung zeigt, ist TCP nur die erste Schicht unter HTTP.  Nach TCP auf der Transportschicht kommt IP als Netzwerkschichtprotokoll.

IP ist die Abkürzung für Internet Protocol.  Während TCP für die Fehlererkennung, die Flusskontrolle und die Gesamtzuverlässigkeit verantwortlich ist, ist IP dafür verantwortlich, Informationen zu übernehmen und sie durch die verschiedenen Switches, Router, Gateways, Repeater und anderen Geräte, die Informationen von einem Netzwerk zum nächsten übertragen, zu übertragen auf der ganzen Welt.  IP versucht, die Daten am Ziel zu liefern (aber es garantiert keine Zustellung - das ist TCPs Aufgabe).  IP erfordert, dass Computer eine Adresse haben (die berühmte IP-Adresse, ein Beispiel ist 208.192.32.40).  IP ist auch dafür verantwortlich, Daten in Pakete zu zerlegen (oft als Datagramme bezeichnet) und diese Pakete manchmal zu zerlegen und neu zusammenzusetzen, so dass sie für ein bestimmtes Netzwerksegment optimiert sind.

Alles, was wir bisher besprochen haben, passiert in einem Computer, aber irgendwann müssen diese IP-Pakete über ein Stück Draht, ein Glasfaserkabel, ein drahtloses Netzwerk oder eine Satellitenverbindung übertragen werden.  Das liegt in der Verantwortung der data link-Schicht.  Eine gängige Technologie ist zu diesem Zeitpunkt Ethernet.  Auf dieser Ebene werden Datenpakete zu Frames und Low-Level-Protokolle wie Ethernet auf 1s, 0s und elektrische Signale fokussiert.

Schließlich erreicht das Signal den Server und kommt über eine Netzwerkkarte herein, wo der Prozess umgekehrt wird.  Die Datenverbindungsschicht liefert Pakete an die IP-Schicht, die Daten an TCP übergibt, die die Daten in die ursprüngliche HTTP-Nachricht, die vom Client gesendet wird, wieder zusammensetzen und in den Webserverprozess schieben können.  Es ist ein wunderschönes Stück Arbeit, das alles durch Standards ermöglicht wird.


Schnelle HTTP-Anfrage mit Sockets und C#

Wenn Sie sich fragen, wie es aussieht, eine Anwendung zu schreiben, die HTTP-Anforderungen stellt, dann ist der folgende C# -Code ein einfaches Beispiel dafür, wie der Code aussehen könnte.  Dieser Code hat keine Fehlerbehandlung und versucht, eine Serverantwort auf das Konsolenfenster zu schreiben (Sie müssen also eine Textressource anfordern), aber es funktioniert für einfache Anfragen. Eine Kopie des folgenden Codebeispiels ist verfügbar unter https://bitbucket.org/syncfusion/http-succinctly.  Der Beispielname ist sockets-sample.

Beachten Sie, wie das Programm die Serveradresse (unter Verwendung von Dns.GetHostEntry) nachschlagen und eine richtige HTTP-Nachricht mit einem GET-Operator und einem Host-Header formulieren muss.  Der eigentliche Netzwerkteil ist ziemlich einfach, da die Socket-Implementierung und TCP den größten Teil der Arbeit übernehmen.  TCP versteht beispielsweise, wie mehrere Verbindungen zu demselben Server verwaltet werden (sie erhalten alle lokal unterschiedliche Portnummern).  Aus diesem Grund werden zwei ausstehende Anforderungen an denselben Server nicht verwechselt und empfangen Daten für den anderen.


Vernetzung und Wireshark

Wenn Sie Einblick in TCP und IP benötigen, können Sie ein kostenloses Programm wie Wireshark installieren (verfügbar für OSX und Windows von wireshark.org).  Wireshark ist ein Netzwerkanalysator, der Ihnen jede Information zeigt, die über Ihre Netzwerkschnittstellen fließt.  Mit Wireshark können Sie TCP-Handshakes beobachten. Dies sind die TCP-Nachrichten, die zum Herstellen einer Verbindung zwischen dem Client und dem Server erforderlich sind, bevor die eigentlichen HTTP-Nachrichten zu fließen beginnen.  Sie können auch die TCP- und IP-Header (jeweils 20 Byte) für jede Nachricht sehen.  Die folgende Abbildung zeigt die letzten beiden Schritte des Handshakes, gefolgt von einer GET-Anfrage und einer 304 Redirect.

Figure 5: Using Wireshark
Verwenden von Wireshark

Mit WiresharkMit Wireshark können Sie sehen, wenn HTTP-Verbindungen hergestellt und geschlossen werden.  Der wichtige Teil, der von all dem genommen wird, ist nicht, wie Handshakes und TCP auf der untersten Ebene arbeiten, sondern dass HTTP fast ausschließlich auf TCP beruht, um sich um all die harte Arbeit zu kümmern und TCP einen gewissen Mehraufwand wie etwa Handshakes beinhaltet.  Daher hängen die Leistungsmerkmale von HTTP auch von den Leistungsmerkmalen von TCP ab, und es ist das Thema für den nächsten Abschnitt.


HTTP, TCP und die Evolution des Webs

In den alten Zeiten des Internets waren die meisten Ressourcen textlich.  Sie können ein Dokument von einem Webserver anfordern, fünf Minuten lang lesen und dann ein anderes Dokument anfordern.  Die Welt war einfach.

Für das heutige Web benötigen die meisten Webseiten mehr als eine Ressource, um vollständig zu rendern.  Jede Seite in einer Webanwendung enthält ein oder mehrere Bilder, eine oder mehrere JavaScript-Dateien und eine oder mehrere CSS-Dateien.  Es ist nicht ungewöhnlich, dass bei der anfänglichen Anforderung einer Homepage 30 oder 50 zusätzliche Anforderungen abgerufen werden, um alle anderen Ressourcen abzurufen, die mit einer Seite verknüpft sind.

In den alten Zeiten war es auch einfach für einen Browser, eine Verbindung mit einem Server herzustellen, eine Anfrage zu senden, die Antwort zu erhalten und die Verbindung zu schließen.  Wenn die heutigen Webbrowser die Verbindungen nacheinander öffnen und darauf warten, dass jede Ressource vollständig heruntergeladen wird, bevor sie mit dem nächsten Download beginnen, wird sich das Web sehr langsam anfühlen.  Das Internet ist voller Latenz.  Signale müssen lange Strecken zurücklegen und sich durch verschiedene Hardware-Teile schlängeln.  Es gibt auch einen gewissen Mehraufwand beim Aufbau einer TCP-Verbindung.  Wie wir im Screenshot von Wireshark gesehen haben, muss ein dreistufiger Handshake abgeschlossen sein, bevor eine HTTP-Transaktion beginnen kann.

Die Entwicklung von einfachen Dokumenten zu komplexen Seiten erfordert einige Einfallsreichtum in der praktischen Verwendung von HTTP.


Parallele Verbindungen

Die meisten Benutzeragenten (auch Webbrowser genannt) stellen keine Anfragen seriell einzeln ab.  Stattdessen öffnen sie mehrere parallele Verbindungen zu einem Server.  Wenn beispielsweise der HTML-Code für eine Seite heruntergeladen wird, sieht der Browser möglicherweise zwei  <img>-Tags auf der Seite. Der Browser öffnet also zwei parallele Verbindungen, um die beiden Bilder gleichzeitig herunterzuladen.  Die Anzahl der parallelen Verbindungen hängt vom Benutzeragenten und der Konfiguration des Agenten ab.

Lange Zeit betrachteten wir zwei als die maximale Anzahl von parallelen Verbindungen, die ein Browser erzeugen würde.  Wir betrachten zwei als das Maximum, weil der populärste Browser seit vielen Jahren - Internet Explorer (IE) 6 - nur zwei gleichzeitige Verbindungen zu einem einzelnen Host zulassen würde.  IE befolgte nur die Regeln, die in der HTTP 1.1-Spezifikation dargelegt sind, die besagt:

Ein Einzelbenutzer-Client SOLLTE NICHT mehr als 2 Verbindungen mit einem Server oder Proxy unterhalten.

Um die Anzahl der parallelen Downloads zu erhöhen, verwenden viele Websites einige Tricks.  Zum Beispiel ist das Limit für zwei Verbindungen pro Host, was bedeutet, dass ein Browser wie IE 6 gerne zwei parallele Verbindungen zu www.odetocode.com und zwei parallele Verbindungen zu images.odetocode.com herstellen würde.  Durch das Hosten von Bildern auf einem anderen Server können Websites die Anzahl paralleler Downloads erhöhen und ihre Seiten schneller laden (selbst wenn die DNS-Datensätze so eingerichtet wurden, dass alle vier Anforderungen auf denselben Server verweisen, da die Grenze für zwei Verbindungen pro Host ist Name, nicht IP-Adresse).

Die Dinge sind heute anders.  Die meisten Benutzeragenten verwenden unterschiedliche Heuristiken, wenn sie entscheiden, wie viele parallele Verbindungen eingerichtet werden sollen.  Beispielsweise wird Internet Explorer 8 jetzt bis zu sechs gleichzeitige Verbindungen öffnen.

Die eigentliche Frage ist: Wie viele Verbindungen sind zu viele?  Parallele Verbindungen werden dem Gesetz abnehmender Erträge folgen. Zu viele Verbindungen können das Netzwerk sättigen und verstopfen, insbesondere wenn mobile Geräte oder unzuverlässige Netzwerke beteiligt sind.  Zu viele Verbindungen können die Leistung beeinträchtigen. Außerdem kann ein Server nur eine begrenzte Anzahl von Verbindungen akzeptieren.  Wenn also 100.000 Browser gleichzeitig 100 Verbindungen zu einem einzelnen Webserver herstellen, werden schlimme Dinge passieren.  Dennoch ist es besser, mehr als eine Verbindung pro Agent zu verwenden, als alles seriell herunterzuladen.

Glücklicherweise sind parallele Verbindungen nicht die einzige Leistungsoptimierung.


Persistente Verbindungen

In den frühen Tagen des Webs würde ein Benutzeragent eine Verbindung für jede einzelne Anfrage, die er an einen Server gesendet hat, öffnen und schließen.  Diese Implementierung entsprach der Idee von HTTP, ein vollständig staatenloses Protokoll zu sein.  Mit steigender Anzahl der Anfragen pro Seite wuchs auch der Overhead, der durch TCP-Handshakes und die speicherinternen Datenstrukturen erzeugt wurde, die erforderlich waren, um jeden TCP-Socket einzurichten.  Um diesen Overhead zu reduzieren und die Leistung zu verbessern, schlägt die HTTP 1.1-Spezifikation vor, dass Clients und Server persistente Verbindungen implementieren und permanente Verbindungen zum Standardverbindungstyp machen.

Eine persistente Verbindung bleibt nach dem Abschluss einer Anfrage-Antwort-Transaktion geöffnet.  Bei diesem Verhalten bleibt ein Benutzeragent mit einem bereits geöffneten Socket, den er verwenden kann, um weiterhin Anforderungen an den Server zu stellen, ohne dass ein neuer Socket geöffnet werden muss.  Permanente Verbindungen vermeiden auch die langsame Startstrategie, die Teil der TCP-Überlastungssteuerung ist, wodurch permanente Verbindungen im Laufe der Zeit besser ausgeführt werden.  Kurz gesagt, dauerhafte Verbindungen reduzieren die Speicherauslastung, reduzieren die CPU-Auslastung, reduzieren die Netzwerküberlastung, reduzieren die Latenz und verbessern im Allgemeinen die Antwortzeit einer Seite.  Aber wie alles im Leben gibt es einen Nachteil.

Wie bereits erwähnt, kann ein Server nur eine begrenzte Anzahl eingehender Verbindungen unterstützen.  Die genaue Anzahl hängt von der Größe des verfügbaren Speichers, der Konfiguration der Serversoftware, der Leistung der Anwendung und vielen anderen Variablen ab.  Es ist schwierig, eine genaue Anzahl anzugeben. Wenn Sie jedoch davon sprechen, Tausende von gleichzeitigen Verbindungen zu unterstützen, müssen Sie testen, ob ein Server die Last unterstützt.  Tatsächlich sind viele Server so konfiguriert, dass sie die Anzahl gleichzeitiger Verbindungen weit unterhalb des Punktes begrenzen, an dem der Server umfallen wird.  Die Konfiguration ist eine Sicherheitsmaßnahme, um Denial-of-Service-Angriffe zu verhindern.  Es ist relativ einfach für jemanden, ein Programm zu erstellen, das Tausende von dauerhaften Verbindungen zu einem Server öffnet und den Server davon abhält, auf echte Clients zu antworten.  Persistente Verbindungen sind eine Leistungsoptimierung, aber auch eine Schwachstelle.

In Anlehnung an eine Schwachstelle müssen wir uns auch fragen, wie lange es dauert, eine dauerhafte Verbindung offen zu halten.  In einer Welt unendlicher Skalierbarkeit können die Verbindungen so lange geöffnet bleiben, wie das Benutzeragentenprogramm ausgeführt wird.  Da jedoch ein Server eine begrenzte Anzahl von Verbindungen unterstützt, sind die meisten Server so konfiguriert, dass sie eine persistente Verbindung schließen, wenn sie für einige Zeit inaktiv sind (z. B. fünf Sekunden in Apache).  Benutzeragenten können Verbindungen auch nach einer Leerlaufzeit schließen.  Die einzige Sichtbarkeit in Verbindungen, die geschlossen werden, ist durch einen Netzwerkanalysator wie Wireshark.

Neben dem aggressiven Schließen persistenter Verbindungen kann die meiste Web-Server-Software so konfiguriert werden, dass persistente Verbindungen deaktiviert werden.  Das ist bei gemeinsam genutzten Servern üblich.  Geteilte Server opfern Leistung, um so viele Verbindungen wie möglich zu ermöglichen.  Da permanente Verbindungen der Standardverbindungsstil mit HTTP 1.1 sind, muss ein Server, der keine dauerhaften Verbindungen zulässt, in jeder HTTP-Antwort einen Connection-Header enthalten.  Der folgende Code ist ein Beispiel.

Der Connection: close-Header ist ein Signal an den Benutzeragenten, dass die Verbindung nicht dauerhaft ist und so schnell wie möglich geschlossen werden sollte.  Der Agent darf keine zweite Anfrage für dieselbe Verbindung stellen.


Pipelineverbindungen

Parallele Verbindungen und dauerhafte Verbindungen werden häufig verwendet und von Clients und Servern unterstützt.  Die HTTP-Spezifikation ermöglicht auch Pipelineverbindungen, die von Servern oder Clients nicht so häufig unterstützt werden.  In einer Pipelineverbindung kann ein Benutzeragent mehrere HTTP-Anforderungen für eine Verbindung senden, bevor er auf die erste Antwort wartet.  Das Pipelining ermöglicht ein effizienteres Packen von Anfragen in Pakete und kann die Latenz reduzieren, aber es wird nicht so häufig unterstützt wie parallele und persistente Verbindungen.


Wo sind wir?

In diesem Kapitel haben wir uns die HTTP-Verbindungen angesehen und einige der Leistungsoptimierungen besprochen, die durch die HTTP-Spezifikationen möglich wurden.  Jetzt, da wir uns tief in HTTP-Nachrichten vertieft und sogar die Verbindungen und die TCP-Unterstützung unter dem Protokoll untersucht haben, werden wir einen Schritt zurücktreten und das Internet aus einer breiteren Perspektive betrachten.

Advertisement
Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.