Threads und Socket Programmierung

Elscha

Mitglied
Hallo

Ich habe vor für ein Abschlussprojekt in C++ ein kleines Chatprogramm zuu programmieren. Mit den Netzwerk und den Sockets komme ich nung gut klar. Allerdings hab ich nund as Problem das einige Anweisungen sogenantne blockierende Verbindungen sind. Da ich nun mehrere solcher Funktionen in verschiedenen Klassen hab, woltle ich dieses Problem nun mit Threads umgehen.
Nun soll das eigentliche Programm, welches in keinem Thread verpackt ist und die in den Objekten enthaltenden Threads laufen.

Zunächst hab ich mir eine Methode zum erstellen von Threads geschrieben:
Code:
HANDLE threadErstellen(LPTHREAD_START_ROUTINE prozedur, bool bSofortStarten, LPDWORD& id)
{
	//Überprüfung ob der Thread sofort starten soll
	 DWORD start = 0;
	 if (!bSofortStarten)
	 {
		 start = CREATE_SUSPENDED;
	 }

	 /* Thread erzeugen:
	  * 1. Parameter: Sicherheitsattribut, gleiches Sicherheitsprofil, wie die Anwendung
		* 2. Parameter: Stackgröße
		* 3. Parameter: Funktionsaufruf
		* 4. Parameter: Paramterübergabe an die Funktion
		* 5. Parameter: Abfrage ob der Thread gestartet erzeugt werden soll
		* 6. Parameter: Verweis auf die Thread ID
		*/
	 return CreateThread(NULL, 0, prozedur, 0, start, id); 
}

Und nun starte ich Besipeilsweise einen Thread aufm Server (der Thread der zum abhöhren neuer Verbindugnen zuständig ist)
Code:
thread = threadErstellen((LPTHREAD_START_ROUTINE)clientenAkzeptieren(), true, threadID);

Wobei thread vom Typ HANDLE ist und clientenAkzeptieren() wie folgt aussieht:
Code:
DWORD WINAPI ServerNetzwerk::clientenAkzeptieren()
{
	while(true)
	{
		SOCKET* pClientSocket = new SOCKET();

		// Versertzt den Socket in nem Listen-Status
		hoehren(this->serverSocket, 1);

		//Verbindung annehmen
		eingehendeVerbindung(*pClientSocket, this->serverSocket);
	}

	return 0;
}

In eingehden Verbindungen wird die Accept Methoide aufgerufen, welche selber nun die eigentliche blockierende Methode darstellt:
Code:
string eingehendeVerbindung(SOCKET& rClientSocket, SOCKET& rServerSocket)
{
	//Akzeptieren von Verbindungen (Blockierende Methode)
	//2. Argument: Optionale Angabe zu der Adresse des Clients
	//3. Argument: Länge der Adresse
	rClientSocket=accept(rServerSocket,NULL,NULL);
	
	//Stauts Rückgabe
	string sRueckgabe;
	if(rClientSocket==INVALID_SOCKET)
	{
		sRueckgabe = "Fehler: accept, Fehlercode: ";
		char sFehlercode[10];
		itoa(WSAGetLastError(), sFehlercode, 10);
		sRueckgabe.append(sFehlercode);
	}
	else
	{
		sRueckgabe = "Neue Verbindung wurde akzeptiert!";
	}

	return sRueckgabe;
}

Zunächsteinmal scheinen die Methoden wie gewollt zu laufen, ich kann beliebig viele Testclienten starten und alle zeigen mit an das sie verbunden sind (weitere Funktionen hab ich noch nicht eingebaut, ausser das sie auch anzeigen können, wenn sich nicht verbunden sind ;-))
Und nun zu meinem Problem:
Auch wenn es sich hierbei um Threads handelt, scheinen diese jedoch weiterhin zu blockieren. Weitere Anweisungen wie zum Beispiel ein cout werden nicht ausgeführt.

Ich selber hab schon mal ein ähnliches Projekt unter Java programmiert, hab also von daher ein paar Kenntnisse zur Thread- und Socketprogrammierung.
Zudem sollen wir Visual Studio 2005 benutzen ohne weitere (externe) Bibilotheken etc. zu verwenden.
 
Hi,

ich hab bisher noch nicht versucht, sowas in Threads zu packen.
Aber um ohne Threads auszukommen, kann man select() benutzen.

C++:
int select(
  int nfds,
  fd_set* readfds,
  fd_set* writefds,
  fd_set* exceptfds,
  const struct timeval* timeout
);

schau einfach in der MSDN.
 
select erspart dir ja auch nicht unbedingt einen Thread, du musst ja damit immer wieder pollen.

Wenn du unter Windows arbeitest, kannst du mit WSAAsyncSelect arbeiten. Das erspart dir einen Thread und leitet alle Socket-Events auf ein Fenster um. Für eine simple Anwendung wie in deinem Fall reicht das allemal. Du bekommst alle Ereignisse ins Haus geliefert und musst nur noch passend reagieren.

Dazu den Socket wie gehabt erstellen, mit WSAAsyncSelect auf ein Fenster umleiten und dann erst connect aufrufen. Achtung, connect wird dir da vermutlich ein WSAEWOULDBLOCK werfen, das ist in dem Fall in Ordnung.

Gleich wichtiger Hinweis: Wenn du das in einen Thread wegpackst (also auch das HWND in dem anderen Thread erstellst), muss dieser Thread selbst eine Message-Schleife haben!
 
Also schon mal danke für die guten Antworten.
select() Woltle ich deshalb nicht benutzen, weil ich die Sockets in verschiedenen Klassen laufen lasse (Wäre an sich dan der Zeiger und referenzen ja auch kein Problem) und es immernoch eine blockierende Methode ist (dumm für den Chatter der nachher eine Nachricht eingeben möchte).

Was den zweiten Eintrag angeht. Ja ich benutze Windows und die windows.h also Win-API. Ich werd mich dann mal über WSAAsyncSelect n bischen schlau machen. Läuft dann trozdem der Rest meines Programms weiter?

Die eigentlichen Methoden die ich über die Threads laufen lassne wollte, waren ja accept und recv jeweils in ner Endlosschleife.

Hätteste evtl ein (möglichst deutsches) Tutorial oder ne Anleitung wie ich WSAAsyncSelect verwenden kann?
In den meisten Tutorials zur Socketprogrammeirung wird auf select() zurück gegriffen, weil der Plattform unabhängiger Code genieriert werden soll.
 
Zuletzt bearbeitet:
Auf Google habe ich den folgenden Beitrag gefunden. Der ist nicht schön und enthält auch noch einen Fehler (siehe letzten Beitrag), aber die zwei Änderungen sind schnell gemacht.

http://www.coding-board.de/board/showthread.php?t=13680

Da wird ein Fenster aufgemacht, der Socket dran geklemmt und man sieht auch, wie die Windows-Nachrichten verarbeitet werden müssen.
 
So hab mich nu nach meinem kleinem Urlaub wieder an mein Projekt gesetzt und noch immer ein paar fragen ;-)

1.)
#define WM_SOCKET (WM_USER +1) wofür ist dieses denn genau da? Und da wir keine defines benutzen sollen, was wird da definiert? Ein DWORD (sprich ein long)? Dient dazu damit ich es dann als const verwenden kann.

2.)
Zur funktionsweise selber ich habe nun den Socket über WSAAsyncSelect connected:
Code:
string connectTo(SOCKET& rSocket, char* pcIP, u_short iPort)
{
	long iFehlermeldung;

	//Socket TCP/IP tauglich machen (Konstruktor aufrufen)
	socketErstellen(rSocket);

	//Socket einrichten und Adresse abspeichern
	SOCKADDR_IN& rAdresse = socketEinrichten(rSocket, pcIP, iPort);

	//Verbinden mit Server
	iFehlermeldung = connect(rSocket, (SOCKADDR*)&rAdresse, sizeof(SOCKADDR));
	
	//Rückgabe des Verbindungsstatusses
	string sRueckgabe;

	if(iFehlermeldung ==SOCKET_ERROR)
	{
		sRueckgabe = "Fehler: connect gescheitert.";
	}
	else
	{
		sRueckgabe = "Verbunden mit ";
		sRueckgabe.append(pcIP);
	}

	return sRueckgabe;
}

Und nun zu dem Teil von der anderen Lösung:
Code:
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
 
 char szSend[2000];
 char readbuff[2000];
 int  rlen;
 
 switch (wMsg)
 {
 case WM_INITDIALOG:
  hMainDlg = hDlg;
  SetDlgItemText(hMainDlg, IDC_IP, "127.0.0.1");
  SetDlgItemText(hMainDlg, IDC_PORT, "1001");
  break;
 
 case WM_CLOSE:
  closewinsock();
  EndDialog(hDlg, 0);
  break;
 case WM_COMMAND:
  switch(LOWORD(wParam))
  {
  case IDC_EXIT:
   closewinsock();
   PostQuitMessage(0);
   break;
  
  case IDC_CONNECT:
   initwinsock();
   winsockconnect();
   break;
  case IDC_DISCONNECT:
   closewinsock();
   break;
  case IDC_SEND:
   
   GetDlgItemText(hMainDlg, IDC_SENDTEXT, szSend, 2000);
   SetDlgItemText(hMainDlg, IDC_SENDTEXT, "");
   strcat(szSend, "\r\n");
   send(connect_socket, szSend, strlen(szSend), 0);
   break;
  }
  break;
  
  case WM_SOCKET:
   switch(LOWORD(lParam))
   {
   case FD_READ:
	rlen = recv(connect_socket,readbuff,2000,0);
	readbuff[rlen] = 0;
	GetDlgItemText(hMainDlg, IDC_RECV, szInput, 2000000);
	strcat(szInput, readbuff);
	SetDlgItemText(hMainDlg, IDC_RECV,  szInput);
	break;
	
   case FD_CLOSE:
	GetDlgItemText(hMainDlg, IDC_RECV, szInput, 2000000);
	strcat(szInput, "Verbindung abgebrochen!\r\n");
	SetDlgItemText(hMainDlg, IDC_RECV,  szInput);
	break;
   case FD_CONNECT:
	GetDlgItemText(hMainDlg, IDC_RECV, szInput, 2000000);
	strcat(szInput, "Verbindung aufgebaut!\r\n");
	SetDlgItemText(hMainDlg, IDC_RECV,  szInput);
	break;
   }
  }  
 return FALSE;
}

Diese Methode muss rein prinzipiell noch angepasst werden, da ich ja nur Verbindungen akzeptieren, Nachrichten versenden und empfangen will.
Meine Frage hierzu:
Muss MainDlgProc in einer Endlosschleife aufrufen oder einmal ausführen oder wie? Schließlich soll ständig auf neue Nachrichten gewartet werden und der Benutzer soll aber weiterhin mit dem Programm später arbeiten können.
Wie gesagt hab ich selber zu C++ nicht soviel Ahnung (eher zu Java) wir dürfen aber nur Visual Studio 2005 inkl aller mitgelieferten Packete verwenden.

3.)
Was mache ich mit dem Server, dieser führt ja selber kein connect durch. Leite ich hier WSAAsyncSelect um bevor ich den bind durchführe?
 
Zuletzt bearbeitet:
Bei der DialogBox-Funktion gibst du die MainDlgProc ja an. Die rufst du um Himmels Willen nicht selber auf. Windows ruft die dann intern bei jeder Windows-Message auf.

Das #define WM_SOCKET (WM_USER +1) ist dafür da, eine Windows-Nachricht zu definieren, die es noch nicht gibt. Per WSAAsyncSelect werden alle Socket-Events als Windows-Nachricht versandt. Wenn du da eine Message nimmst, die bereits anderweitig verwendet wird, gibt es Probleme. Deshalb definierst du dir eine eigene (WM_USER ist vereinfacht gesagt der Anfang des frei wählbaren Bereichs). Das kannst du genauso in ein const DWORD packen wenn du dich dadurch besser fühlst. Um das WM_USER-Define kommst du aber nicht drumrum.
 
Also muss ich nur in die
Code:
BOOL CALLBACK MainDlgProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
reinschreiben was er bei bestimmten Socket Nachrichten tun soll.
OK und an welcher Stelle definiere ich diese Klasse? Is das egal? Hab schließlich mehrere Klassen die ich mir fürs Netzwerk geschrieben hab
 
Wichtig ist für dich der Abschnitt mit WM_SOCKET, wie im Beispiel.

Die MsgDlgProc lässt sich nur als statische Klassen-Member-Funktion in eine Klasse einsetzen.
 
OK ich glaube solangsam raffe ich es ;-) Hab mir auch zusätzlich ne 10MB WinAPI Hilfe runter geladen.

Nun hab ich mir erstmal für WSAAsyncSelect ne Methode geschrieben:
Code:
string aufFensterUmleiten(SOCKET& rSocket, HWND& rFenster, long iEvent)
{
	long iFehlermeldung = WSAAsyncSelect(rSocket,	rFenster, coniWM_SOCKET, iEvent);
	string sRueckgabe;

	//Möglichen Fehler zurückliefern
	if (iFehlermeldung == SOCKET_ERROR)
	{
		sRueckgabe = "WSAAsyncSelect liefert einen Fehler zurück!";
	}
	else
	{
		sRueckgabe = "WSAAsyncSelect erfolgreich gestartet.";
	}

	return sRueckgabe;
}

Und nun zu meiner ServerNetzwerkschicht, der Konstruktor sieht in etwa so aus:
Code:
ServerNetzwerk::ServerNetzwerk(ServerOperator* psoOperator)
: vVerwaltung(), zeit()
{
	this->psoOperator = psoOperator;
	string sRueckmeldung;

	//Netzwerkstarten
	// Winsock starten
	sRueckmeldung = startWinSock();
	this->psoOperator->meldungEingegangen(&sRueckmeldung);
	//Socket Konfigurieren
	sRueckmeldung = socketErstellen(serverSocket);
	this->psoOperator->meldungEingegangen(&sRueckmeldung);
	//Socket auf weiteres Fenster umleiten
	sRueckmeldung = aufFensterUmleiten(serverSocket, fenster, FD_ACCEPT);
	this->psoOperator->meldungEingegangen(&sRueckmeldung);
	//Socket an den Port binden
	sRueckmeldung = binden(serverSocket, coniPORT);
	this->psoOperator->meldungEingegangen(&sRueckmeldung);
}

bei psoOperator handelt es sich nur um eine Schnittstelle, die die drüberliegende Klasse implementiert hat um Nachrichten nach oben weiterleiten zu können.

Die Ausgabe meines Test Servers sieht nun so aus:
Code:
Winsock erfolgreich gestartet!
Socket erfolgreich erstellt!
WSAAsyncSelect liefert einen Fehler zurück!
Socket an port 1000 gebunden.

Was mache ich denn da nun falsch?

Liegt es vielleicht am Fenster? Da ich damit noch keine Ahnung hab, habe ich das bislang nur deklariert:
Code:
HWND fenster;
 

Neue Beiträge

Zurück