2010-06-11 4 views
1

J'ai un serveur qui a plusieurs clients C1 ... Cn à chacun desquels une connexion TCP est établie. Il y a moins de 10 000 clients.Accès séquentiel aux sockets asynchrones

Le protocole de message est basé sur une requête/réponse, où le serveur envoie une requête à un client, puis le client envoie une réponse.

Le serveur a plusieurs threads, T1 ... Tm, et chacun d'entre eux peut envoyer des requêtes à l'un des clients. Je veux m'assurer qu'un seul de ces threads puisse envoyer une requête à un client spécifique à un moment donné, tandis que les autres threads voulant envoyer une requête au même client devront attendre.

Je ne veux pas empêcher les threads d'envoyer des requêtes à différents clients en même temps.

E.g. Si T1 envoie une requête à C3, un autre thread T2 ne devrait pas pouvoir envoyer quoi que ce soit à C3 tant que T1 n'a pas reçu sa réponse.

Je pensais à l'aide d'une déclaration de verrouillage simple sur la prise:

lock (c3Socket) 
{ 
    // Send request to C3 
    // Get response from C3 
} 

J'utilise les sockets asynchrones, donc je dois utiliser le moniteur à la place:

Monitor.Enter(c3Socket); // Before calling .BeginReceive() 

Et

Monitor.Exit(c3Socket); // In .EndReceive 

Je suis préoccupé par les choses qui ne vont pas et qui ne lâchent pas le moniteur, bloquant ainsi tous les acc ess à un client. Je pense que mon thread de battement de coeur pourrait utiliser Monitor.TryEnter() avec un timeout et jeter des sockets pour lesquelles il ne peut pas obtenir le moniteur.

Serait-il sensé de rendre les appels Begin et End synchrones afin de pouvoir utiliser l'instruction lock()? Je sais que je sacrifierais la concurrence pour la simplicité dans ce cas, mais cela en vaut peut-être la peine.

Suis-je surplombant quelque chose ici? Toute contribution appréciée.

Répondre

2

Ma réponse ici serait une machine d'état par socket. Les États seraient free et busy:

  • Si la prise est free, le fil de l'expéditeur serait marquer busy et commencer à envoyer au client et en attente de réponse.
  • Vous pouvez configurer un délai d'attente sur cette attente juste au cas où un client serait bloqué d'une manière ou d'une autre.
  • Si l'état est busy - le thread est en veille, en attente de signal.
  • Lorsque le délai d'attente lié au client expire - fermez le socket, le client est mort.
  • Lorsqu'une réponse est correctement reçue/analysée, marquez à nouveau le socket free et signalez/réveillez les threads en attente.
  • Verrouillez uniquement l'interrogation et la manipulation de l'état du socket, et non l'adresse réseau réelle. Cela signifie un verrou par socket, plus une sorte de primitive wait comme une variable conditionnelle (désolé, ne me souviens pas de ce qui est vraiment disponible dans .NET)

Espérons que cela aide.

+0

Je suis déjà en train d'encapsuler le socket dans un objet (que j'ai appelé SocketHolder), donc cela semble être un endroit évident pour mettre le statut libre/occupé. Merci pour l'idée. –

2

Vous ne pouvez certainement pas utiliser l'approche de verrouillage que vous avez décrite. Étant donné que votre système est principalement asynchrone, vous ne pouvez pas savoir quelles opérations de thread s'exécuteront. Cela signifie que vous pouvez appeler Exit sur le mauvais thread (et avoir une SynchronizationLockException levée), ou un autre thread peut appeler Enter et réussir même si ce client est "en cours d'utilisation", juste parce qu'il est arrivé à obtenir le même thread que Enter initialement appelé.

Je serais d'accord avec Nikolai que vous devez maintenir un état supplémentaire à côté de chaque socket pour déterminer si elle est actuellement utilisée ou non. Il va de soi que vous avez besoin d'un verrouillage pour mettre à jour cet état partagé.

+0

Donc, vous dites que le thread sur lequel EndReceive retourne est différent de celui qui a appelé BeginSend? Je n'ai même pas pensé à ça, mais c'est un très bon point. –

+0

Il peut certainement revenir sur un thread différent, votre seule option serait de réorganiser l'appel sur le thread appelé BeginSend. Pour ce faire, cela signifierait bien entendu que le thread BeginSend traîne, à tel point que vous avez perdu le point d'être asynchrone :) –

+0

Cela semble définitivement une bonne idée de le stocker avec le socket. Merci! –