|
|
Rahmenprogramm eines Client-Server Paars
Ein Server beantwortet in einer Endlosschleife Clientanfragen.
Bevor er in diese Endlosschleife geht, muss er seinen Dienst anmelden.
Er blockiert erstmals bei
[tcp-Server] #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <netdb.h>
#define MAXPUF 1023
main() { int IDMySocket, IDPartnerSocket; struct sockaddr_in AdrMySock, AdrPartnerSocket; struct servent *Service; int AdrLen;
char Puffer[MAXPUF]; int MsgLen;
IDMySocket = socket(AF_INET, SOCK_STREAM, 0); /* Socket an Port-Nummer binden */ AdrMySock.sin_family = AF_INET; AdrMySock.sin_addr.s_addr = INADDR_ANY; /* akzept. jeden */
/* Bestimme Port */ Service = getservbyname("hilfe","tcp"); AdrMySock.sin_port = Service->s_port;
bind(IDMySocket, &AdrMySock, sizeof(AdrMySock)); listen(IDMySocket, 5); do { IDPartnerSocket = accept(IDMySocket, &AdrPartnerSocket, &AdrLen); MsgLen = recv(IDPartnerSocket, Puffer, MAXPUF, 0);
/* tu was mit den Daten */
send(IDPartnerSocket, Puffer, MsgLen, 0); close(IDPartnerSocket); } while(1); /* bis zum St. Nimmerlein */ }
Dieser Server bearbeitet nacheinander jede Anfrage, die über den Port »hilfe«
an ihn gestellt wird. Nach jeder Anfrage wird die Verbindung wieder gelöst
und ein anderer Client kann anfragen.
Ein solcher Server dürfte auf jedem Betriebssystem arbeiten, das TCP/IP
unterstützt. Lediglich der Aufruf von
Der zugehörige Client bereitet die Verbindung in der Variablen AdrSocket
vor und ruft damit die Funktion
[tcp-Client] #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <netdb.h>
#define MAXPUF 1023
main() { int IDSocket; struct sockaddr_in AdrSock; int len; /* Die Laenge der Socketstruktur */
struct hostent *RechnerID; struct servent *Service; char Puffer[MAXPUF];
IDSocket = socket(AF_INET, SOCK_STREAM, 0);
/* Bestimme den Zielrechner */ RechnerID = gethostbyname("server"); bcopy(RechnerID->h_addr, &AdrSock.sin_addr, RechnerID->h_length);
/* Bestimme den Port */ Service = getservbyname("hilfe","tcp"); AdrSock.sin_port = Service->s_port;
connect(IDSocket, (struct sockaddr *)&AdrSock, sizeof(AdrSock));
send(IDSocket, Puffer, MAXPUF, 0); recv(IDSocket, Puffer, MAXPUF, 0); close(IDSocket); }
Es gibt zwei Variablen pro Socket. Die eine ist wie bei Dateizugriffen ein
einfaches Handle (hier mit ID gekennzeichnet), die andere enthält die Adresse
der Verbindung, also die Internet-Nummer des Rechners und die Nummer des
Ports. Der Server legt die IP-Nummer des Rechners nicht fest, von dem er
Anfragen akzeptiert. Das erreicht er, indem er die Konstante
INADDR_ANY benutzt wird. Der Client dagegen gibt die Adresse des
anzusprechenden Servers an. Die Funktion
Parallelität
Der Server wird nun ergänzt, damit er die Vorteile einer Multitaskingumgebung
nutzen und mehrere Anfragen parallel abarbeiten kann.
Dazu muss an passender Stelle ein
[Multitasking Server] do { IDPartnerSocket = accept(IDMySocket, &AdrPartnerSocket, &len); if (fork()==0) { MsgLen = recv(IDPartnerSocket, Puffer, MAXPUF, 0);
/* tu was mit den Daten */
send(IDPartnerSocket, Puffer, MsgLen, 0); close(IDPartnerSocket); /* Sohn toetet sich selbst */ exit(0); } /* if fork.. */ close(IDPartnerSocket); /* der Vater schliesst Verbindung */ } while(1);
Man sieht, mit welch geringer Änderung ein multitaskingfähiger Server zu
realisieren ist. Beim Aufruf von
Der Server ist so, wie er nun vorliegt, ein statusloser Server (stateless
server). Das bedeutet, er
kann sich den Stand einer Kommunikation nicht merken. Fragt derselbe Client
noch einmal an, wird er ihn wie eine völlig neue Anfrage behandeln.
In dieser Art arbeitet ein Webserver. Jede Anfrage ist für ihn neu.
Andere Server, beispielsweise POP3-Server, halten die Verbindung mit ihrem
Client solange aufrecht, bis beide ein Ende der Verbindung vereinbaren.
In solch einem Fall würde eine Schleife im Sohnprozess über Wie im Zusammenhang mit Signalen gezeigt, sollte die Entstehung von Zombies verhindert werden. Zombies entstehen, wenn ein Sohn endet, aber der Vater nicht auf ihn wartet. Dadurch bleibt ein Eintrag in der Prozesstabelle mit dem Exitwert des Sohnes. Der Aufwand ist denkbar gering:
signal(SIGCLD, SIG_IGN); Diese Zeile sollte dem Server eingebaut werden, bevor er in die Endlosschleife läuft.
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|