Socket in Thread
-
Hallo, ich möchte einen (Web)server programmieren, der für jeden neuen Client, der sich verbindet, einen neuen Thread erstellt und in diesem Thread auf die Eingaben des Clients reagiert. Dazu ist schon einmal die erste Frage, ob diese Idee überhaupt Sinn macht und üblich ist.
Danach kommt die Frage der Verwirklichung: Ich möchte nicht gleich beim Starten des Servers mehrere Threads erstellen, die dann angesteuert werden. Mein Ziel ist es, dass der (Web)server für jede neue Verbindung eines Clients neu einen Thread erstellt. Doch an welchem Punkt beginne ich? Angenommen, ich habe folgenden Code:#include <windows.h> #include <winsock.h> #define MAX_CLIENTS 3 HANDLE hThreads[MAX_CLIENTS]; DWORD id[MAX_CLIENTS]; DWORD wait; int main() { long rc; WSADATA wsa; SOCKET s; SOCKADDR_IN addr; rc = WSAStartup(MAKEWORD(2, 0), &wsa); if(rc != 0) { return 1; } s = socket(AF_INET, SOCK_STREAM, 0); if(s == INVALID_SOCKET) { return 2; } memset(&addr, 0, sizeof(SOCKADDR_IN)); addr.sin_family = AF_INET; addr.sin_port = htons(12345); addr.sin_addr.s_addr = ADDR_ANY; rc = bind(s, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN)); if(rc == SOCKET_ERROR) { return 3; } rc = listen(s, 10); if(rc == SOCKET_ERROR) { return 4; } return 0; }
Kann ich dann nach einem Accept - Signal (welches hier noch nicht überprüft wird) einen neuen Thread erstellen? Oder ist es sinnvoller, auch den Listen - Teil in einen neuen Thread zu stecken? Ich habe vor einiger Zeit den Code eines kleinen Servers gesehen, der wohl schon ganz am Anfang (vor dem Bind - Teil) die Threads erstellt hat. Wäre das sinnvoll?
solanum
-
eigentlich wäre die sachen mit den threads etwas sinnlos.
wenn ein user zu deinem webserver eine verbindung aufbaut, wird im ja eine nummer zugewiesen an der er ja identifiziert werden kann.
nun besteht die möglichkeit, dass du die sache ganz einfach über WSAAsyncSelect regelst mit zb. den flags FD_READ ; FD_CLOSE etc....
hast du´s so gemacht, dann kannst du die nachrichten in deiner callback funktion abfangen , zum beispiel der user beendet die verbindung dann kannst du in FD_CLOSE herausfinden wer usw... . die zuweisung der aktion läuft wieder über die nummer ( socket ).
-
Das ist eine durchaus übliche Technik. Nach einem Accept würdest du den Verbindungssocket an einen thread weitergeben, der sich drum kümmert.
@mmh: deien Lösung ist zwar amchbar, aber wenn mans nicht ganz kompliziert macht, ineffizient. Ein Thread blockt, sobald er auf eine I/O-Operation (meistens Platte) wartet. Dann müsste man auch die ganzen Platten Read/Writes über Asynchrones I/O machen was den Code unglaublich komplex macht. Threads sind da schon schöner.
-
Hm, ich habe nun ein wenig mit Threads rumprobiert, doch mein jetziger Code lässt sich nicht einmal mehr kompilieren. Evtl. könntet Ihr drüber schauen und meine Fehler korrigieren?
Hier also die Funktion, die aufgerufen wird und ein Teil der main(), welcher für die Threads und die Sockets verantwortlich sein sollte:HANDLE hThreads[MAX_THREADS]; DWORD id[MAX_THREADS]; DWORD wait; DWORD WINAPI TestFunction(SOCKET c) { char b[BUFFER_SIZE], q[12], p[128], z[1024]; FILE *f; sscanf(b, "%s %s %s",q, p, z); if(strcmp(p, "/") == 0) { strcpy(p, strcat("/", DEFAULT_PAGE)); } if(!strcmp(q, "GET")){ if ((f = fopen(p + 1, "r")) > 0){ int i = 0; b[0] = fgetc(f); while(!feof(f)) { i++; b[i] = fgetc(f); } fclose(f); send(c, b, i, 0); } } closesocket(c); return (DWORD)0; }
.
.
.int i = 0; for(;;) { c = accept(s, NULL, NULL); hThreads[i] = CreateThread(NULL, 0, TestFunction, (SOCKET)c, 0, &id[0]); i++; } wait = WaitForMultipleObjects(i, hThreads, TRUE, INFINITE); for(int j = 0; j < i; j++) { CloseHandle(hThreads[j]); } closesocket(s); WSACleanup();
Vielen Dank für eure Hilfe,
solanum
-
Die Thread-Funktion muss immer vom Parameter her einen void * haben, das musst du dann innerhlab der Funktion zurechtcasten. Also in der Art:
DWORD WINAPI TestFunction(LPVOID p) {
SOCKET c = (SOCKET)p;
-
Mal davon abgesehen, dass ich den Sinn darin nicht verstehe: Danke, habe es gefixt und der Code lässt sich nun kompilieren. Jedoch beendet er den Server sofort wieder. Woran liegt das?
Eine Frage für später? Wie kann ich am einfachsten testen, ob für jeden Benutzer ein Thread erstellt wird?solanum
-
Ups, er beendet den Server doch nicht sofort wieder. Jedoch schickt er keine Daten an den Server zurück ...
-
Hatte meinen recv() Teil gar nicht eingebaut. Wenn ich ihn jedoch vor
sscanf(b, "%s %s %s",q, p, z);
einfüge, wird mir die Fehlermeldung "continue statement not within a loop" gegeben. Der Teil sieht wie folgt aus:
if(recv(c, b, 999, 0) < 0) { continue; }
Danke,
solanum
-
NEUE VERSION!
Da es viel übersichtlicher aussieht und wohl auch plattformunabhängig ist, habe ich nun folgende Methode verwendet, die auch wunderbar läuft:
#include <windows.h> #include <winsock.h> #include <process.h> #include <stdio.h> #define DEFAULT_PAGE "index.html" #define HTTP_PORT 80 SOCKET c; void threaded_socket(void *n) { char b[1024], q[12], p[128], z[1024]; FILE *f; while(recv(c, b, 999, 0) < 0) { continue; } sscanf(b, "%s %s %s",q, p, z); if(strcmp(p, "/") == 0) { strcpy(p, strcat("/", DEFAULT_PAGE)); } if(!strcmp(q, "GET")){ if ((f = fopen(p + 1, "r")) > 0){ int i = 0; b[0] = fgetc(f); while(!feof(f)) { i++; b[i] = fgetc(f); } fclose(f); send(c, b, i, 0); } } closesocket(c); _endthread(); } main(){ SOCKADDR_IN addr; WSADATA wsa; SOCKET s; long rc; rc = WSAStartup(MAKEWORD(2, 0), &wsa); s = socket(AF_INET, SOCK_STREAM, 0); memset(&addr,0, sizeof(SOCKADDR_IN)); addr.sin_family = AF_INET; addr.sin_port = htons(HTTP_PORT); addr.sin_addr.s_addr = ADDR_ANY; rc = bind(s, (SOCKADDR*)&addr, sizeof(SOCKADDR_IN)); if(listen(s, 3)) exit(1); for(;;) { c = accept(s, NULL, NULL); _beginthread(threaded_socket, 0, NULL); } closesocket(s); WSACleanup(); return 0; }
Jedoch frage ich mich immernoch, ob meine Benutzung von Threads überhaupt Sinn macht. Ich hoffe, ihr könnt mir Gewissheit verschaffen.
solanum