2009-05-19 8 views
2

Ich versuche, konzeptionell durch ein Modell für eine Client-Server-Socket-Anwendung zu arbeiten Ich schreibe in C# (Client und Server). Mein Server muss viele Clients gleichzeitig behandeln, vorzugsweise mehrere Anfragen von einem Client gleichzeitig. Ich habe einen Container für meine Kommunikation ausgearbeitet, wo ich am Anfang jeder Nachricht einen Header fester Länge senden werde, der (unter anderem) die Länge der Nachricht enthalten wird. Ich habe etwas Erfahrung mit Socket-Programmierung in C#, so dass ich Asynchrone Sockets verwenden kann.C# Client-Server-Protokoll/Modell Frage

Die Hauptsache, mit der ich konzeptionell Probleme habe, ist, dass ich sowohl den Client als auch den Server brauche, um Nachrichten jederzeit empfangen zu können. Der Client baut eine Verbindung auf und bleibt "angemeldet" (wie ein IM-Client) und muss zu beliebigen Zeiten Daten empfangen und zu beliebigen Zeiten Anfragen stellen. Ich wollte auch als Teil meines Protokolls eine Antwort auf jede Anfrage erhalten (sei es vom Server zum Client oder vom Client zum Server).

Ich würde gerne in der Lage sein, einen einzigen Sockel zu verwenden, wenn möglich. Ich glaube, ich könnte dies mit zwei Sockets machen, einen für Server-> Client-Anfragen und einen für Client-> Server-Anfragen, aber ich möchte nicht die zusätzlichen Komplikationen beim Umgang mit zwei Ports/Verbindungen. Mit einem einzigen Socket bin ich jedoch nicht sicher, wie ich Sendeanforderungen verwalten und Antworten erhalten soll, wenn sie verschachtelt werden könnten.

Ich kann keine Beispiele für einen ähnlichen Server oder Client in meiner Suche finden. Danke an jeden der etwas anbietet.

Antwort

2

Wenn Sie TCP verwenden, müssen Sie unbedingt einen Socket pro Client auf der Serverseite sowie den Socket für Verbindungen haben. Für UDP kannst du das alles mit einem Socket machen. Unabhängig davon, tun Sie folgendes für entweder Ihren einen UDP-Socket oder für jeden TCP-Client-Socket:

Haben Sie eine BeginReceive gehen immer und BeginWrite wenn Sie das notwendige Ereignis auftritt (dh der Benutzer drückt eine Taste oder eine Nachricht kam in dass du etwas tun musst).

EDIT: Als Antwort auf Ihren Kommentar, wäre die Art, wie ich damit umgehen würde, eine Anfrage ID in Ihrer Kopfzeile enthalten. Wenn Sie eine Anfrage senden (von beiden Seiten), müssen Sie einen eindeutigen Wert für diese Nachricht angeben. Verwenden Sie eine Dictionary<RequestId, RequestState> (mit entsprechenden Ersetzungen für die Typen) und sehen Sie, ob eine eingehende Nachricht sich auf eine bereits bestehende Anfrage bezieht. Darüber hinaus können Sie festlegen, dass alle IDs mit dem hohen Bit gesetzt Anfragen an dem Client mit Ursprung und IDs mit dem hohen Bit gelöscht sind vom Server Kollisionen zu vermeiden:

Server      Client 
Request 0x00 -------------> Starts processing 

Starts processing <------- (unrelated) Request 0x80 

Request 0x00 complete <---- Sends response to 0x00 

Sends response to 0x80 ---> Request 0x80 complete 

Dies ist das System, das OSCAR AOL-Protokoll verwendet für (möglicherweise langsame) Stateful-Anfragen.

EDIT2: Hm, ok ... willst du es wirklich blockieren? Etwas, was Sie tun könnten, ist ein separater Thread behandeln die Send/Receive Anrufe. Dieser Thread würde mit dem Haupt-Thread über eine Thread-sichere "Nachricht senden" -Warteschlange und eine ähnliche "Nachrichten empfangen" -Pseudo-Warteschlange kommunizieren. Der Grund, warum ich Letzteres eine Pseudo-Warteschlange nenne, ist, dass Sie die Möglichkeit haben möchten, Nachrichten aus der Warteschlange herauszunehmen. Der Hauptthread würde eine Nachricht in die Sendewarteschlange schreiben und dann in der Empfangswarteschlange blockieren. Jedes Mal, wenn der Socket-Thread die Empfangswarteschlange aktualisiert, wird der Haupt-Thread aufwachen und prüfen, ob die gewünschte Nachricht noch vorhanden ist. Wenn dies der Fall ist, würde es (außer Betrieb) dauern und die gewünschte Operation beenden. Wenn die Nachricht noch nicht vorhanden ist, wird sie in der Warteschlange wieder blockiert. Wenn die Operation schließlich abgeschlossen ist, würde sie einfach den normalen Betrieb wieder aufnehmen und Nachrichten in der Reihenfolge von der Empfangswarteschlange empfangen und sie verarbeiten oder tun, was auch immer sie tut.

Macht das irgendeinen Sinn? Es tut mir leid, wenn es irgendwie verwirrend ist ...

+0

Ja. Aber nehmen wir an, der Server fordert etwas vom Client an, das eine Weile dauert. Währenddessen fragt der Client den Server nach etwas. Wie kann ich in meinem BeginReceive des Servers wissen, dass diese eingehenden Daten die Anfrage vom Client oder die Antwort auf die Anfrage des Servers sind? Daran kämpfe ich. Ich habe die Grundlagen der asynchronen Sockelung. Vielen Dank. – Jarrod

+0

Sorry, all das Gerede über Sockets ließ mich denken, dass dein Problem auf diesem Level war; Wie ist das Update? –

+0

Tut mir leid, ich hatte eine schwierige Zeit, meine Frage zu stellen ... aber genau das ist genau die Art von Sache, die ich versucht habe. Ich hatte überlegt, Anfrage-IDs und das Wörterbuch zu verwenden, aber fragte mich, wie ich eine Methode implementieren konnte, um eine Anforderung zu stellen und zu blockieren, bis das Ergebnis kam und es zurückgab, ohne ein ResetEvent in meinem Anfrage-Statusobjekt zu verwenden. Das wäre schlecht das für jede Anfrage nicht zu erstellen? Vielen Dank. – Jarrod

3

Steckdosen sind bidirektional. Sie benötigen nur einen Socket, um Daten zwischen zwei Endpunkten hin- und herzusenden. Aber auf dem Client müssen Sie wahrscheinlich einen Thread ständig aus dem Socket lesen und eine Komponente informieren oder die empfangenen Daten in eine Warteschlange stellen, damit sie woanders verarbeitet werden, während ein anderer Thread Daten über denselben Socket sendet. Auf diese Weise haben Sie eine asynchrone Kommunikation.

Aber auf der Serverseite müssen Sie einen ServerSocket öffnen, der an einen TCP-Port bindet und Verbindungen an diesem Port akzeptiert; Jede Verbindung, die du akzeptierst, ist ein neuer Socket. Sie haben also einen Socket pro Client, und Sie werden wahrscheinlich einen Thread pro Client benötigen.

Sie müssen ein Protokoll für die Nachrichten erstellen, die Sie über den Socket senden. Sie können entweder Ihr eigenes Protokoll erstellen oder eins nachschlagen, je nachdem, was Sie tun möchten. Normalerweise werden Sie zuerst zwei oder vier Bytes mit der Länge für den Rest der Nachricht senden, sodass die andere Seite weiß, dass viele Bytes aus dem Stream gelesen werden müssen; Das ist eine Nachricht. Der Anfang der Nachricht ist normalerweise der Nachrichtentyp; In einem sehr einfachen Szenario können Sie sagen, dass eine "1" eine Clientanforderung, eine "2" eine Serverantwort, eine "3" ein Clientecho, "4" Serverechoantwort, "5" Server ist echo, "6" ist die Client-Echo-Antwort usw. Auf diese Weise erhalten Sie eine Nachricht, analysieren sie und wissen dann, dass es sich beispielsweise um eine Anfrage eines Clients oder eine Antwort auf eine vom Server gesendete Nachricht handelt.