724 lines
24 KiB
TeX
724 lines
24 KiB
TeX
\subsection{Einführung zu Sockets}
|
|
|
|
Das \vocab[Generisches API]{Generische API}
|
|
\begin{itemize}
|
|
\item unterstützt \vocab[Nachrichten-orientiert]{Nachrichten-orientierte} und \vocab[Verbindungs-orientiert]{Verbindungs-orientierte} Kommunikation.
|
|
\item ist betriebssystemunabhängig
|
|
\item nutzt I/O Dienste des Betriebssystems
|
|
\item unterstützt verschiedene Protokollfamilien
|
|
\item ist unabhängig von der konkreten Adress-Repräsentation der kommunizierenden Endpunkte
|
|
\item unterstützt ggf.~manche Clients und Server auf spezielle Weise
|
|
\end{itemize}
|
|
|
|
Es gibt verschiedene APIs für den Zugriff auf TCP / IP:
|
|
\begin{itemize}
|
|
\item (Berkeley-) Sockets, diese werden wir uns genauer ansehen
|
|
\item TLI (System V Transport Layer Interface)
|
|
\item XTI (X/Open Transport Interface)
|
|
\item Winsock
|
|
\item MacTCP
|
|
\item Asio C++ Library (plattformunabhängig)
|
|
\end{itemize}
|
|
|
|
\subsubsection{Benötigte Funktionen des Network API}
|
|
Ein Network API stellt grundsätzlich Folgendes zur Verfügung:
|
|
|
|
Vorbereitung:
|
|
\begin{itemize}
|
|
\item Identifikation der beiden Endpunkte der Kommunikation
|
|
\item Verbindungsaufbau (ggfs.~kein Verbindungsaufbau, d.h.~verbindungslos)
|
|
\item Warten auf eingehende Verbindungswünsche (typisch für Server)
|
|
\end{itemize}
|
|
|
|
Datentransferphase:
|
|
\begin{itemize}
|
|
\item Senden und Empfangen von Daten
|
|
\item Fehlerbehandlung
|
|
\end{itemize}
|
|
|
|
Nachbereitung:
|
|
\begin{itemize}
|
|
\item Ordentlicher Abbau einer Verbindung
|
|
\item ggf.~Fehlerbehandlung
|
|
\end{itemize}
|
|
|
|
\subsubsection{Berkeley-Sockets}
|
|
\vocab{Berkeley-Sockets} stammen ursprünglich aus Berkeley-Unix, mittlerweile werden sie einfach Sockets genannt.
|
|
Diese werden wir uns genauer ansehen.
|
|
|
|
Ein Socket ist eine Datenstruktur innerhalb eines Programms und stellt eine \emph{abstrakte Repräsentation} eines kommunizierenden Endpunktes
|
|
dar.
|
|
Sockets sind durch Systemfunktionen implementiert und arbeiten ähnlich wie andere Unix I/O-Dienste.
|
|
Sockets unterstützen verschiedene Protokollfamilien und sind insbesondere unabhängig von der konkreten Adressdarstellung.
|
|
|
|
\subsubsection{Socket framework}
|
|
|
|
Mit einem Socket können wir vom User Space aus auf Funktionalität des Kernel Space, insbesondere
|
|
TCP oder UDP, und damit indirekt auf IP und Netzwerktreiber zugreifen. Der direkte Zugriff auf \ac{ip} benötigt in der Regel
|
|
privilegierte Rechte.
|
|
|
|
\subsubsection{Unix Descriptor Table}
|
|
Unix führt eine \vocab{Descriptor Table}, die jeweilige Datenstrukturen enthält (siehe auch \autoref{descriptortable}).
|
|
Dieser kann sowohl Dateideskriptoren (wie bereits kennengelernt), als auch Socket
|
|
Descriptoren enthalten.
|
|
|
|
Ein \vocab{Socket Descriptor} ist ein normaler File Descriptor, dessen Eintrag in der Descriptor Table jedoch eine Address-Family, einen Service, Local IP, Remote IP, Local Port und Remote Port enthält.
|
|
|
|
|
|
|
|
\subsubsection{Socket Domain Families}
|
|
|
|
Die \vocab{Protokollfamilie} eines Sockets muss bei Initialisierung festgelegt werden.
|
|
Es gibt die folgenden Schlüssel:
|
|
|
|
\begin{itemize}
|
|
\item \vocab{Internet Domain Sockets}, verfügbar mittels \ccintro{AF_INET}
|
|
\item \vocab{Unix Domain Sockets}, mittels \ccintro{AF_UNIX} oder \ccintro{AF_LOCAL}
|
|
\item \vocab{Novell IPX}, mittels \ccintro{AF_IPX}
|
|
\item \vocab{AppleTalk} (buuuuh!), mittels \ccintro{AF_APPLETALK}
|
|
\end{itemize}
|
|
|
|
\begin{remark}
|
|
Beachte, dass alle diese macros mit \code{AF_} beginnen, dies steht für \enquote{address family}.
|
|
Mittlerweile wird allerdings nicht mehr zwischen Protokollfamilien und Adressfamilien unterschieden.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Service Typ eines Sockets}
|
|
Ein Socket kann einen von drei Typen haben
|
|
\begin{description}
|
|
\item[Stream]
|
|
Schlüssel \ccintro{SOCK_STREAM}, Verbindungs- und Byte-Stream orientiert.
|
|
Unter \code{AF_INET} wird \ac{tcp} eingesetzt.
|
|
\item[Datagram]
|
|
Schlüssel \ccintro{SOCK_DGRAM}, verbindungslos und unzuverlässig.
|
|
Unter \code{AF_INET} wird \ac{udp} eingesetzt.
|
|
\item[Direktzugriff]
|
|
Schlüssel \ccintro{SOCK_RAW}.
|
|
Dieser umgeht das Transportprotokoll und greift direkt auf das Kommunikationsprotokoll (im Fall von \code{AF_INET} \ac{ip}) zu.
|
|
In der Regel werden spezielle Rechte benötigt.
|
|
\end{description}
|
|
|
|
|
|
Die letzte Möglichkeit besteht zwar, erfordert allerdings viel aufwand,
|
|
weil IP selbst nicht ports spezifiziert (das machen \ac{udp} / \ac{tcp} ),
|
|
und dies somit manuell geschehen muss.
|
|
Anwendung findet dies in speziellen Routern oder eigenen Protokollen.
|
|
|
|
\autoref{tab:protokollfamilien} zeigt die Protokollfamilien und den Service-Typ eines sockets,
|
|
und die entsprechenden Protokolle, die verwendet werden.
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\begin{tabular}{c | c | c | c | c | c}
|
|
& \cc{AF_INET} & \cc{AF_INET6} & \cc{AF_LOCAL}, \cc{AF_UNIX} & \cc{AF_ROUTE} & \cc{AF_KEY} \\
|
|
\hline
|
|
\cc{SOCK_STREAM} & \ac{tcp} & \ac{tcp} & Ja & & \\
|
|
\cc{SOCK_DGRAM} & \ac{udp} & \ac{udp} & Ja & & \\
|
|
\cc{SOCK_RAW} & IPv4 & IPv6 & & Ja & Ja
|
|
\end{tabular}
|
|
\caption{Kombinationsmöglichkeiten von Protokollfamilie und Socket Type für Internetprotokolle}
|
|
\label{tab:protokollfamilien}
|
|
\end{table}
|
|
|
|
|
|
\subsubsubsection{Erzeugung eines Sockets}
|
|
Vergleiche für die beiden Parameter auch \autoref{tab:protokollfamilien}
|
|
|
|
Das Erzeugen eines sockets geschieht mit \ccintro{socket()}.
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
int socket(int domain, int type, int protocol);
|
|
// domain gibt die Protokollfamilie an: AF_INET, AF_INET6, AF_LOCAL, ...
|
|
// type gibt SOCK_STREAM, SOCK_DGRAM oder SOCK_RAW an
|
|
// protocol setzt spezielle Werte im Protokoll-feld
|
|
// wird aber bei TCP und UDP automatisch behandelt, und deswegen auf 0 gesetzt.
|
|
|
|
// Rueckgabewerte: ein socketdeskriptor, -1 im Fehlerfall
|
|
\end{lstlisting}
|
|
|
|
Solch ein Socket stellt nun abstrakte Ressourcen bereit, um zu kommunizieren,
|
|
kümmert sich jedoch noch nicht um Adressierung etc.
|
|
|
|
Um einen Socket zu benutzen, um empfangsbereit zu sein, verwendet man:
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
|
|
// sockfd: der behandlete socket file deskriptor
|
|
// myaddr: ein Zeiger auf eine (generische) Adresse
|
|
// addrlen: Laengenangabe des Adress-structs unter @myaddr
|
|
|
|
// gibt -1 im Fehlerfall zurueck, 0 sonst
|
|
\end{lstlisting}
|
|
|
|
\subsubsubsection{Socket-Adressen}
|
|
|
|
Ein \ccintro{struct sockaddr} sieht wie folgt aus:
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
typedef uint16_t sa_family_t;
|
|
|
|
struct sockaddr {
|
|
sa_family_t sa_family; // address family
|
|
char sa_data[14]; // up to 14 bytes of direct address
|
|
}
|
|
\end{lstlisting}
|
|
|
|
% \todoquestion{sehe ich das richtig, dass sowohl der socket als auch der addr die adressfamilie bekommen müssen?} Ja. Siehe https://stackoverflow.com/questions/5263262/why-does-struct-sockaddr-contain-a-address-family-field
|
|
|
|
\begin{remark}
|
|
Auf manchen Betriebssystemen gibt es noch ein \ccintro[sin_len]{uint8_t sin_len}
|
|
im \cc{struct sockaddr}, das interessiert uns jedoch ersteinmal nicht.
|
|
\end{remark}
|
|
|
|
\begin{table}[htpb]
|
|
\centering
|
|
\caption{Typen}
|
|
\label{tab:types}
|
|
\begin{tabular}{lll}
|
|
\textbf{Datentyp} & \textbf{Beschreibung} & \textbf{Header} \\
|
|
\hline
|
|
\ccintro{int8_t}, \ccintro{int16_t} & signed integer ($8 / 16 / 32 / 64$ bit) & \code{sys/types.h}\\
|
|
\ccintro{int32_t},\ccintro{int64_t} \\
|
|
\ccintro{uint8_t}, \ccintro{uint16_t} & unsigned integer ($8 / 16 / 32 / 64$ bit) & \code{sys/types.h}\\
|
|
\ccintro{uint32_t}, \ccintro{uint64_t}\\
|
|
\hline
|
|
\multirow{2}*{\ccintro{sa_family_t}} & address family of socket address structure,& \multirow{2}*{\code{sys/socket.h}} \\
|
|
& normally \cc{uint16_t}& \\
|
|
\multirow{2}*{\ccintro{socklen_t}} & length of socket address structure,&\multirow{2}*{\code{sys/socket.h}} \\
|
|
& normally \cc{uint32_t} &\\
|
|
\hline
|
|
\ccintro{in_addr_t} & IPv4 address, normally \cc{uint32_t} & \code{netinet/in.h} \\
|
|
\ccintro{in_port_t} & TCP or UDP port, normally \cc{uint16_t} & \code{netinet/in.h}\\
|
|
\ccintro{struct in6_addr} & IPv6 address & \code{netinet/in.h}
|
|
|
|
\end{tabular}
|
|
\end{table}
|
|
|
|
Die Adressen der Internet-Protokollfamilie sehen wie folgt aus:
|
|
|
|
\begin{lstlisting}
|
|
struct in_addr {
|
|
in_addr_t s_addr; // 32 bit IPv4 address
|
|
}
|
|
|
|
struct sockaddr_in {
|
|
sa_family_t sin_family; // here: AF_INET
|
|
in_port_t sin_port; // 16-bit TCP / UDP port number. network byte order!
|
|
struct in_addr sin_addr; // 32 bit IPv4 address. network byte order!
|
|
char sin_zer[8]; // unused, total size 16 bytes
|
|
};
|
|
\end{lstlisting}
|
|
|
|
Das \cc{struct sockaddr_in} wird somit auf ein \cc{struct sockaddr} gecastet,
|
|
um an \cc{socket()} übergeben zu werden.
|
|
|
|
Unser socket enthält somit bereits IP-Adresse, Protokoll-Type und Portnummer,
|
|
um einen verbindungsaufbau zu ermöglichen.
|
|
|
|
\subsubsubsection{Network byte order}
|
|
|
|
Die Werte \cc{sin_port} sowie \cc{sin_addr} müssen in der sogenannten
|
|
\vocab{network byte order} abgelegt sein.
|
|
|
|
Das liegt daran, dass verschieden Systeme \vocab{Little Endian} oder \vocab{Big Endian}
|
|
benutzen können, und wir für die Netzwerkübertragung eine einheitliche Darstellung benötigen.
|
|
|
|
Die \vocab{Host Byte Order} ist die byte order des hosts.
|
|
|
|
\begin{definition}
|
|
Die \vocab{Network Byte Order} ist als Big Endian definiert.
|
|
\end{definition}
|
|
|
|
\begin{warning}
|
|
Beim spezifizieren von Netzwerk-Parametern müssen wir also auf die Endianness achten!
|
|
\end{warning}
|
|
|
|
Hierzu stehen folgende Hilfsfunktionen zur Verfügung:
|
|
|
|
\begin{lstlisting}
|
|
uint16_t htons(uint16_t hostshort);
|
|
uint32_t htonl(uint32_t hostlong);
|
|
uint16_t ntohs(uint16_t netshort);
|
|
uint32_t ntohl(uint32_t netlong);
|
|
\end{lstlisting}
|
|
|
|
Hierzu werden die Abkürzungen \emph host, \emph network, \emph short und \emph long benutzt.
|
|
|
|
\subsubsubsection{Komplettbeispiel: Öffnen eines sockets mit \ac{udp}}
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
int fd, err;
|
|
struct sockaddr_in addr;
|
|
|
|
fd = socket(AF_INET,SOCK_DGRAM,0);
|
|
if (fd<0) { ... } // error handling
|
|
|
|
addr.sin_family = AF_INET;
|
|
addr.sin_port = htons(4711);
|
|
addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
|
|
err = bind(fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in));
|
|
if (err<0) { ... } // error handling
|
|
\end{lstlisting}
|
|
|
|
\begin{remark}
|
|
Das macro \ccintro{INADDR_ANY} gibt an, dass das Betriebssystem die IP-Adresse
|
|
automatisch bestimmt, und zwar die des eigenen Rechners.
|
|
|
|
Analog kann auch die Portnummer als 0 spezifiziert werden,
|
|
dann kümmert sich das Betriebssystem automatisch darum,
|
|
einen geeigneten Port zuzuweisen. Diese ist $> 1023$.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Weitere Socket-Adress-Strukturen}
|
|
|
|
Die structs for die IPv6-Familie oder für Unix-Domain Sockets können auch
|
|
deutlich länger als 16 Byte sein. Deswegen ist die übergabe der Länge
|
|
des struct essentiell.
|
|
|
|
|
|
\subsubsubsection{Kommunikation über Sockets mit \ac{udp}}
|
|
|
|
Wir kennen bereits \code{socket()} und \code{bind()}.
|
|
Um nun zwischen Client und Server zu kommunizieren,
|
|
führt üblicherweise nur der Server ein \code{bindto()} aus,
|
|
und wartet mit \code{recvfrom()} auf eingehende Datagramme.
|
|
|
|
Mit \code{sendto()} können nun sowohl client als auch Server
|
|
Datagramme verschicken.
|
|
Auch der Client kann nun mit \code{recvfrom()} Daten empfangen.
|
|
|
|
Typischerweise befinden sich nun Client und Server in einem
|
|
Zyklus aus \vocab{Empfangen}, \vocab{Verarbeiten}, \vocab{Beantworten}.
|
|
|
|
Mit \code{close()} kann der Socket am Ende geschlossen werden.
|
|
|
|
\begin{warning}
|
|
Es ist sehr wichtig, den Socket am Ende wieder zu schließen,
|
|
sonst bleibt der Port auch über das Programmende eine
|
|
gewisse Zeit im kernelspace, weil Sockets (grundsätzlich)
|
|
kernel-persistence besitzen.
|
|
|
|
Der Kernel schließt den socket allerdings nach einer
|
|
gewissen Zeit ($\approx$ 1min.), das kann jedoch bereits
|
|
nervig sein.
|
|
\end{warning}
|
|
\todoquestion{kann ich einen bestehenden socket übernehmen?}
|
|
|
|
|
|
\subsubsubsection{\code{sendto()}}
|
|
|
|
Der Aufruf von \code{sendto()} hat die Signatur:
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
int sendto(int sockfd, const void *buff, size_t nbytes,
|
|
int flags, const struct sockaddr *to, socklen_t tolen);
|
|
|
|
// sockfd: file descriptor des sockets
|
|
// buff: zu sendender Buffer
|
|
// nbytes: Anzahl zu sendender Bytes
|
|
// flags: Optionen, spaeter behandlet
|
|
// to: Zeiger auf Zieladress-Struktur
|
|
// tolen: Laenge der Ziel-Adressstruktur
|
|
// return: -1 bei Fehler, sonst Anzahl gesendeter Bytes
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{example}[Senden von Daten]
|
|
\label{ex:senden-von-daten-udp}
|
|
\begin{lstlisting}[gobble=2]
|
|
// nimm an, dass wir den socket sockfd bereits erstellt haben
|
|
// (nicht zwingend gebinded)
|
|
|
|
char msg[64];
|
|
int err;
|
|
struckt sockaddr_in dest;
|
|
|
|
strcpy(msg, "hello, world!");
|
|
|
|
dest.sin_family = AF_INET;
|
|
dest.sin_port = htons(4711);
|
|
dest.sin_addr.s_addr = inet_addr("130.37.193.13");
|
|
|
|
err = sendto(
|
|
sockfd, msg, strlen(msg) + 1, 0,
|
|
(struct sockaddr*) &dest, sizeof(struct sockaddr_in)
|
|
);
|
|
if (err < 0) { ... } // Fehlerbehandlung
|
|
|
|
...
|
|
\end{lstlisting}
|
|
\end{example}
|
|
|
|
\begin{remark}
|
|
Man sollte auf jeden Fall den Fehlerstatus überprüfen.
|
|
Die Anzahl der gesendeten bytes sollte vor allem dann
|
|
überprüft werden, wenn man potenziell zu viele bytes
|
|
gesendet hat.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Hilfsfunktionen für \ac{ip}-Adressen}
|
|
|
|
\ac{ip}-Adressen werden zur Verbesserung der Lesbarkeit üblicherweise
|
|
als \vocab{Dotted-Decimal-Schreibweise} durch 4 Dezimalzahlen dargestellt,
|
|
die jeweist 8 bit, also Zahlen von $0$ bis $255$.
|
|
|
|
Es stehen zwei Hilfsfunktionen zur Verfügung:
|
|
|
|
\begin{lstlisting}
|
|
#include <arpa/inet.h>
|
|
|
|
in_addr_t inet_addr(const char *dotted); // Conversion of dotted address to in_addr_t
|
|
char *inet_ntoa(struct in_addr network); // Conversion of in_addr_t to dotted
|
|
\end{lstlisting}
|
|
|
|
\begin{warning}
|
|
Der Aufruf von \code{inet_ntoa} returned die Adresse in einem
|
|
statisch allokierten Buffer, der bei weiteren Aufrufen überschrieben wird.
|
|
|
|
Damit ist der Aufruf insbesondere \alert{nicht} thread-safe.
|
|
\end{warning}
|
|
|
|
\begin{remark}[Der Vollständigkeit halber]
|
|
Es gibt auch \code{inet_aton(const char *cp, struct in_addr *inp)},
|
|
die ebenfalls von der dotted schreibweise konvertiert,
|
|
und das Ergebnis im entsprechenden struct ablegt.
|
|
|
|
Man hätte also in \autoref{ex:senden-von-daten-udp} auch folgendes schreiben
|
|
können:
|
|
|
|
\begin{lstlisting}
|
|
// dest.sin_addr.s_addr = inet_addr("130.37.193.13");
|
|
char dest_addr[15] = "130.37.193.13";
|
|
inet_aton( &dest_addr, &dest.sin_addr );
|
|
\end{lstlisting}
|
|
\end{remark}
|
|
|
|
|
|
\subsubsubsection{\code{recvfrom()} - Empfangen von Daten}
|
|
|
|
\begin{lstlisting}
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
int recvfrom(int sockfd, void *buff, size_t nbytes,
|
|
int flags, struct sockaddr *from, socklen_t *fromlen);
|
|
|
|
// sockfd: File Deskriptor des sockets
|
|
// buff: buffer fuer zu empfangende daten
|
|
// nbytes: laenge des buffers
|
|
// flags: optionen, standard 0 (weiter spaeter)
|
|
// from: zeiger auf socket-adress-strucktur mit quelladresse (port + ip-adresse)
|
|
// fromlen: Zeiger auf lange der adress-struktur.
|
|
|
|
//return: -1 bei fehler, sonst laenge der empfangenen daten
|
|
|
|
// das argument fromlen ist ein value-return argument, d.h.
|
|
// es wird bei erfolgreichem funktionsaufruf geupdatet,
|
|
// und zwar auf die tatsaechliche laenge der
|
|
// struct sockaddr *from struktur
|
|
// war diese laenger als die urspruengliche eingabe von fromlen
|
|
// wird die from - addresse abgeschnitten,
|
|
// und fromlen auf den entsprechend groesseren wert
|
|
// gesetzt
|
|
\end{lstlisting}
|
|
|
|
\begin{remark}
|
|
Mit Quelle ist hierbei nicht gemeint, dass man \emph{nur} Datagramme
|
|
von dieser Quelle akzeptiert.
|
|
Diese ist unspezifiziert, die Quelladresse wird jedoch
|
|
im \code{struct sockaddr *from} abgelegt.
|
|
|
|
Der Aufruf von \code{recvfrom()} blockiert (im Normalfall),
|
|
bis Daten empfangen wurden.
|
|
|
|
Man kann \code{from} und \code{fromlen} auch auf \code{NULL} setzen,
|
|
dann wird die Sender-Adressstruktur nicht gespeichert.
|
|
Stattdessen kann man auch einfach \code{recv()} aufrufen,
|
|
das die beiden argumente nicht erwartet.
|
|
\end{remark}
|
|
|
|
\begin{example}[Beispiel von \code{recvfrom()}]
|
|
\begin{lstlisting}[gobble=4]
|
|
char msg[64];
|
|
int len, flen;
|
|
struck sockaddr_in from;
|
|
|
|
flen = sizeof(struct sockaddr_in);
|
|
|
|
len = recvfrom(
|
|
sockfd, msg, sizeof(msg), 0,
|
|
(struckt sockaddr*) &from, &flen);
|
|
|
|
if (len < 0) { ... } // fehlerbehandlung
|
|
|
|
printf("Received %d bytes from host %s with port %d: %s",
|
|
len, inot_ntoa(from.sin_addr), ntohs(from.sin_port), msg);
|
|
|
|
\end{lstlisting}
|
|
\end{example}
|
|
|
|
\begin{warning}
|
|
Das obige Beispiel ist natürlich Sicherheitstechnisch völlig grauenhaft.
|
|
Wenn wir nicht gerade einen string bekommen haben, könnte das
|
|
printen mittels '\%s' der erhaltenen nachricht unseren gesamten speicher
|
|
leaken / segfaulten / \ldots
|
|
\end{warning}
|
|
|
|
\begin{warning}
|
|
Die manpage behauptet, dass überschüssige Daten eines Datagrammes,
|
|
das zu lange für den angegebenen Buffer ist, je nach sockettyp
|
|
verworfen werden können.
|
|
|
|
In der Vorlesung wurden 2 Euro verwettet, weil fälschlicherweise
|
|
geclaime wurde,
|
|
dass dies bei UDP nicht der Fall ist,
|
|
mit einem weiteren \code{recvfrom()} kann dann der Rest der Nachricht
|
|
abgerufen werden.
|
|
|
|
UDP verliert überschüssige Daten.
|
|
\end{warning}
|
|
|
|
|
|
\subsubsubsection{Schließen eines sockets}
|
|
|
|
Funktioniert gleich wie bei files:
|
|
|
|
\begin{lstlisting}
|
|
#include <unistd.h>
|
|
|
|
int close(int sockfd);
|
|
\end{lstlisting}
|
|
|
|
\subsection{Kommunikation über Sockets mit \ac{tcp}}
|
|
|
|
Da \ac{tcp} ein Verbindungsorientiertes Protokoll ist,
|
|
sieht die Kommunikation etwas anderst aus.
|
|
|
|
Für einen Server stehen \code{listen()} und \code{accept()}
|
|
zur Verfügung, um auf eingehende Verbindungswünsche zu hören,
|
|
und sie zu akzeptieren.
|
|
|
|
Der Client verwendet \code{connect()}, um eine Verbindung aufzubauen.
|
|
|
|
Besteht die Verbindung, so stehen \code{read()} und \code{write()}
|
|
auf beiden Seiten zur Verfügung.
|
|
|
|
Mit \code{close()} schließt der Client die Verbindung,
|
|
dies kann beim server beim nächsten \code{read()} erkannt werden.
|
|
|
|
\todoimg{3.2 - 70}
|
|
|
|
\subsubsubsection{Servervorbereitung bei \ac{tcp}}
|
|
|
|
Nachdem ein Socket mit \code{socket()} erstellt und mit \code{bind()}
|
|
gebinded wurde, versetzt man ihn mit \code{listen()} in einen aktiven
|
|
Zustand, d.h.~dieser wartet auf Verbindungsaufbauten
|
|
|
|
\begin{lstlisting}
|
|
#include <sys/socket.h>
|
|
|
|
int listen(int sockfd, int backlog);
|
|
|
|
// sockfd: socket file deskriptor
|
|
// backlog: maximale anzahl gleicher verbindungsaufbauten. typisch: 5
|
|
|
|
//return: -1 bei fehler, 0 bei erfolg
|
|
\end{lstlisting}
|
|
|
|
\begin{remark}
|
|
Der Parameter \code{backlog()} beschränkt nicht die Anzahl der möglichen
|
|
Verbindungen, nur die Anzahl derjenigen, die
|
|
eine Verbindung aufbauen, aber noch nicht akzeptiert wurden.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Clientvorbereitung bei \ac{tcp}}
|
|
|
|
Nachdem der client mit \code{socket()} einen socket erstellt hat,
|
|
kann er mit \code{connect()} einen Verbindungsaufbau beginnen.
|
|
|
|
\begin{lstlisting}
|
|
#include <sys/types.h>
|
|
#incldue <sys/socket.h>
|
|
|
|
int connect(int sockfd. const struct sockaddr *serv_addr, socklen_t addrlen);
|
|
|
|
// sockfd: sockt file deskriptor
|
|
// serv_addr: zeiger auf adresse des servers (die enthaelt ip + port)
|
|
// addrlen: leange der adress struktur
|
|
|
|
// return: -1 bei fehler, 1 bei erfolg
|
|
\end{lstlisting}
|
|
|
|
|
|
\subsubsubsection{Server: Akzeptieren von Verbindungen bei \ac{tcp}}
|
|
|
|
Hierzu steht \code{accept()} zur Verfügung.
|
|
|
|
\begin{lstlisting}
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
|
|
|
|
// sockfd: socket file deskriptor
|
|
// addr: zeiger auf adress-struktur
|
|
// addrlen: laenge der adress-struktur
|
|
|
|
// return: *neuer* socket-deskriptor, der die verbindung darstellt,
|
|
// -1 bei Fehler
|
|
\end{lstlisting}
|
|
|
|
Auch hier wird \code{addrlen} als value-return dynamisch angepasst.
|
|
|
|
\begin{example}
|
|
\begin{lstlisting}[gobble=4]
|
|
int sockfd, newsock, res;
|
|
sockaddr_in client_addr;
|
|
socklen_t addrlen;
|
|
|
|
res = listen(sockfd,5);
|
|
if (res<0) { ... } // fehlerbehandlung
|
|
|
|
addrlen = sizeof(struct sockaddr_in);
|
|
newsock = accept(sockfd,(struct sockaddr *) &client_addr, &addrlen);
|
|
|
|
if (newsock < 0) { ... }
|
|
else {
|
|
printf("Received connection from %s!\n", inet_ntoa(client_addr.sin_addr);
|
|
}
|
|
\end{lstlisting}
|
|
\end{example}
|
|
|
|
\subsubsubsection{Schwebezustand im Verbindungsaufbau}
|
|
\todoimg{3.2 - s.76}
|
|
|
|
Führt ein Client \code{connect()} aus, so findet ein sog. 3-Wege-Handshake
|
|
statt.
|
|
Nach dem 2. Handshake kehrt \code{connect()} zurück.
|
|
Nach dem 3. Handshake kehrt ein evtl. vorhandener \code{accept()}
|
|
Aufruf unmittelbar zurück.
|
|
|
|
\begin{remark}
|
|
Wir werden später betrachten, wie ein Server mit mehreren
|
|
Verbindungen umgehen kann.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Senden von Daten bei \ac{tcp}}
|
|
|
|
Der Aufruf ist ähnlich wie der von \code{sendto()} im Falle von \ac{udp}.
|
|
|
|
\begin{lstlisting}
|
|
#include <unistd.h>
|
|
|
|
ssize_t write(int sockfd, const void *buff, size_t count);
|
|
|
|
// sockfd: File deskriptor des sockets
|
|
// buff: zu sendender buffer
|
|
// count: laenge der zu sendenden daten
|
|
|
|
// return: -1 bei fehler, sonst anzahl gesendeter bytes
|
|
\end{lstlisting}
|
|
|
|
\begin{remark}
|
|
Die Anzahl der gesendeten bytes kann durchaus weniger als \code{count} sein.
|
|
|
|
Mas sollte deswegen immer den Rückgabewert prüfen und ggfs.~restliche Daten
|
|
erneut senden.
|
|
\end{remark}
|
|
|
|
|
|
\subsubsubsection{Empfangen von Daten bei \ac{tcp}}
|
|
Hierzu gibt es \code{read()}
|
|
|
|
\begin{lstlisting}[gobble=2]
|
|
#include <unistd.h>
|
|
|
|
ssize_t read(int sockfd, void *buff, size_t count);
|
|
|
|
// sockfd: file deskriptor des sockets
|
|
// buff: buffer, in den gelesen wird
|
|
// count: groesse des buffers
|
|
|
|
// return: -1 bei felher
|
|
// 0: sonderfall: kennzeichnung von end of file
|
|
// >0 anzahl der empfangenen bytes
|
|
\end{lstlisting}
|
|
|
|
|
|
\begin{remark}
|
|
Der Rückgabewert von \code{read()} kann natürlich kleiner als \code{count()} sein.
|
|
|
|
Der return value $0$ für EOF signalisiert den Wunsch auf Verbindungsabbau,
|
|
d.h.~der Kommunikationspartner hat \code{close()} aufgerufen.
|
|
\end{remark}
|
|
|
|
|
|
\subsubsubsection{Aufruf von \code{close()}}
|
|
|
|
\code{close()} \vocab{markiert} einen Socket zum schließen.
|
|
Erst wenn alle existierenden file-deskriptoren des sockets
|
|
geschlossen wurden, schließt das Betriebssystem diesen tatsächlich.
|
|
|
|
Bereits an den socket übergebene, aber von \ac{tcp} noch nicht
|
|
versendete Daten gehen beim schließen \emph{nicht} verloren.
|
|
|
|
\begin{remark}
|
|
Es ist möglich, dass ein Prozess mit bestehender \ac{tcp}-Verbindung
|
|
\code{fork()} aufruft, und der parent die verbindung schließt.
|
|
Das child kann weiterhin die Verbindung nutzen.
|
|
\end{remark}
|
|
|
|
\subsubsubsection{Alternative zu \code{close()}: \code{shutdown()}}
|
|
|
|
Mit \code{shutdown()} kann man einseitig eine Verbindung schließen.
|
|
|
|
\begin{lstlisting}
|
|
#include <sys/socket.h>
|
|
|
|
int shutdown(int sockfd, int howto);
|
|
\end{lstlisting}
|
|
|
|
\code{howto} ist hierbei eines von \code{SHUT_RD}, \code{SHUT_WR} oder \code{SHUT_RDWR},
|
|
die kanonisch nur read, nur write, oder beide Teile der Verbindung schließen können.
|
|
|
|
|
|
\subsubsection{Sockets in Rust}
|
|
|
|
Wir geben einen groben Überblick über sockets in Rust,
|
|
um die Analogien auch über Programmiersprachen hinweg zu geben.
|
|
|
|
|
|
\subsubsection{Übersicht der Adressen bei \ac{udp}}
|
|
|
|
\todoimg{3.2 - 93, 94}
|
|
|
|
\subsubsection{Übersicht der Adressen bei \ac{tcp}}
|
|
|
|
\todoimg{3.2 - 95, 96}
|
|
|
|
\subsubsection{Hilfsfunktionen bei \ac{tcp} und \ac{udp}}
|
|
|
|
\ac{udp} und \ac{tcp} bestimmen die Adressen in der Regel automatisch,
|
|
dies umfasst
|
|
|
|
\begin{itemize}
|
|
\item Portnummer beim Client
|
|
\item IP-Adresse beim Client
|
|
\item IP-Adresse beim Server
|
|
\item ggfs.~Portnummer beim SEvrer
|
|
\end{itemize}
|
|
|
|
Die konkreten Adressen kann man mit \code{getsockname()} und \code{recvmsg()}
|
|
selbst auslesen.
|
|
|
|
Dies sollen hier jedoch nicht näher betrachtet werden.
|