Lesbare Deskriptoren bei select(...)

ENNEMMEE

Member
Beschäftige mich grad mit Netzwerkprogrammierung in C/C++...
Bin jetzt aber auf folgendes Problem gestoßen:

Mal grob der Ablauf:

select wartet auf neue Connections:
int nready = ::select( m_highestsock+1, &m_currentsockset, NULL, NULL, &tv );

Neue Connection kommt an, nready wird auf 1 gesetzt, über accept einem Deskriptor zugewiesen und nready dekrementiere ich danach für weitere Verarbeitung...

Im nächsten Durchlauf wird nready allerdings vom select(...) wieder auf 1 gesetzt, obwohl keine neue Verbindung ankommt und obwohl auf diesem Deskriptor keinerlei Daten liegen...

Auf jeglichen Seiten im I-Net lese ich allerdings, dass select nur neue Verbindungen oder lesbare Deskriptoren MIT Daten zurückliefert. Es existieren sogar Funktionen, die select(...) dazu benutzen um bei einem Deskriptor darauf zu warten, dass Daten lesbar werden, damit recv bzw. recvfrom nicht blockieren - interessanterweise tun sie das bei mir allerdings auch nicht...

Jemand ne Idee? Benutze ein FreeBSD 5.3 falls das hilft...

Wenn jemand mehr Info braucht, hier mal die Klassenfunktion:
Code:
int NetSocket::select() {
	struct timeval tv;
	fd_set m_currentsockset;
	
	tv.tv_sec = 2;
	tv.tv_usec = 0;
	
	m_currentsockset = m_sockset;
	
	cout << "waiting for incoming connections..." << endl;
	
	int nready = ::select( m_highestsock+1, &m_currentsockset, NULL, NULL, &tv );

	//too many connections?
	if (m_sockclient.size() < FD_SETSIZE) {
		// new connection
        if (FD_ISSET(m_socklisten, &m_currentsockset)) {
            int clilen = sizeof( m_addrclient );
            int connfd = ::accept( m_socklisten, ( sockaddr * ) &m_addrclient,  ( socklen_t * )  &clilen );
			
			cout << "new connection accepted on " << connfd << endl;
			
            m_sockclient.push_back(connfd);

            FD_SET(connfd, &m_sockset);	/* add new descriptor to set */
            
            test = connfd;

			if (connfd > m_highestsock)
				m_highestsock = connfd;			/* for select */

			nready--;

			OnNewConnection( m_sockclient.size() );
        }
	}

	//check all connections for data
	if (nready > 0) {
        int sockfd;
        vector<int>::iterator it;
        for( it = m_sockclient.begin(); it != m_sockclient.end(); it++ ) {

            if ( (sockfd = *it) < 0)
                continue;
                
            if (FD_ISSET(sockfd, &m_currentsockset)) {
                if (NetSocket::is_readable(sockfd,0,5)) {
                	cout << "data available on " << sockfd << endl;
                    OnNewData(sockfd);
                    nready--;
                }
            }
        }
	}
	
	cout << "nready at " << nready << endl << endl;

	return nready;
}

Das Problem ist, dass die Funktion wunderbar wartet solange keine Connections aufgebaut werden...
Sobald eine Verbindung mit einem Client besteht, rennt der select(...) durch weil er angeblich lesbar ist - das heisst im Prinzip hab ich dann keinen schlafenden Prozess mehr, sondern einen der ständig am rödeln ist... und das ist alles andere als brauchbar :zitter:
Über fcntl(...) den einzelnen fd auf ~O_NONBLOCK zu setzen hab ich auch versucht, keine Wirkung...

Jetzt bin ich ein wenig irritiert :grumble:

P.S.: Und sorry, das Board wirft irgendwie die Formatierung vom Code ein wenig durcheinander :confused:
 
Arbeite das mal durch: http://zotteljedi.de/doc/socket-tipps/index.html

Prinzipiell geht das so:
* Ein select wartet bis einer der "scharfgemachten" Sockets "hier" schreit.
* Einen socket "scharfmachen" geht mit FD_SET, nachdem mit FD_ZERO des SET (die Menge) zu Null initialisiert wurde.
* Du musst jedesmal die Sequenz: zuerst FD_ZERO, und dann für jeden socket ein FD_SET durchlaufen. Nicht nur einmal am Programmanfang.
* Welcher socket "hier" geschrien hat frägst Du mit FD_ISSET ab.
* Der erste Parameter von select ist eins mehr als das Maximum der nummerischen Werte aller sockets (ein socket wird im Programm duch ein int dargestellt).

Bei Zotteljedi steht das alles schön ausführlich mit Beispielen. Besonders die Seite "Die Scheisse geht ned!" könnte Dir helfen. :D
 
Hallo ENNEMMEE,

schau doch mal hier: UNIX Network Programming. Es gibt auf dieser Seite auch die Code-Snippets aus sein Buch.
Vielleicht helfen sie Dir weiter.

Soweit wie ich das überfliegen des Codes gesehen habe (bei Dir und in den Snippets), mußt Du den Puffer "leerlesen". Dabei solltest Du den Rückgabewert von read beachten!

Viele Grüße

Jürgen

PS: Oben genanntes Buch dürfte wohl eins der besten zur UNIX-Programmierung sein, die es gibt.
 
Das Buch von Stevens liegt schon seit einiger Zeit neben mit und ist hochgradig interessant, allerdings weisen seine Beispiele dasselbe Problem auf... sobald ein einziger Client connected hat, blockiert select(...) nicht mehr und schreit immer, dass >0 Deskriptoren "hier" schreien... das fd_set entleeren mit FD_ZERO(...) bewirkt leider auch nichts :-(

Vielleicht hilft das zum Verständnis:
Code:
bool NetSocket::is_readable(int fd, int sec, int usec) {
	fd_set rset;
	struct timeval tv;

	FD_ZERO(&rset);
	FD_SET(fd, &rset);

	tv.tv_sec = sec;
	tv.tv_usec = usec;


	if (::select(fd+1, &rset, NULL, NULL, &tv) > 0)
		return true;
	else
		return false;
}

Ein komplett leeres fd_set wird nochmal mit FD_ZERO resetted und ein einziger Deskriptor hinzugefügt.
Wenn auf diesem Deskriptor schon eine Verbindung hergestellt ist, so liefert select(...) IMMER 1 zurück, obwohl keine Daten auf diesem Deskriptor bereitstehen :-(

Diese Funktion wollte ich eigentlich dazu benutzen, vor einem rcv bzw. rcvfrom nachzusehen ob Daten vorliegen, damit rcv bzw. rcvfrom nicht blockieren... blockieren tun sie nicht, obwohl sie eigentlich laut Definition standardmäßig blockierend sind... rcv bzw rcvfrom kehren aber sofort zurück mit 0 gelesen Bytes :grumble: Also ist ein "Leerlesen" wohl auch nicht die Lösung... Wenn ich Daten vom Client sende kommen die auch sofort an... nur der Fall, dass keine Daten kommen wird behandelt als wären die Aufrufe von select(...) und rcv bzw rcvfrom non-blocking...

Im weiteren Programmverlauf resultiert daraus leider ein Verhalten wie bei non-blocking Aufrufen, aber das kann ich nicht brauchen...

Versteht ihr wo mein Problem liegt?
 
ENNEMMEE schrieb:
... Wenn auf diesem Deskriptor schon eine Verbindung hergestellt ist, so liefert select(...) IMMER 1 zurück, obwohl keine Daten auf diesem Deskriptor bereitstehen ...
Überprüfe mal was in diesem Falle (also wenn select "1" zurückgibt) FD_ISSET(fd, &rset) ergibt. Wenn das TRUE ist, dann gibt es auch was zum lesen.
Den Rückgabewert von select habe ich eigentlich nur ausgewertet, wenn ich wissen mag ob es ein timeout war. Ansonsten arbeite ich immer mit FD_ISSET.
 
Schon lang gemacht... FD_ISSET(...) ist true für diesen Deskriptor... aber es sind definitiv keine lesbaren Daten vorhanden... ein rcv oder rcvfrom liefert 0 gelesene Zeichen zurück ;'(

Und select(...) blockiert eben nicht für die angegebene Zeit sondern liefert ohne Zeitverzögerung einen Wert zurück - zu einem Timeout kommt es garnicht... DAS ist mein Problem... ich könnt's ja ignorieren, aber ich muss auf die Leistung am Server achten und da hilft mir ne endlose Schleife nichts... ;'(

Wenn Daten vorhanden sind, ist das ja richtig so - die kann ich ja dann auch lesen vom Socket - aber ein "schlafender" bzw. nicht-sendender Client erzeugt auf diese Art Prozessorlast... :grumble:
 
Ok, ich hab's doch noch selbst gefunden, was es ist - ich poste es aber mal, falls noch irgendwer mal auf dieses Problem stoßen sollte:

Das Problem an meinem Code ist die fehlende Behandlung eines EOF, wenn ein Client seine Verbindung trennt... ein EOF führt dazu, dass rcv oder rcvfrom 0 Zeichen auslesen und ohne zu blockieren zurückkehren... auch der Select registriert ein EOF und setzt aus diesem Grund den Deskriptor und blockiert natürlich dann auch nicht mehr...

Tut mir leid, dass ich euch mit meinem Fehler belastet habe... war meine eigene Schuld... ich liebe solche "Kleinigkeiten" an denen man sich tagelang die Zähne ausbeisst und irgendwann fällt es einem wie Schuppen aus den Haaren :D
 
ENNEMMEE schrieb:
... Tut mir leid, dass ich euch mit meinem Fehler belastet habe...
Aber dazu ist das Forum doch da - das man sich über Probleme austauscht.
Schau Dir nochmal http://zotteljedi.de/doc/socket-tipps/code_client.html an. Besonders das Fragment:
Code:
			bytes = read(sender, buf, sizeof(buf));
			if (bytes == 0)
				remove_client(&list, sender);
			else
				send_all(buf, bytes, &list, sender);
Das ist genau der if - Zweig an dem Du Probleme hattest. Null Bytes gelesen = EOF ==> Client hat sich verabschiedet.
So ist das eben mit der Programmierung: Meist hängt man an einer winzigen Kleinigkeit. Wenn man dann mit anderen drüber reden kann findet man es schon. Viel Spass noch beim coden :)
 
Zurück
Oben