Serverprogrammierung mit Sockets in C
Vorwort
Jeder, der mit Computern zutun hat, wird irgendwann mit dem Thema Netzwerk bzw. Internet zusammen stoßen müssen. Wenn sich bei einigen Usern, auch noch Fragen über Struktur, Aufbau und Funktionsweise eines Netzwerks bilden werden Begriffe wie Client-Server Architektur fallen. Nun und genau um dieses Thema geht es in diesem Tutorial, die Programmierung von Clients und Servern.
Dieses Tutorial, soll den Entwicklern einen detaillierten Einblick in die Netzwerk Programmierung mit Sockets bitten. Socket Programmierung aus dem Grund, weil es Betriebssystem übergreifend ist. Was ich damit sagen möchte ist, dass ein Windowsprogramm auf eine sehr einfache Art und Weise zu Linuxprogramm umgeschrieben werden kann, dies bedarf meist nur die Verweisung auf andere Heade-Dateien. Nun viel Spaß.
Voraussetzung
Um einen Server, mit der Verwendung von Socket in C zu erstellen, bedarf es nicht viel; um genauer zu werden , bedarf es nur einen Computer mit einem Betriebssystem(das Sockets unterstützt) und einen Compiler(Ich verwende den BCB5, die KONSOLEN ANDWENDUNG).
Um einen Server in C zu erstellen, benötigen wir natürlich Ein- und Ausgabeoperationen. Diese Operationen(Server ein und ausgabeoperationen) stehen in der Header-Datei „Winsock.h“,die wir natürlich inkludieren müssen. Die Headerdatei ist das Herz unseres Servers, ohne Sie(die Befehle der Winsoch.h) kann man keinen Server erstellen.
Was ist ein Socket
Also ein Socket ist um es einfach zu sagen, ein Kommunikationsendpunkt. Theoretisch gesehen ist ein Socket der Endpunkt einer Verbindung. Zwei Sockets, definieren somit ein Verbindung. Ein Socket, kann durch IP und eine Portnummer eindeutig identifiziert werden. Er dient somit als Interface zwischen einem Netzwerkprotokoll wie TCP/IP und einem Anwedungsprogramm wie Telnet.Ursprünglich stammt der Socket aus Berkeley (BSD-Unix) und wurde später von Microsoft im Socket 1.1 übernommen.
Einbindung von Sockets
Die Einbindung von Sockets, findet mit dem Befehl WSAStartup() statt.
Auszug aus einer Deklaration
int WSAStartup (
WORD wVersionRequested ,
LPWSADATA lpWSAData
);
Parameters
wVersionRequested
[in] The highest version of Windows Sockets support that the caller can use. The high order byte specifies the minor version (revision) number; the low-order byte specifies the major version number.
lpWSAData
[out] A pointer to the WSADATA data structure that is to receive details of the Windows Sockets implementation.
Die Initialisierung eines Windows Sockets sollte direkt nach der Main-Funktion erfolgen
Auszug Source
void main(void)
{
WSADATA wsa;
if(WSAStartup(MAKEWORD(1,1),&wsa)){
printf("Fehler : Fehler bei der Socket einbindung");
getch();
}}
Anfordern eines Sockets
Damit wir eine Verindung aufbauen können, benötigen wir einen Socket. Der Befehl Sockt(), fodert einen solchen Socket vom Betriebssystem. Die Deklaration sieht folgendes vor :
Int socket(int domain, int type, int protocol);
Domain
Dieser Parameter gibt die Adressfamilie an. Es gibt mehrere Typen von Unterstützten Adressfamilien.
AF_UNIX
Unix domain sockets
AF_LOCAL
POSIX name for AF_UNIX
AF_INET
IP v 4
AF_IPX
Novell IPX
AF_APPLETALK
AppleTalk DDP
AF_INET6
IP v 6
AF_IRDA
IRDA sockets
AF_BLUETOOTH
Bluetooth socket
Das momentan geltende Format, dass ich verwende ist AF_INET. Dieses wird auch in diesem Tutorial verwendet. AF_INET ist für das Internet Protokol version 4, dies ist die momentan geltende Version.
Type
Der "Typ" Parameter, gibt die Übertragungsart an. Auch hierzu gibt es mehrere Möglichkeiten.
SOCK _ STREAM
Provides sequenced, reliable, two way connection based byte streams. An out-of-band data transmission mechanism may be supported.
SOCK _ DGRAM
Supports datagrams (connectionless, unreliable mes-
sages of a fixed maximum length).
SOCK _ SEQPACKET
Provides a sequenced, reliable, two-way connection-
based data transmission path for datagrams of fixed
maximum length; a consumer is required to read an
entire packet with each read system call
SOCK _ RAW
Provides raw network protocol access.
SOCK _ RDM
Provides a reliable datagram layer that does not
gurantee ordering.
SOCK _ PACKET
Obsolete and should not be used in new programs
In dieser Liste, gibt es eigentlich nur zwei für uns wichtige Übertragungsarten; das Sock_stream(Sie Übertragen einen Datenstrom), Und socke_Dgram (Übertragung von Datagrammen). Ich werde jedoch nur auf das SOCK_STREAM eingehen, weil es die Kommunikation mit dem Verbindungsorientierten TCP beschreibt.
Protocol
Hier, weist man Socket zu daß die Übertragungsart zu verwenden
Auszug Source
void main(void)
{
int s;
//SOCKET EINBINDUNG
WSADATA wsa;
if(WSAStartup(MAKEWORD(1,1),&wsa))
{
printf("Fehler : Fehler bei der Socket einbindung");
getch();
}
// SOCKET FORDERN
s= socket(AF_INET,SOCK_STREAM,0);
// SOCKETFEHLER ABFANGEN
if (s == -1){
perror("socket() failed");
return 2;}
}
Den Eigenen Port festlegen
Damit sich jemand mit unserem Server kontaktieren kann, muss er die IP- Adresse und die Portnummer kennen. Wir müssen nun unserem Betriebssystem beim Programmstart sagen, welchen Socket es mit einem bestimmten Port assoziieren(verbinden) soll. Geht ein Datenpakten ein, kann das Betriebssystem anhand der Zielport festlegen, für welchen Socket das Paket gedacht ist. Für den Zweck der Port festlegung, verwenden wir den Befehl Bind(). Die Deklaration sieht folgendes vor:
Int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
Sockfd
Mit dem Befehl socket(), haben wir uns von Betriebssystem ein Socket geholt, diesen Socket müssen wir hier rein setzen
Struct sockaddr *my_addr
Der zweite Parameter ist (wie leicht zu erkennen ist) vom Typ struct sockaddr . Dieser Variablentyp ist jedoch nur als Schablone für andere, speziellere Variablentypen gedacht. stuct sockaddr ist folgendermassen definiert:
Definition
struct sockaddr {
unsigned short sa_family /* Adressfamilie AF_xxx */
char sa_data[14] /* 14 Byte mit Protokollspezifischen Adressdaten */
};
Für unsere Sockets, geben wir hier anstatt einer struct sockaddr eine struct sockaddr_in an. Diese sieht wie folgt aus:
Definition
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
Die Werte, die man den einzelnen Membervariablen der struct sockaddr_in zuweist, hängen nun sehr stark von der , späteren Verwendung des Socket ab. Soll das Socket auf eine Verbindungen warten, so werden nur die Variablen sin_family und sin_port näher bestimmt werden. Den anderen Variablen werden Konstanten zugeordnet.
Addrlen
Ich denke, da muss man nicht viel zu sagen. Das ist die Stringlänge
Auszug Source
void main(void)
{
int s;
#define port 1234
struct sockaddr_in srv;
//SOCKET EINBINDUNG
WSADATA wsa;
if(WSAStartup(MAKEWORD(1,1),&wsa))
{
printf("Fehler : Fehler bei der Socket einbindung");
getch();
}
// SOCKET FORDERN
s= socket(AF_INET,SOCK_STREAM,0);
// SOCKETFEHLER ABFANGEN
if (s == -1)
{
perror("socket() failed");
return 2;
}
srv.sin_addr.s_addr = INADDR_ANY;
srv.sin_port = htons(port);
srv.sin_family = AF_INET;
if(bind(s,(struct sockaddr*) &srv,sizeof(srv))== -1)
{
perror("bind() failed");
} }
Verbindungen abwarten
Nun sind wir an einer Stelle angekommen, wo wir auf die Eingehenden verbindungen warten.. Der Befehl Listen() unterbricht die Verbindung sollange, bis jemand sich konntaktiert. Die Deklaration des Listen- befehls lautet wie folgt :
int listen(int s, int backlog);
Viel gibt es zur ausführung dieses Befehls nicht zu sagen. „s“ ist der Angeforderte Socket und das Attribut backlog gibt die maximale Anzahl der Verbindungen an.
Verbindung Annehmen
Wir sind an einem Punkt angekommen, wo der Server auf eine Verbindung wartet. Was soll nun passieren? Es ist eindeutig, dass wenn sich jemand mit dem Server kontaktieren möchte dieser(der Server) die Verbindung annimmt, also Akzeptiert. kurz gesagt, der Befehl Accept() nimmt die Clients, die sich Verbinden wollen aus einer Art Warteschlange.
Int accept(int s,struct sockaddr * addr,socklen_t *addrlen);
int s
Der Wert s , ist der Socket auf dem die Verbindung eingeht.
struckt sockaddr *addr
Auf diese Struktur, des Typen sockaddr_in, werden Daten wie Adresse, Port etc. des Clients abgelegt.
socklen_t *addrlen
Dieser Zeiger auf socklen_t, ist die Adresse der Variable, in die die Länge der Struktur, die die Daten enthält gespeichert wird.
Hier gibt es noch etwas sehr wichtiges zu sagen, wenn ein Fehler auftritt, wird zwar der Rückgabe wert „-1“ sein, läuft jedoch alles glatt erhalten wir einen neuen Socket über den wir die Übertragung dieser Verbindung tätigen. Was ist jetzt mit dem Alten Socket ? Ja … diesen können wir immer noch verwenden um neue Verbindungen entgegen zu nehmen.
Für einen Server, ist es an dieser Stelle sinnvoll, eine Endlosschleife zu verwenden damit dieser nicht die Verbindung bei einem Kontakt abbricht.
Ausflug zum Befehl inet_ntoa();
Eigentlich, wäre es angebracht hier diesen Befehl etwas näher zu erläutern. Jedoch bin ich der Meinung, dass dieses Tutorial den Einstieg zur Socketprogrammierung liefern soll. Jeder der „mehr“ wissen möchte muss sich auf eigener Faust durchs I-net kämpfen. Nun gut, einen kleinen Einblick muss man schon haben.
Der Befehl inet_ntoa() macht aus der IP-Adresse (32-bit Zahl) wieder eine lesbare dotted version, sprich „192.168.127.1“.
Verarbeiten der Daten
Theoretisch sind wir nun fertig und unser Server funktioniert. Um dies zu beweisen, erstellen wir uns eine Funktion, mit deren Hilfe eine Verbindung aufgebaut werden kann und der Client eine Nachricht erhält.
Senden und Empfangen von Daten
Zum senden der Daten verwenden wir den Befehl send()(Siehe Beispiel). Das Empfangen von Daten geht mit dem Befehl recv().
Fertiger Source
Fertiger Source
#include <winsock.h>
#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#define buffergr 1024
#define port 1234
int verarb(int c)
{
char buffer[buffergr] , name[buffergr];
char TEXT[1024],*user,*pass;
int bytes;
int i;
i=0;
name[0]='0x0';
user="\x0";
strcpy(TEXT,"/********************************/\n\r");
bytes = send(c,TEXT,strlen(TEXT),0);
strcpy(TEXT,"/*** www.Mein_Server_de.com**/\n\r");
bytes = send(c,TEXT,strlen(TEXT),0);
strcpy(TEXT,"/********************************/\x0");
bytes = send(c,TEXT,strlen(TEXT),0);
if(bytes == -1)
{
//return -1;
}
clrscr();
if(bytes == -1)
{
//return -1;
}
return 0;
}
//int main (int argc, char *argv[])
void main(void)
{
int s , c ;
int cli_size;
struct sockaddr_in srv, cli;
//SOCKET INICIALISIEREN
WSADATA wsa;
if(WSAStartup(MAKEWORD(1,1),&wsa))
{
printf("FEHLER");
getch();
}
printf("*****************************************\n");
printf("****Mein Kleiner Server version 1.0****\n");
printf("* Port : %i *\n",port);
s= socket(AF_INET,SOCK_STREAM,0);
if (s == -1)
{
perror("socket() failed");
//return 2;
}
srv.sin_addr.s_addr = INADDR_ANY;
srv.sin_port = htons(port);
srv.sin_family = AF_INET;
if(bind(s,(struct sockaddr*) &srv,sizeof(srv))== -1)
{
perror("bind() failed");
//return 3;
}
if(listen(s,3)== -1)
{
perror("listen() failed");
//return 4;
}
for(;;)
{
cli_size = sizeof(cli);
c = accept(s, (struct sockaddr*) &cli, &cli_size);
if(c == -1)
{
perror("accept() failed");
//return 5;
}
printf("* Verbunden mit : %s *", inet_ntoa(cli.sin_addr));
if(verarb(c)==-1)
{
fprintf(stderr,"Connection : handling() failed");
}
}
// return 0;
}
So, ich hoffe ich konnte einigen einen kleinen Einblick in die Programmierung mit Sockets erleichtern. Sollten speziefische auftauchen Postet Sie einfach ins Forum.
************************************
Marek Swierzy - Server Programmierung mit Sockets in C