27 votes

Premiers pas avec la programmation des sockets en C # - Meilleures pratiques

J'ai vu beaucoup de ressources, ici, sur DONC sur les Sockets. Je crois que aucun d'entre eux couverts les détails que je voulais savoir. Dans mon application, le serveur fait tout traitement et d'envoyer des mises à jour périodiques aux clients.

L'Intention de ce post est de couvrir toutes les idées de base requises lors de l'élaboration d'une prise d'application et de discuter des meilleures pratiques. Ici sont les choses de base que vous verrez avec presque tous les socket en fonction des applications.

1 - la Liaison et à l'écoute sur une socket

Je suis en utilisant le code suivant. Il fonctionne bien sur ma machine. Ai-je besoin de prendre soin de quelque chose d'autre quand je la déployer sur un serveur réel?

IPHostEntry localHost = Dns.GetHostEntry(Dns.GetHostName());
IPEndPoint endPoint = new IPEndPoint(localHost.AddressList[0], 4444);

serverSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, 
                     ProtocolType.Tcp);
serverSocket.Bind(endPoint);
serverSocket.Listen(10);

2 - Réception de données

J'ai utilisé un 255 de la taille du tableau d'octets. Alors, quand je suis à recevoir des données qui est à plus de 255 octets, j'ai besoin d'appeler la méthode de réception jusqu'à ce que je obtenir la totalité des données, droit? Une fois que j'ai eu l'ensemble des données, j'ai besoin d'ajouter tous les octets reçus jusqu'à présent pour obtenir le message complet. Est-ce exact? Ou est-il une meilleure approche?

3 - l'Envoi de données et en spécifiant la longueur de données

Puisqu'il n'existe aucun moyen de TCP pour trouver la longueur du message de recevoir, j'ai l'intention d'ajouter de la longueur du message. Ce sera le premier octet du paquet. Donc, les systèmes client sait quelle quantité de données est disponible à la lecture.

Toute autre meilleure approche?

4 - fermer le client

Lorsque le client est fermé, il va envoyer un message au serveur indiquant la proximité. Serveur de supprimer les données du client à partir de la liste de clients. Voici le code utilisé côté client pour déconnecter le connecteur (partie messagerie n'apparaît pas).

client.Shutdown(SocketShutdown.Both);
client.Close();

Des suggestions ou des problèmes?

5 - la Fermeture du serveur

Le serveur envoie un message à tous les clients indiquant l'arrêt. Chaque client de débrancher la prise quand il reçoit ce message. Les Clients pourront envoyer le message de fermeture de serveur et de les fermer. Une fois que le serveur reçoit le message de fermeture de tous les clients, il débranche la prise et arrêter de les écouter. Appel Disposer sur chaque client sockets pour libérer les ressources. Est-ce la bonne démarche?

6 - client Inconnu déconnexions

Parfois, un client peut déconnecter sans en informer le serveur. Mon plan pour résoudre ce problème est: Quand le serveur envoie des messages à tous les clients, vérifier la prise état. Si il n'est pas connecté, retirez le client à partir de la liste de clients et de fermer la socket du client.

Toute aide serait super!

23voto

jerryjvl Points 9310

Puisque c'est "démarrer" de ma réponse sera bâton avec une mise en œuvre simple, plutôt que d'un très évolutif un. Il est préférable de d'abord se sentir à l'aise avec l'approche la plus simple avant de rendre les choses plus compliquées.

1 - la Liaison et à l'écoute
Votre code semble bien pour moi, personnellement, j'utilise:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));

Plutôt que d'aller dans le DNS, mais je ne pense pas qu'il existe un réel problème de toute façon.

1.5 - Accepter les connexions client
Le simple fait de mentionner ce à des fins d'exhaustivité... je suis en supposant que vous faites cela, sinon vous ne seriez pas obtenir à l'étape 2.

2 - Réception de données
Je voudrais faire le tampon un peu plus de 255 octets, à moins que vous pouvez vous attendre tous vos messages du serveur pour être au plus de 255 octets. Je pense que vous auriez envie d'une mémoire tampon qui est susceptible d'être plus grande que le paquet TCP de taille de sorte que vous pouvez éviter de faire plusieurs lectures pour recevoir un seul bloc de données.

Je dirais que la cueillette de 1500 octets devrait être bon, ou peut-être même 2048 pour un beau chiffre rond.

Alternativement, peut-être que vous pouvez éviter d'utiliser un byte[] pour stocker les fragments de données, et au lieu d'emballer votre côté serveur socket client dans un NetworkStream, enveloppé dans un BinaryReader, de sorte que vous pouvez lire les composants de votre message directement à partir de la prise sans se soucier de la taille du buffer.

3 - l'Envoi de données et de la spécification des données de longueur
Votre approche fonctionne très bien, mais il ne s'impose à l'évidence qu'il est facile de calculer la longueur du paquet avant de commencer à envoyer.

Alternativement, si votre format de message (de l'ordre de ses composants) est conçu de façon que, à tout moment, le client sera en mesure de déterminer s'il devrait y avoir plus de données suivantes (par exemple, le code 0x01 signifie que la prochaine sera un entier et une chaîne de caractères, le code 0x02 signifie que la prochaine sera de 16 octets, etc, etc). Combiné avec l' NetworkStream approche sur le côté client, cela peut être une approche très efficace.

Pour être sur le côté sûr, vous pouvez ajouter de la validation des composants étant reçu à assurez-vous de ne traiter sain d'esprit des valeurs. Par exemple, si vous recevez une indication pour une chaîne de longueur 1 TO vous ont peut-être eu un paquet de corruption de quelque part, et il peut être plus sûr de fermer la connexion et de forcer le client à se re-connecter et 'recommencer'. Cette approche vous donne un très bon fourre-tout comportement en cas de panne imprévue.

4/5 - Clôture le client et le serveur
Personnellement, j'opterais pour juste Close sans plus de messages; lorsqu'une connexion est fermée, vous aurez une exception sur tout blocage en lecture/écriture à l'autre extrémité de la connexion que vous aurez à répondre.

Depuis que vous avez à répondre à un "inconnu déconnexions" de toute façon à obtenir une solution robuste, prise de déconnexion plus compliqué, c'est généralement inutile.

6 - Inconnu déconnexions
Je ne voudrais pas faire confiance à même le socket état... il est possible pour une connexion à mourir quelque part le long du chemin entre client / serveur, sans que le client ou le serveur ne s'en aperçoive.

La seule garantie pour raconter une connexion qui est décédé de façon inattendue est la prochaine fois que vous essayez d' envoyer quelque chose le long de la connexion. À ce point, vous trouverez toujours une exception indiquant l'échec si quelque chose s'est mal passé avec la connexion.

En conséquence, le seul moyen infaillible pour détecter tous les imprévus de connexions est de mettre en œuvre un "ping" mécanisme, où, idéalement, le client et le serveur va envoyer périodiquement un message à l'autre extrémité que seuls les résultats dans un message de réponse indiquant que le "ping" a été reçu.

Pour optimiser hors inutile de ping, vous voudrez peut-être avoir un "temps" qui n'envoie un ping lorsque aucun autre trafic a été reçu à partir de l'autre extrémité, pour un montant fixé de temps (par exemple, si le dernier message du serveur est de plus de x secondes, le client envoie une commande ping pour s'assurer que la connexion n'est pas mort et sans préavis).

Plus avancé
Si vous voulez une grande évolutivité, vous aurez à examiner les méthodes asynchrones pour toutes les opérations de socket (Accepter / Envoyer / Recevoir). Ce sont les "début/Fin de" variantes, mais ils sont beaucoup plus compliqués à utiliser.

Je déconseille d'essayer jusqu'à ce que vous avez la version simple et de travail.

Notez également que si vous n'êtes pas de planification à l'échelle plus loin que quelques dizaines de clients, ce n'est pas réellement un problème en soit. Async techniques sont vraiment nécessaire que si vous avez l'intention d'échelle dans les milliers ou des centaines de milliers de clients liés, tout en n'ayant pas de votre serveur de mourir sur le coup.

J'en ai probablement oublié tout un tas d'autres suggestions importantes, mais cela devrait être suffisant pour vous un assez robuste et fiable de la mise en œuvre de commencer avec

11voto

dtb Points 104373

1 - la Liaison et à l'écoute sur une socket

Semble bien pour moi. Votre code aura pour effet de lier la prise à une seule adresse IP si. Si vous voulez simplement écouter sur toutes les adresses IP/interface réseau, utilisez IPAddress.Any:

serverSocket.Bind(new IPEndPoint(IPAddress.Any, 4444));

À l'avenir, vous souhaitez peut-être en charge le protocole IPv6. À écouter sur toutes les adresses IPv6, utilisez IPAddress.IPv6Any à la place de IPAddress.Any.

Notez que vous ne pouvez pas écouter sur toutes les adresses IPv4 et aucune adresse IPv6 en même temps, sauf si vous utilisez une Double Pile de Socket. Cela va vous obliger à annuler l' IPV6_V6ONLY option de socket:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, 0);

Pour activer Teredo avec votre support, vous devez définir l' PROTECTION_LEVEL_UNRESTRICTED option de socket:

serverSocket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)23, 10);

2 - Réception de données

Je vous recommande d'utiliser un NetworkStream qui encapsule la prise en Stream , au lieu de lire les morceaux manuellement.

La lecture d'un nombre fixe d'octets est un peu gênant si:

using (var stream = new NetworkStream(serverSocket)) {
   var buffer = new byte[MaxMessageLength];
   while (true) {
      int type = stream.ReadByte();
      if (type == BYE) break;
      int length = stream.ReadByte();
      int offset = 0;
      do
         offset += stream.Read(buffer, offset, length - offset);
      while (offset < length);
      ProcessMessage(type, buffer, 0, length);
   }
}

NetworkStream brille vraiment, c'est que vous pouvez l'utiliser comme n'importe quel autre Stream. Si la sécurité est importante, il suffit de conclure l' NetworkStream en SslStream pour authentifier le serveur et (éventuellement) les clients avec X. 509 certificats. La Compression fonctionne de la même manière.

var sslStream = new SslStream(stream, false);
sslStream.AuthenticateAsServer(serverCertificate, false, SslProtocols.Tls, true);
// receive/send data SSL secured

3 - l'Envoi de données et en spécifiant la longueur de données

Votre démarche doit fonctionner, bien que vous avez probablement ne veut pas aller en bas de la route à réinventer la roue et de la conception d'un nouveau protocole pour cela. Regarder BIP ou peut-être même quelque chose de simple comme protobuf.

En fonction de vos objectifs, il pourrait être utile de réfléchir sur le choix de l'abstraction au-dessus de sockets comme WCF ou quelque autre mécanisme RPC.

4/5/6 - Fermeture et d'Inconnu déconnexions

Ce jerryjvl dit :-) Le seul fiable mécanisme de détection sont les pings ou de l'envoi de keep-alives lorsque la connexion est inactive.

Alors que vous avez à traiter avec des inconnus déconnexions en tout cas, je serais personnellement garder un protocole élément à fermer une connexion, d'un commun accord, au lieu de simplement fermer sans avertissement.

2voto

Dzmitry Huba Points 3333

Pensez à utiliser des sockets asynchrones. Vous pouvez trouver plus d'informations sur le sujet dans

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X