C Client Server Anwendung | Client Anfragen bearbeiten

hell-student

Lieutenant
Registriert
Nov. 2007
Beiträge
671
Hallo Zusammen,

Ich bin immer noch dabei mein Projekt umzusetzen. Ich würde gerne eine Client-Server Anwendung in C implementieren. Es muss leider C sein. Das ganze soll als IPC genutzt werden, also auf einem PC laufen. Ich habe noch ein paar Fragen. Der Server wartet ja bis eine Anfrage des Clients kommt. Wie sieht es nun mit der Bearbeitung aus, da in der gleichen Zeit ja auch wieder neue Clients Anfragen stellen können. Wird das normal per Thread gemacht, also pro Clientanfrage einen Thread beim Server, der die Anfrage bearbeitet? D.h Server wartet weiter und parallel wird die Anfrage bearbeitet? Mein Problem ist, dass wenn der Client eine Anfrag stellt, die Berarbeitung (Server) beginnen soll und möglicherweise länger braucht. Sobald jedoch eine neue Anfrage des selben Clients erfolgt, soll mit der Bearbeitung der vorherigen Clientanfrage abgebrochen werden (Da neue Daten vorhanden sind und die Bearbeitung auf den alten Daten verworfen werden muss). Irgendwelche Tipps und Links?
thx
 
Du musst nicht unbedingt einen neuen Thread starten. Kannst auch forken. Wenn eine neue Verbindung von
dem selben Client kommt, schickst du dem Childprozess das Signal, dass er sich verabschieden soll.
 
@asdfmam

thx. Habe mich gerade durchs Netz gelesen und genau die gleiche Info gefunden. Ich glaub so werd ichs auch machen.
 
Üblicherweise wartet der Hauptthread im Server in einer Endlosschleife auf Anfragen und wenn eine neue kommt wird diese in einem eigenen Thread bearbeitet. Die Threads merkt man sich in einer geeigneten Datenstruktur und kann dann bei einer neuen Anfrage des selben Clients den entsprechenden Thread killen.

Prinzipiell kann man auch forken, wenn ich mich recht entsinne, würde ich das aber nicht empfehlen, da dadurch ja jedes mal ein neuer Prozess entsteht und der Prozesswechsel im Vergleich zum Threadwechsel ziemlich teuer ist. Aus diesem Grund gibt es ja auch Threads und man sollte sie nutzen.
 
Forken hat auch den Nachteil, dass man den Server ggf. DoSen kann, indem man etliche Verbindungen
öffnet, für die jedes Mal ein neuer Prozess erstellt wird. Aber ich wollte es zumindest als Möglichkeit mal
erwähnt haben. Denn fork() hat den Vorteil, dass es selbst von jemandem wie mir, der sich nicht viel
Arbeit mit Doku lesen machen will, völlig unkompliziert einsetzen lässt.
 
Das stimmt, ist auf jeden Fall leichter zu handhaben. Ich finde es aber immer gut die Nachteil dazuzunennen, damit der Fragende selbst entscheiden kann ob er das in Kauf nehmen möchte oder nicht ;)
 
Danke für eure Antworten,

Ich werde Threads verwenden, statt zu forken. Mein Problem ist momentan aber ein anderes. Ich schaffe es gerade nicht für mehrere Clients und einen Server die Interprozesskommunikation mittels Unix Domain Sockets zu implementieren. Habe schon alle möglichen Beispiele im Netz gelesen, aber eigentlich immer nur mit einem Client gleichzeitig. Hat jemand ein Link parrat oder ein gutes Buch? thx
 
Die Clients bekommen davon ja nichts mit und verhalten sich wie immer. Im Server musst du dann was in dieser Form bauen
Code:
...
while(true) {
    connectedsocket = accept(socket, ...);
    //neuer thread mit connectedsocket als parameter
}
...

Sorry, das ganze ist sehr schematisch, da ich jetzt die Syntax für die ganzen Socket- und Threadoperationen nicht vor Augen hab.

Edit: Ich hab die Codepassage angepasst, da ich gesehen hab, dass da Müll stand. Das hat wahrscheinlich zur Verwirrung beigetragen.
 
Zuletzt bearbeitet: (//neuer socket ... --> //neuer thread...)
@Freezedevil

hmm ok, also nur beim Server. Ich habs leider heute nicht hinbekommen. Falls du mal ein Link herausfinden könntest wäre es sehr nice. thx schonmal
 
Ich hab anhand des Beispiels auf der Seite http://beej.us/guide/bgipc/output/html/multipage/unixsock.html mal was zusammengehackt. Da die Threads bei dir ja nicht das Problem sind, habe ich es jetzt mal mit fork gemacht, da das, wie asdfman schon sagte, auch ohne Lesen der Doku möglich ist (also mach was ich sage nicht was ich tue ;)). Hat zur Veranschaulichung auch den Vorteil, dass ich die PID als Nachricht senden kann und man sieht, dass tatsächlich jeder Client seine eigene bearbeitende Instanz hat. Lange Rede kurzer Sinn:

server.c
Code:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "/Users/max/Desktop/sockets/mysocket"

void handle(int socket) {
    int msg = getpid();
    while(1) {
        if (send(socket, &msg, sizeof(int), 0) < 0) {
            perror("send");
        }
        sleep(2);
    }
}

int main(void)
{
    int s, s2, t, len;
    struct sockaddr_un local, remote;
    char str[100];

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    local.sun_family = AF_UNIX;
    strcpy(local.sun_path, SOCK_PATH);
    unlink(local.sun_path);
    len = strlen(local.sun_path) + sizeof(local.sun_family);
    if (bind(s, (struct sockaddr *)&local, len) == -1) {
        perror("bind");
        exit(1);
    }

    if (listen(s, 5) == -1) {
        perror("listen");
        exit(1);
    }

    while(1) {
        int done, n;
        printf("Waiting for a connection...\n");
        t = sizeof(remote);
        if ((s2 = accept(s, (struct sockaddr *)&remote, &t)) == -1) {
            perror("accept");
            exit(1);
        }

        printf("Connected.\n");

        if(fork()==0) {
            //child
            handle(s2);
        } else {
            close(s2);
        }
    }

    return 0;
}
client.c
Code:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "/Users/max/Desktop/sockets/mysocket"

int main(void)
{
    int s, t, len;
    struct sockaddr_un remote;

    if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    }

    printf("Trying to connect...\n");

    remote.sun_family = AF_UNIX;
    strcpy(remote.sun_path, SOCK_PATH);
    len = strlen(remote.sun_path) + sizeof(remote.sun_family);
    if (connect(s, (struct sockaddr *)&remote, len) == -1) {
        perror("connect");
        exit(1);
    }

    printf("Connected.\n");

    int msg;
    while(1) {
        if ((t=recv(s, &msg, sizeof(int), 0)) > 0) {
            printf("echo> %i\n", msg);
            fflush(stdout);
        } else {
            if (t < 0) perror("recv");
            else printf("Server closed connection\n");
            exit(1);
        }
    }

    close(s);

    return 0;
}

Um es noch einmal ausdrücklich zu erwähnen, ich habe mich nur schnell um die Funktionalität gekümmert. Es sind also uU sinnlose includes und ebenso sinnlose Codepassagen drin. Denk also selbst lieber noch einmal nach bevor du hier irgendwas raus kopierst.

Sollte dann etwa so aussehen:
jJXZ0.png
 
Zuletzt bearbeitet: (Bild hinzugefügt)
@Freezedevil

thx. Danke für die Mühe. Ich werds gleich anschauen. Mir hat nur irgendwie der Kniff gefehlt. Logisch, dass ich mit dem fd s2 einfach das zurücksenden des pids machen kann. Hast mir gut weitergeholfen.
 
Kein Problem, man lernt ja selbst immer was dabei ;). Ich persönlich hatte ehrlich gesagt vorher noch gar nichts von Unix Domain Sockets gehört.
 
Freezedevil schrieb:
Üblicherweise wartet der Hauptthread im Server in einer Endlosschleife auf Anfragen und wenn eine neue kommt wird diese in einem eigenen Thread bearbeitet. Die Threads merkt man sich in einer geeigneten Datenstruktur und kann dann bei einer neuen Anfrage des selben Clients den entsprechenden Thread killen.

Prinzipiell kann man auch forken, wenn ich mich recht entsinne, würde ich das aber nicht empfehlen, da dadurch ja jedes mal ein neuer Prozess entsteht und der Prozesswechsel im Vergleich zum Threadwechsel ziemlich teuer ist. Aus diesem Grund gibt es ja auch Threads und man sollte sie nutzen.

genau das ist der richtig weg! und nciht forken. forken sollte man auf gar keinen fall für eine client/server anwendung verwenden, wenn es mehrere clients erlaubt. dagegen sprechen mehrere gründe.

rechnet man mit sehr sehr vielen clients, dann muss man noch etwas mehr machen, jeh nach anwendungsgebiet. man sollte in jedem fall die max. anzahl an threads beschränken. will man jedoch dennoch mehr clients bedienen als man threads hat (die anzahl der threads zu beschränken hat verschiedene gründe), weil z.b. die clients nciht so anspruchsvoll sind, aber dafür sehr viele, so kann man innerhalb eines threads merhere clients bedienen, z.b. in einer art round robbin verfahren, d.h. zyklisch kommt jeder client mal drann. aber das geht jetzt glaube ich zu weit.
 
Man braucht nichtmal einen Thread, sondern kann auch select() bzw. epoll() verwenden. Das ist die beste Möglichkeit.
 
@stwe
Hättest du einen Link oder Beispiel. Warum ist es die beste Lösung?

Irgendwie hauen mir gerade die Threads alles um die Ohren. Heute ist erstmal schluss. Morgen wieder.
Ergänzung ()

Also ich hab mich nun nochmal drangesetzt und es sieht momentan so aus:

runtime_system.c == client

Code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCK_PATH "socket"

int main(int argc, char *argv[]) {
	printf("####### RUNTIME SYSTEM (%i) #######\n\n", getpid());
	int s, t, len;
	struct sockaddr_un remote;

	if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
		perror("socket");
		exit(1);
	}

	printf("Trying to connect...\n");

	remote.sun_family = AF_UNIX;
	strcpy(remote.sun_path, SOCK_PATH);
	len = strlen(remote.sun_path) + sizeof(remote.sun_family);
	if (connect(s, (struct sockaddr *)&remote, len) == -1) {
		perror("connect");
		exit(1);
	}

	printf("Connected.\n");

	int msg;
	char message[100];
	char pid[100];
	sprintf(pid, "%i", getpid());
	strcpy(message, "R_");
	strcat(message, "0_");
	strcat(message, pid);
	send(s, &message, 100, 0);

	return 0;
}

arbiter.c == server

Code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include "arbiter.h"

void thread_handle_test(int *socket) {
	int t_socket;
	int t;
	char message[100];
	t_socket = *socket;
	t = recv(t_socket, &message, 100, 0);
	message[t] = '\0';

	printf("%s\n", message);
	fflush(stdout);

	pthread_exit(NULL);
}

#define DEBUG 1
void init_arbiter() {
	socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
	local.sun_family = AF_UNIX;
	strcpy(local.sun_path, SOCKET_PATH);
	unlink(local.sun_path);

	remote_addr_length = sizeof(remote);
	local_addr_length = strlen(local.sun_path) + sizeof(local.sun_family);

	bind(socket_fd, (struct sockaddr *)&local, local_addr_length);
	listen(socket_fd, 100);
}

#define DEBUG 1
void clean_up_arbiter() {
	close(sd);
	unlink(SOCKET_PATH);
}

#define DEBUG 1
int main(void) {
	if (DEBUG) {
		printf("####### ARBITER (%i) #######\n\n", getpid());
	}

	init_arbiter();

	while (1) {
		sd = accept(socket_fd, (struct sockaddr *)&remote, &remote_addr_length);
		if (DEBUG) {
			printf("ARBITER: connected\n");
		}
		pthread_create(&thread, NULL, (void *) thread_handle_test, (void *) &sd);
	}

	clean_up_arbiter();
	pthread_exit(NULL);
	return 0;
}

Wenn ich das so laufen lasse klappt mal die Aufgabe beim Server. Mein Problem ist momentan einfach, dass wenn ich mal Valgrind starte, mir es so vorkommt, als dass immer mehr Speicher allokiert wird und nicht mehr freigegeben wird. Nachdem ein Thread doch thread_handle_test aufgerufen hat und beendet hat, müsste der Speicher wieder freigegeben werden, oder irre ich mich hier? Ich hatte versucht per bestimmter Nachricht den Server zu beenden, aber das hat leider nicht so geklappt. Daher wird ja auch nie Cleanup aufgerufen. Muss ich eigentlich jeden neuen FileDescriptor von der von jedem Thread genutzt wird auch wieder schließen? Fehlerabfrage muss ich noch überall rein machen (z.b ob listen geklappt hat etc).
 
Zuletzt bearbeitet:
hallo hab hier einen kleinen webserver geschrieben der so funktioniert wie "Freezedevil" es in #8 beschrieben hat... das ist die beste lösung instanziert halt für jeden Client einen neuen thread zur "Abarbeitung" :)

lg

m2k
 
Zuletzt bearbeitet: (a)
Hallo Zusammen,

danke für die Antworten. Ja das würde ich auch gerne so umsetzen, aber leider klappt das nicht so ganz. Wie räume ich das ganze wieder auf?

Hier mein SERVER:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <pthread.h>
#include "arbiter.h"

void thread_handle_test(int *socket) {
	int t_socket;
	int n;
	char message[100];
	t_socket = *socket;
	int done;
	char *message_ptr;
	char type;
	int status;
	int pid;

	done = 0;

	do {
		n = recv(t_socket, message, 100, 0);
		if (n != 0) {
			message[n] = '\0';
			message_ptr = strtok(message, "_");
			type = *message_ptr;
			message_ptr = strtok(NULL, "_");
			status = atoi(message_ptr);
			message_ptr = strtok(NULL, "_");
			pid = atoi(message_ptr);

			if(type == 'R') {
				printf("created Runtime System Element with PID: %i\n", pid);
			}
			if(type == 'T') {
				printf("created T Element with PID: %i\n", pid);
			}
		}
		else {
			done = 1;
		}
	} while (!done);
	pthread_exit(NULL);
}


#define DEBUG 1
void clean_up_arbiter() {
	close(sd);
	unlink(SOCKET_PATH);
}

#define DEBUG 1
int main(void) {
	if (DEBUG) {
		printf("####### ARBITER (%i) #######\n\n", getpid());
	}

	socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
	local.sun_family = AF_UNIX;
	strcpy(local.sun_path, SOCKET_PATH);
	unlink(local.sun_path);

	remote_addr_length = sizeof(remote);
	local_addr_length = strlen(local.sun_path) + sizeof(local.sun_family);

	bind(socket_fd, (struct sockaddr *)&local, local_addr_length);
	listen(socket_fd, 100);

	char str[100];

	while (1) {
		remote_addr_length = sizeof(remote);
		if ((sd = accept(socket_fd, (struct sockaddr *)&remote, &remote_addr_length)) == -1) {
			perror("accept");
			exit(1);
		}
		pthread_create(&thread, NULL, (void *) thread_handle_test, (void *) &sd);
	}
	pthread_exit(NULL);
	return 0;
}

Hier mein CLIENT:

Code:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "runtime_system.h"

#define SOCK_PATH "socket"

#define DEBUG 1
void sign_up_with_arbiter() {
	int msg;
	char message[100];
	char pid[100];
	sprintf(pid, "%i", getpid());
	strcpy(message, "R_0_");
	strcat(message, pid);
	send(socket_fd, &message, 100, 0);
}

#define DEBUG 1
void clean_up_runtime_system() {
	close(socket_fd);
}

#define DEBUG 1
void init_runtime_system() {
	socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
	remote.sun_family = AF_UNIX;
	strcpy(remote.sun_path, SOCK_PATH);
	remote_addr_length = strlen(remote.sun_path) + sizeof(remote.sun_family);
}

#define DEBUG 1
int main(int argc, char *argv[]) {
	if (DEBUG) {
		printf("####### RUNTIME SYSTEM (%i) #######\n\n", getpid());
	}

	init_runtime_system();

	connect(socket_fd, (struct sockaddr *)&remote, remote_addr_length);

	if (DEBUG) {
		printf("RUNTIME SYSTEM: connected\n");
	}
	char str[100];
	int t;

	sign_up_with_arbiter();

	close(socket_fd);

	return 0;
}

Mein Problem ist, wie oben schonmal geschrieben, rufe ich das ganze mit Valgrind auf, so hab ich das Problem, dass pro pthread_create Speicher allokiert wird, der niemals wieder freigegeben wird. Also wie kann ich diesen wieder freigeben, da ja die while-Schleife beim Server niemals endet. Ist es überhaupt richtig, wie ich die Threads erzeuge und pthread_exit() aufrufe? Per Nachrichtenprotokoll, wie je nach dem gemacht, was gemacht werden soll.
 
Zurück
Oben