2010-01-14 8 views
11

Wir schreiben einen Client und einen Server zu tun (was ich dachte, war) ziemlich einfache Netzwerk-Kommunikation. Mehrere Clients verbinden sich mit dem Server, der die Daten dann an alle anderen Clients zurücksenden soll.Lesen und Schreiben in den gleichen Socket (TCP) mit Select

Der Server sitzt nur in einer blockierenden select Schleife auf Datenverkehr, und wenn es dazu kommt, sendet die Daten an die anderen Clients. Das scheint gut zu funktionieren.

Das Problem ist der Client. Als Antwort auf einen Lesevorgang möchte er manchmal schreiben.

Allerdings habe ich festgestellt, dass, wenn ich benutze:

rv = select(fdmax + 1, &master_list, NULL, NULL, NULL); 

Mein Code wird blockiert, bis neue Daten zu lesen ist. Aber manchmal (asynchron, von einem anderen Thread) habe ich neue Daten, um auf den Netzwerk-Kommunikations-Thread zu schreiben. Also, ich möchte meine wählen regelmäßig aufwachen und lassen Sie mich prüfen, ob Daten zu schreiben sind, wie:

if (select(....) != -1) 
{ 
    if (FD_SET(sockfd, &master_list)) 
    // handle data or disconnect 
    else 
    // look for data to write and write()/send() those. 
} 

I Einstellung versucht, den Auswahlmodus (oder lächerlich kurzen Timeouts) abfragen mit:

// master list contains the sockfd from the getaddrinfo/socket/connect seq 
struct timeval t; 
memset(&t, 0, sizeof t); 
rv = select(fdmax + 1, &master_list, NULL, NULL, &t); 

aber haben festgestellt, dass dann Client nie eingehende Daten erhält.

Ich habe auch versucht die Buchse fd Einstellung nicht blockierend zu sein, wie:

fcntl(sockfd, F_SETFL, O_NONBLOCK); 

aber das löst nicht das Problem:

  1. , wenn mein Kunde select() kein struct timeval hat, Lesen Daten funktionieren, aber es gibt keine Blockierung, um nach schreibbaren Daten zu suchen.
  2. Wenn mein Client select() hat eine timeval, um es zu Abfrage, dann signalisiert es nie, dass eingehende Daten zu lesen sind, und meine App einfriert denken, es gibt keine Netzwerkverbindung (trotz der Tatsache, dass alle anderen Funktionsaufrufe erfolgreich waren)

Irgendwelche Hinweise überhaupt, was ich falsch machen könnte? Ist es nicht möglich, Read-Write am gleichen Socket zu machen (ich kann nicht glauben, dass das wahr ist).

(EDIT: Die richtige Antwort, und was ich auf dem Server in Erinnerung, aber nicht auf dem Client, ist eine zweite fd_set zu haben, und kopiert Master_List vor jedem Anruf zu wählen():

// declare and FD_ZERO read_fds: 
// put sockfd in master_list 

while (1) 
{ 
    read_fds = master_list; 
    select(...); 

    if (FD_ISSET(read_fds)) 
    .... 
    else 
    // sleep or otherwise don't hog cpu resources 
} 

)

+0

Sorry, dass if (FD_SET (... sollte if (FD_ISSET (.... – MarcWan

Antwort

12

Alles sieht gut aus, außer der Linie, wo Sie tun if (FD_SET(sockfd, &master_list)). Ich habe eine sehr ähnliche Codestruktur und habe FD_ISSET verwendet. Sie sollten testen, ob die Liste festgelegt ist, und nicht, um sie erneut festzulegen. Ansonsten sehe ich nichts anderes.

Bearbeiten. Außerdem habe ich im Anschluss an die für den Timeout:

timeval listening_timeout; 
listening_timeout.tv_sec = timeout_in_seconds; 
listening_timeout.tv_usec = 0; 

vielleicht ein Problem gibt es, wenn man es auf 0 gesetzt (wie Sie zu tun scheinen?)

Edit2. Ich erinnerte mich, dass ich ein seltsames Problem hatte, als ich den Lesesatz nicht löschte, nachdem die Auswahl beendet war und bevor ich sie erneut eingegeben hatte. Ich musste etwas tun wie:

FD_ZERO(&sockfd); 
FD_SET(sockfd, &rd); 

bevor ich select eingegeben wurde. Ich kann mich nicht erinnern warum.

+0

Es sollte kein Problem mit der Einstellung der Zeitüberschreitung auf '0 'sein: In diesem Fall wird' select' nur prüfen, ob alle Deskriptoren sind zur Zeit bereit und kehren sofort zurück – JaakkoK

+1

Zu deinem Edit2: 'select' wird die Sets modifizieren, also musst du sie zurücksetzen, bevor du sie wieder anrufst – JaakkoK

+0

@jk danke, ich werde das als Kommentar zu meiner Quelldatei hinzufügen Es wird in der Zukunft nützlich sein – laura

7

Ich erinnere mich an einen Trick über das Erstellen und Freigeben eines Lese-/Schreib-Dateideskriptors zwischen dem Netzwerk-Thread und dem Haupt-Thread, der den Deskriptoren im Select-Aufruf hinzugefügt wird. Dieses fd hat ein Byte vom Haupt-Thread geschrieben, wenn es etwas zu senden hat. Der Write aktiviert den Netzwerkthread vom ausgewählten Anruf und der Netzwerkthread erfasst dann die Daten aus einem gemeinsam genutzten Puffer und schreibt sie in das Netzwerk, um dann in der Auswahl in den Ruhezustand zurückzukehren.

Entschuldigung, wenn das ein bisschen vage und ohne Code ist ... und mein Gedächtnis kann falsch sein .. so müssen andere Sie möglicherweise weiter führen.

+0

Ich mag wirklich Diese Idee eine Menge.Obwohl es mein Problem nicht löst, verwende ich bereits eine Pipe, um den Netzwerk-Thread zum Beenden zu sagen, und hatte nicht den Sprung gemacht, um auch zu sagen der Thread, zu dem Daten zum Schreiben verfügbar sind. Ich liebe es !! – MarcWan

1

Ich sehe nichts falsch mit Ihrem Code, so sollte es funktionieren. Wenn Sie es nicht zum Laufen bringen können, wäre es eine Möglichkeit, eine Pipe zu erstellen, die von Ihrem Lese-Thread und dem Thread zum Schreiben verwendet werden kann, und das Leseende der Pipe zu Ihrem select Set hinzuzufügen. Wenn dann der andere Thread Daten zum Schreiben vorbereitet hat, sendet er nur etwas auf die Pipe, Ihr Lese-Thread wird von select geweckt und kann dann schreiben. Abhängig davon, wie oft Daten zu lesen oder zu schreiben sind, könnte dies auch effizienter sein.

+0

Nun, der Code oben war in der Tat grundsätzlich korrekt (mit Ausnahme der Sache FD_SET/FD_ISSET). Was nicht richtig war, war was fehlte und was @jk oben aufgezeigt hat - man muss das fd_set vor jedem Aufruf zur Auswahl zurücksetzen. Ich habe einen neuen namens read_fds und kurz vor jedem Aufruf erstellt, um "read_fds = master_list" auszuwählen, und dann schauen alle nachfolgenden FD_ISSET-Aufrufe in read_fds, nicht in master_list. Dank dafür. Lesen/Schreiben von Sockets funktioniert wieder, wooO! – MarcWan

+0

Es war eigentlich @Laura, die darauf hingewiesen, ich habe nur den Grund erklärt. Ich hätte ihre Antwort akzeptiert, da sie das Problem gelöst hat. – JaakkoK

+0

Okay, fertig. Jeder mag ein ehrliches Plakat :) – MarcWan

0

2 Threads sollten in der Lage sein, mit demselben Socket gleichzeitig zu arbeiten, also sollte Ihr Haupt-Thread in der Lage sein, auf einen Client zu schreiben, während der andere in select wartet und auf eingehende Daten wartet. Dies setzt natürlich voraus, dass beide Threads Zugriff auf die Client-Liste haben.