connect

Wie sieht's aus?

#include <sys/types.h>
#include <sys/socket.h>

int connect(int s, const struct sockaddr *name, socklen_t namelen);
    

Was tut's?

Mit connect() verbindet man einen Socket mit einem Ziel. Dieses Ziel wird in unserem Fall (PF_INET) zumeist ein Server sein, der auf einem TCP-Port oder UDP-Port lauscht. Auch wenn UDP keine Verbindungen in dem Sinne kennt, hat connect() dennoch einen Einfluss: nach erfolgreichem Aufruf kann man mit send() und recv() auf dem Socket operieren, und ist nicht mehr auf sendto() und recvfrom() angewiesen. Der Socket wird dadurch natürlich gewissermaßen unflexibler.

Was machen die Parameter?

Der erste Parameter gibt einen mit socket() erzeugten Socket an, auf den die Operation durchgeführt werden soll.

Der zweite Parameter gibt eine Adresse an, mit der der Socket verbunden werden soll. Es heißt hier allgemeiner name, weil connect auch auf Sockets der Domain PF_UNIX operieren kann, das soll uns hier nicht weiter stören. Der Typ ist eine struct sockaddr{}, eine generische Datenstruktur, die eine Adresse aufnehmen kann. In unserem Fall (PF_INET) ist dies sockaddr_in{}, welche so aussieht:

#include <sys/socket.h>
#include <netinet/in.h>

struct sockaddr_in {
    short               sin_family;  /* AF_INET     */
    u_short             sin_port;    /* Port-Nummer */
    struct in_addr      sin_addr;    /* IP-Adresse  */
    char                sin_zero[8]  /* Platzhalter um auf sizeof(sockaddr) zu kommen */
};
    

Das Präfix sin_ dient als Erinnerung, daß man eine struct sockaddr_in{} am Wickel hat (manchmal eine günstige Erinnerung, wenn man auf einmal nur noch eine Veriable namens dingens hat :-)), die Feldernamen sind sprechend: sin_family nimmt die Adress-Familie auf (AF_INET), sin_port die Portnummer des Zielservices, und sin_addr die IP-Adresse.

ACHTUNG!
Es wäre ja zu einfach, wenn hier nicht ein böser Fallstrick versteckt wäre. Alle ganzzahligen Werte die mehr als ein Byte umfassen (als multi-byte-values bekannt) haben die sonderbare Eigenschaft auf zwei verschiedene Weisen angeordnet werden zu können, Little Endian und Big Endian. Eine genauere Erklärung findet sich bei htons(). Hier soll uns erstmal nur interessieren, daß die Port-Nummer vorher mit htons() umgewandelt werden muß, bevor man sie einsetzt. Umgekehrt macht man sie mit ntohs() wieder "lesbar".

Der dritte Parameter gibt an, wie groß die Struktur des zweiten Parameters ist. Daran erkennt der Kernel, was man nun von ihm will. Hier setzt man in der Regel sizeof ein.

Wie verwende ich es?

int s;
struct sockaddr_in addr;
...                             /* s = socket (...); */
addr.sin_addr.s_addr = ...      /* z.B. inet_addr("127.0.0.1"); */
addr.sin_port = ...             /* z.B. htons(80);              */
addr.sin_family = AF_INET;

if (connect(s, (struct sockaddr*) &addr, sizeof(addr)) == -1)
{
    perror("connect() failed");
    return 2;
}
    

Der Cast auf struct sockaddr{}* ist notwendig, weil der Prototyp auf diesen generischen Datentyp abgestimmt ist. Es mag auf den ersten Blick ohne gehen, aber spätestens wenn man die Warnings hochdreht wird der Compiler schimpfen (und das mit Recht).

Ein Aufruf von connect() auf einen blockierenden Socket kann je nach Betriebssystem und sogar abhängig vom System des Zielhosts verschieden lang brauchen. Beim Einsatz blockierender Sockets kann dies zu unerwünschten Verzögerungen führen. Um den Aufruf nach einer definierten Wartezeit abbrechen zu können muß man zu select() greifen.