| Dieser Artikel oder Abschnitt bedarf einer Überarbeitung. Näheres ist auf der Diskussionsseite angegeben. Hilf bitte mit, ihn zu verbessern, und entferne anschließend diese Markierung. |
Ein Socket (englisch wörtlich übersetzt „Sockel“ oder „Steckverbindung“) ist eine bidirektionale Software-Schnittstelle zur Interprozess- (IPC) oder Netzwerk-Kommunikation. Sockets sind vollduplexfähige Alternativen zu Pipes oder Shared Memory.
Sockets bilden eine plattformunabhängige, standardisierte Schnittstelle (API) zwischen der Netzwerkprotokoll-Implementierung des Betriebssystems und der eigentlichen Anwendungssoftware.
Inhaltsverzeichnis |
Die Berkeley Software Distribution (BSD) verwendet Netzwerk-Sockets seit 1983 in Form der Berkeley Sockets API. Auch Linux oder Solaris verwenden BSD-Sockets. Der Zugriff erfolgt ähnlich wie auf Dateien. Windows verwendet eine den Berkeley Sockets nachempfundene API, die Windows Sockets (Winsock).
Unix verwendet Sockets auch zur lokalen Interprozesskommunikation, sogenannte Unix Domain Sockets. Sie sind Teil des POSIX-Standards. Hierbei wird auf Sockets und auf Dateien in gleicher Art und Weise zugegriffen.
Der Kommunikationsablauf heutiger Netzwerkapplikationen, beispielsweise von Client-Server-Anwendungen folgt einem einfachen Konzept:
Um die für diesen Ablauf nötige Funktionalität dem Programmierer zur Verfügung zu stellen, wurden im Laufe der Zeit einige Schnittstellen (= Interfaces) entwickelt, von denen das Sockets-Interface wohl das erfolgreichste ist. Ihren Ursprung hat diese Schnittstelle im traditionellen „Everything-is-a-file“-Konzept von Unix. Die Eingabe/Ausgabe-Behandlung (I/O) unter Unix folgt dem sogenannten Open-Read-Write-Close-Algorithmus. „Open“ (= Öffne) überprüft die Berechtigung bzw. gewährleistet den Zugriff auf I/O-Ressourcen. Darauf folgen eine oder mehrere Read/Write (=Lies/Schreibe)-Zyklen, wobei „Read“ Daten von der I/O-Ressource liest und dem Benutzer zur Verfügung stellt, „Write“ hingegen Daten schreibt (z. B. Speichervorgang). Zum Abschluss des Vorgangs erfolgt das Kommando „Close“. Als Netzwerkunterstützung in Unix-Systemen integriert wurde, wollte man die Kommunikation ähnlich diesem ORWC-Algorithmus gestalten. Aus diesen Bemühungen entstand unter BSD-Unix die Sockets-Schnittstelle.
Sockets wurden ursprünglich nur für das BSD-Unix-Betriebssystem entwickelt, definieren jedoch mittlerweile einen De-facto-Standard. So modellierte auch Microsoft das Windows Socket Interface der Windows-Betriebssysteme auf Basis der Berkeley-Sockets.
Bei einem Socket handelt es sich um ein Ende einer Kommunikationsschnittstelle zwischen zwei Programmen, welche Daten über ein Netzwerk austauschen. Eine Applikation fordert einen Socket vom Betriebssystem an und kann über diesen anschließend Daten verschicken und empfangen. Das Betriebssystem hat die Aufgabe, alle benutzten Sockets sowie die zugehörigen Verbindungsinformationen zu verwalten. Verschiedene Socket-Klassen repräsentieren die Verbindung auf der Client- wie auf der Serverseite. Eine Socket-Adresse kann zum Beispiel definiert sein durch:
sin_family, das heißt der zugehörigen Adressfamilie des Netzwerks (z. B. AF_INET = Adressfamilie Internetadresse)
Diese Informationen sind allerdings vom verwendeten Protokoll abhängig, so ist die Adress-Information für einen Unix Domain Socket (wird benutzt für Interprozesskommunikation) ein Dateipfad. Typischerweise handelt es sich bei der Adress-Information im Internet um die IP-Adresse und den Port.
Die Vergabe der Port-Nummern erfolgt beim Verbindungsaufbau. Die Port-Nummern werden großteils vom System beliebig vergeben. Ausnahmen sind die sogenannten Well-Known-Ports, welche festgelegt sind und von bekannten Applikationen verwendet werden.
Der wesentliche Unterschied zwischen diesen beiden Typen besteht darin, dass Stream Sockets über einen kontinuierlichen Zeichen-Datenstrom kommunizieren, wohingegen Datagram Sockets auf dem Senden von einzelnen Nachrichten basieren.
Stream Sockets verwenden meist TCP, was aufgrund der Eigenschaften von TCP zu einer hohen Verlässlichkeit führt. Andere Transportprotokolle als TCP sind denkbar, aber wenig verbreitet. Datagram Sockets arbeiten üblicherweise über UDP, also verbindungslos. Dies impliziert Datenaustausch mit geringer Latenz, jedoch geringe Verlässlichkeit. Auch hier sind natürlich alternative Protokolle möglich.
Ein Socket ist normalerweise die Verbindungsstelle zu einem bestimmten entfernten Programm, repräsentiert durch dessen Adress-Information (z. B. IP-Adresse und Portnummer). Dem Socket selbst ist natürlich auch die eigene Adress-Information zugeordnet.
Ein Server muss jedoch auf Anfragen von unbekannten Rechnern warten können. Er kann sich auf eine bestimmte Adresse binden und auf Anfragen an diese Adresse warten. Allerdings muss ein Server auch auf vielen Adressen Anfragen bearbeiten können. Um dies effizienter zu gestalten, gibt es bei einigen Protokollen auch eine sogenannte Wildcard Adresse, bei der ein oder mehrere Teile der Adress-Information nicht spezifisch sind. Im Beispiel von TCP/IP und UDP/IP ist bei einer Wildcard-Adresse nur die Port-Nummer relevant, es wird eine spezielle (ungültige) IP-Adresse angegeben, um zu signalisieren, dass Verbindungen auf allen IP-Adressen akzeptiert werden sollen.
Wenn der Server eine Anfrage von einem Client erhält, dann wird vom Server-Socket die „normale“ Socket-Verbindung abgeleitet. Das heißt, dass der ursprüngliche Server-Socket erhalten bleibt und weiterhin auf neue Verbindungen wartet, während ein neuer, auf den bestimmten Client gerichteter Socket geöffnet wird, der nur für die Kommunikation mit diesem einen Client verwendet wird. Dieser bleibt solange bestehen, bis die Verbindung zum Client von einer der beiden Seiten beendet wird.
Unterschiede beim Aufbau von Sockets auf Client- bzw. Server-Seite:
Client-seitig:
Server-seitig:
Java als plattformunabhängige Programmiersprache unterstützt im Paket java.net unmittelbar die Socket-Programmierung. Das zeigt die Betriebssystemunabhängigkeit des Socket-Konzeptes. Die Implementierung der Sockets für die verschiedenen Plattformen (Linux, Windows, Spezialsysteme) erfolgt in der Klassenbibliothek der virtuellen Maschine.
Die Klassen für die Socket-Programmierung sind Socket und ServerSocket. Folgendes Kurzbeispiel zeigt die Verwendung:
ServerSocket serverSocket = new ServerSocket(port); //Serversocket mit bestimmter Port-Nummer erstellen while(true) { Socket clientSocket = serverSocket.accept(); //auf Anfragen warten InputStream input = clientSocket.getInputStream(); //InputStream-Objekt öffnen int data = input.read(); //Daten lesen clientSocket.close(); //Verbindung schließen }
Weiterhin gibt es in der aktuellen Java-Version die Möglichkeit, Sockets über die NewIO-(nio)-Bibliothek anzusprechen. Der Code ist etwas aufwändiger, kann jedoch schneller ausgeführt werden. Das Multiplexing mehrerer Sockets geschieht über einen so genannten Selector (vergleichbar dem Unix-Systemaufruf select).
Die rein funktionale Programmiersprache Haskell bietet durch das Modul Network eine plattformunabhängige Möglichkeit zur Verwendung von Sockets. Das folgende Beispiel beinhaltet den vollständigen Code für einen sehr einfachen Server und einen korrespondierenden Client. Der Client nimmt Textzeilen von der Standardeingabe entgegen und schickt diese über den Netzwerk-Socket an den Server. Dieser wiederum gibt die Textzeilen auf die Standardausgabe aus.
Der hier gezeigte Server ist insofern einfach, als er z. B. keine sichere Beendigung ermöglicht. Eine Lösungsmöglichkeit wird in http://www.haskell.org/haskellwiki/Concurrency_demos/Graceful_exit unter Verwendung von Software Transactionsal Memory (STM) aufgezeigt.
module Main where
import Control.Concurrent
import Control.Monad
import IO
import Network
main :: IO ()
main = withSocketsDo $ do
theSocket <- listenOn (PortNumber 2048)
forever $ acceptConnectionAndFork theSocket echoServer
type MessageHandler = (Handle, String, PortNumber) -> IO ()
acceptConnectionAndFork :: Socket -> MessageHandler -> IO ()
acceptConnectionAndFork theSocket handler = do
connection <- accept theSocket
forkIO $ handler connection
return ()
echoServer :: MessageHandler
echoServer (handle,hostname,portnumber) = do
putStrLn $ "("++hostname++":"++show portnumber++"): Open"
c <- hGetContents handle
sequence_ [putStrLn $ "("++hostname++":"++show portnumber++"): Msg "++show l | l<-lines c]
putStrLn $ "("++hostname++":"++show portnumber++"): Close"
module Main where
import Network
import IO
import Control.Concurrent
main :: IO ()
main = withSocketsDo $ do
handle <- connectTo "localhost" (PortNumber 2048)
input <- getContents
sequence_ [do
hPutStrLn handle l
hFlush handle |
l<-lines input]
hClose handle