PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C++] Socketklasse


Zahl
09.07.07, 02:51:26
Hier die Zahlsche Socketklasse, kann TCP über IPv4 :p

Supported ausgehende Connections, lauschende Sockets, so viele
Verbindungen gleichzeitig wie man lustig ist uvm.
Ein kleines Beispiel is bei was die Google Hauptseite requested, wenn
ich Zeit hab mach ich mehr Beispiele.

blue
16.07.07, 22:00:53
Klasse für TCP/IPv4 Server.

/* ################################
* ## TCP Server Klasse fuer IPv4 ##
* ################################
* */

#ifndef TCPSERVER_H_
#define TCPSERVER_H_

#define STRING_BUF 2048
#define socket_t SOCKET

/* Headerfiles */
#include <iostream>
#include <errno.h>
//#include "charArray.h"
/* MS Windows */
#ifdef _WIN32
#include <winsock.h>
#include <windows.h>
#else
/* Unix/Linux */

#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#endif

using namespace std;

class TcpServer {
private:
#ifdef _WIN32
socket_t sock1, sock2, sock3;
//WORD wVersionRequested;
WSADATA wsaData;

bool initWinsock(void);
#else
int sock1, sock2, sock3; // Socket-Descrtiptor
#endif

char* buffer;
int i, ready, sock_max, max;
int client_sock[FD_SETSIZE];
fd_set socks_all, socks_read;
struct sockaddr_in server;
struct sockaddr_in client;
struct timeval tv;
int domain;
int type;
int protocol;
int port;
char *ip;
hostent* host;

void error_exit(char *_errorMessage);
socket_t s_init(void);
bool s_bind(void);
bool s_listen(void);
void s_accept(void);
void s_close(socket_t);

public:
TcpServer(int);
~TcpServer(void);

bool startServer(void);
bool s_receive(char*, int);

};



/* ############
* ## PUBLIC ##
* ############
* */

TcpServer::TcpServer(int _port) {
#ifdef _WIN32
if(!this->initWinsock())
this->error_exit("Error: InitWinsock()");
#endif
this->domain = AF_INET;
this->type = SOCK_STREAM;
this->protocol = IPPROTO_TCP;
this->port = _port;
this->buffer = new char[STRING_BUF];
memset(this->buffer, 0, (STRING_BUF-1));
this->tv.tv_sec = 5;
this->tv.tv_usec = 0;
}

TcpServer::~TcpServer(void) {
#ifdef _WIN32
WSACleanup();
#endif
}

bool TcpServer::startServer(void) {
cout << "Starting up server ...\n";
this->max = -1;
/* Main Socket erstellen */
cout << "Setting up host ...\n";
this->sock_max = this->sock1 = this->s_init();
/* Port binden */
cout << "Main Socket: " << this->sock1 << "\nBinding port ...\n";
if(!this->s_bind())
return false;
/* Clients erlauben */
cout << "Setting up listening mode ...\n";
if(!this->s_listen())
return false;

/* Clients Initialisieren */
for(this->i=0; i<FD_SETSIZE; i++) {
this->client_sock[i] = -1;
}

/* FD Sets nullen */
FD_ZERO(&this->socks_all);
/* Main Socket ins FD Set aufnehmen */
FD_SET(this->sock1, &this->socks_all);

/* Jetzt gehts ab. Hier ist die endlos-Server-Schleife */
cout << "Let's go ...\n";
for(;;) {
this->socks_read = this->socks_all;

/* Auf neue Clients warten */
cout << "Waiting for new clients ...\n";
//this->ready = select(this->sock_max+1, &this->socks_read, NULL, NULL, &this->tv);
this->ready = select(this->sock_max+1, &this->socks_read, NULL, NULL, NULL);
/* Ist ein neue Client in der Warteschleife? */
if(FD_ISSET(this->sock1, &this->socks_read)) {
/* Akzeptiere diesen Client und speicher Socket Deskriptor in this->sock2 */
cout << "Accepting new client ...\n";
this->s_accept();

/* Neuen Client im Client-Set speichern. Vorher einen freihen Platz suchen. */
for(i=0; i<FD_SETSIZE; i++) {
if(this->client_sock[i] < 0) {
cout << "Saving new Client ...\n";
this->client_sock[i] = this->sock2;
break;
}
}
/* Wenn der Server bereits C<bervoll ist und keine Clients mehr erlaubt */
if(i == FD_SETSIZE) {
cout << "No more space to save client\n";
}
/* Den neuen Descriptor zu gesamt Menge hinzufC<gen */
FD_SET(this->sock2, &this->socks_all);

/* Die hC6chste Descriptor Nummer ermitteln */
if(this->sock2 > this->sock_max)
this->sock_max = this->sock2;
/* HC6chster Index */
if(this->i>this->max)
this->max = this->i;
/* Sind weitere Deskriptoren zum lesen bereit? */
this->ready--;
if(this->ready >= 0)
continue;
} // if(FD_ISSET(...

/* Nun werden alle eingehC$ngten Discreptoren auf Nachrichten C<berprC<ft.
* Besteht eine, so wird sie erstmal auf dem Bidlschirm ausgegeben. */
for(this->i=0; this->i<=this->max; this->i++) {
cout << "Checking for messages ...\n";
getchar();
/* Kein Descriptor */
if((this->sock3 = this->client_sock[i] < 0))
continue;
/* Descriptor */
if(FD_ISSET(this->sock3, &this->socks_read)) {
if(!s_receive(this->buffer, (STRING_BUF-1)))
cout << "Could not get message.";
cout << "New message: " << this->buffer << endl;

/* "quit" Message erhalten */
if(strcmp(this->buffer, "quit\n") == 0) {
s_close(this->sock3); // hieraus noch eine Methode der Klasse machen!!
/* Socket aus der Menge lC6schen */
FD_CLR(this->sock3, &this->socks_all);
/* Platz wieder freigeben fC<r weitere Clients */
this->client_sock[i] = -1;
cout << "BYE client." << endl;
} // if(strcmp(this->buffer, "quit\n") == 0)
/* Weitere Discreptoren zum lesen vorhadnen? */
this->ready--;
if((this->ready) <= 0)
break; // Noe, keine mehr da
} // if(FD_ISSET(sock3, &socks_read))
} // for(i=0; i<=max; i++)
} // for(;;)
}

/* Empfange Daten von sock3 und schreibe die Daten in _buffer.
* Der Returnwert gibt MiC-/Erfolg an.*/
bool TcpServer::s_receive(char *_buffer, int _buffer_size) {
memset(_buffer, 0, _buffer_size);
int len;
if(len = recv(this->sock3, _buffer, _buffer_size, 0) == -1) {
return false;
}
else {
_buffer[len] = '\r\n';
return true;
}
}

/* #############
* ## Private ##
* #############
* */

#ifdef _WIN32
bool TcpServer::initWinsock(void) {
//this->wVersionRequested = MAKEWORD(2,2);

if(WSAStartup(MAKEWORD(2,2), &wsaData)!= NO_ERROR)
return false;
else
return true;
}
#endif

void TcpServer::error_exit(char *_errorMessage) {
#ifdef _WIN32
cout << _errorMessage << "\n" << WSAGetLastError() << endl;
WSACleanup();
#else
cout << _errorMessage << "\n" << strerror(errno) << endl;
#endif

exit(EXIT_FAILURE);
}

socket_t TcpServer::s_init(void) {

/* socket erzeugen.
* AnschlieCend fertig initialisiertes Socket an Funktion zurC<ckgeben */

socket_t sock;

const int y = 1;

/* Socket erzeugen */
sock = socket(
this->domain, /* Adressfamilie */
this->type, /* Socket Typ: SOCK_STREAM = TCP; SOCK_DGRAM = UDP */
this->protocol); /* Cbertragungsprotokoll: SOCK_STREAM = TCP; SOCK_DGRAM = UDP */

if(sock < 0) {
/* Ein Fehler ist aufgetreten. */
this->error_exit("Fehler im initialisieren des Sockets");
return 0;
}

/* Socket so einstellen, dass mehrere Prozesse einen
* Port nutzen kC6nnen. (Dadurch umgeht man den Timeout
* von drei Minuten) */
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&y, sizeof(int));

/* */

/* Erzeugtes Socket zurueckgeben */
return sock;
}

bool TcpServer::s_bind(void) {

memset(&this->server, 0, sizeof(server));

this->host = gethostbyname("");
this->ip = inet_ntoa(*(struct in_addr *)*this->host->h_addr_list);
this->server.sin_family = this->domain;
//this->server.sin_addr.s_addr = htonl(this->type);
this->server.sin_addr.s_addr = inet_addr(this->ip);
this->server.sin_port = htons(this->port);

cout << "Port: " << this->port << endl;

if(bind(this->sock1, (struct sockaddr*)&this->server, sizeof(this->server)) == -1) {
/* Fehler! */
this->s_close(this->sock1);
this->error_exit("Could not bind the port.");
return false;
}
else
return true;
}

bool TcpServer::s_listen(void) {

if(listen(this->sock1, 5) < 0) {
error_exit("Listening error.");
return false;
}
return
true;
}

void TcpServer::s_accept(void) {

int len = sizeof(client);
#ifdef _WIN32
this->sock2 = accept(this->sock1, (struct sockaddr*)&client, &len);
#else
this->sock2 = accept(this->sock1, (struct sockaddr*)&client, (socklen_t*)&len);
#endif
if(this->sock2 == -1)
error_exit("Could not accept new client.");
}

void TcpServer::s_close(socket_t _sock) {
#ifdef _WIN32
closesocket(_sock);
#else
close(_sock);
#endif
}
#endif


Hier Beispielcode um die Klasse zu verwenden:

#include "tcpServer.h"

int main() {
TcpServer ts(12345);
ts.startServer();
}

Zahl
16.07.07, 22:23:08
OK, habs jetzt (noch) nicht weiter getestet aber einige Punkte:
- sock1, 2 und 3 sind mir schleierhaft... Zu Anfang würd ich einfach
alle Clients in einer verketteten Liste speichern.
- Warum max und sock_max? Ein max für den größten Descriptor reicht,
den haust du ins select. Danach erst den Listener checken und dann mit ner
for-Schleife die clients checken, und wenn ready == 0 is raus gehn.
Tipp zur receive Funktion: Check die Rückgabe noch auf == 0, dann hat
der Client die verbindung getrennt und du kannst den Sock schließen
und aus deiner Liste nehmen.

Ich find das in der Tat ziemlich kompliziert wie du das gelöst hast, aber kann
jetzt so keinen Fehler sehen. Ich denk mal einfach irgendwo wird ein max
nicht richtig gesetzt, eine Schleife bricht zu früh ab, ein Array stimmt nicht..
Wer weiß :-D
Btw. warum sind alle möglichen Vars direkt in der Klasse definiert obwohl sie
auch gut direkt in die Funktionen gekonnt hätten?

blue
18.07.07, 13:38:43
Ich muss die Klasse eh nochmal gut überarbeiten, glaube aber, ich weiß nun wo der Fehler liegt. Und zwar hab ich ein Array an maximalen Sockets pro Programm erstellt:

int client_sock[FD_SETSIZE];

Die Konstante gibt die OS spezifische Anzahl an Sockets an. Da unter Windows die Sockets aber nicht vom Typ Integer sind, klappt das mit dem Speichern der Discreptoren nicht.
(Die Discreptoren sind unter Windows wesentlich höher angelegt, ich denke ein 64-Bit Integer könnte Abhilfe schaffen - aber wozu gibt es den Typ SOCKET ;)).

Naja, ich werde mich später noch etwas mit dem Code beschäftigen.

Zahl
18.07.07, 14:38:57
Afaik ist ein SOCKET nur ein int... Sollte man ja mit sizeof checken können ;)

blue
18.07.07, 14:46:22
Afaik ist ein SOCKET nur ein int... Sollte man ja mit sizeof checken können ;)

Wohl wahr! Nur M$ muss natürlich wieder einen eigenen Pseudo-Typen dafür einführen ~.~