2
votes

C # Socket BeginAccept vs Select?

J'apprends la programmation de socket pour créer une salle de discussion.

Je sais que je pourrais utiliser une socket asynchrone telle que

class ClientState
{
    public Socket socket;
    public byte[] readBuff=new byte[1024];
}

Je pourrais aussi utiliser

namespace Server
{
    class App
    {
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static void Main(string[] args)
        {
            Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 8888);
            listenFd.Bind(iPEndPoint);
            listenFd.Listen(0);
            Console.WriteLine("Server start!");
            listenFd.BeginAccept(AcceptCallback, listenFd);

            while(true)
            {
                Thread.Sleep(1000);
            }
        }

        static void AcceptCallback(IAsyncResult result)
        {
            var listenfd = result.AsyncState as Socket;
            var connfd = listenfd.EndAccept(result);
            var clientState = new ClientState { socket = connfd };
            clients.Add(connfd, clientState);
            connfd.BeginReceive(clientState.readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
            Console.WriteLine($" Client connected!");
            listenfd.BeginAccept(AcceptCallback, listenfd);
        }



        static void EndReceiveCallback(IAsyncResult result)
        {
            var connfd = result.AsyncState as Socket;
            var count = connfd.EndReceive(result);
            if (count <= 0)
            {
                Console.WriteLine("Client disconnected!");
                connfd.Close();
                return;
            }

            connfd.BeginReceive(clients[connfd].readBuff, 0, 1024, 0, EndReceiveCallback, connfd);
            string strFromClient=System.Text.Encoding.Default.GetString(clients[connfd].readBuff,0,count);
            Console.WriteLine($"string from client:{strFromClient}");
            string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+strFromClient;
            byte[] sendBuff= System.Text.Encoding.Default.GetBytes(strFromClientWithTime,0,strFromClientWithTime.Length);
            foreach(var conn in clients.Keys)
            {
                conn.BeginSend(sendBuff, 0, sendBuff.Length, 0, EndSendCallback, conn);
            }
        }

        static void EndSendCallback(IAsyncResult result)
        {
            var connfd = result.AsyncState as Socket;
            connfd.EndSend(result);
        }
    }
}

Je connais la signification de base de ce que font async et select .

Cependant, je ne le sais pas savoir dans quel scénario l'un devrait être meilleur que l'autre.

Modifier:

En fait, je suivais un tutoriel. Il dit que l'utilisation de select est meilleure que l'async parce que la logique est plus claire.

Voici deux exemples:
Celui utilise select:

namespace Server
{
    class App
    {
        static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
        static string ipAddr="127.0.0.1";
        static int port=8888;
        static void Main(string[] args)
        {
            Socket listenFd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPAddress iPAddress = IPAddress.Parse(ipAddr);
            IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, port);
            listenFd.Bind(iPEndPoint);
            listenFd.Listen(0);
            Console.WriteLine("Server start!");
            List<Socket>checkRead=new List<Socket>();

            while(true)
            {
                checkRead.Clear();
                checkRead.Add(listenFd);
                foreach(var clientState in clients.Values)
                {
                    checkRead.Add(clientState.socket);
                }
                Socket.Select(checkRead,null,null,1000);
                foreach(var socket in checkRead)
                {
                    if(socket==listenFd)
                    {
                        ReadListenfd(socket);
                    }
                    else
                    {
                        ReadClientfd(socket);
                    }
                }
            }


        }

        public static void ReadListenfd(Socket listenfd)
        {
            Console.WriteLine("Accept");
            Socket clientfd=listenfd.Accept();
            ClientState state=new ClientState();
            state.socket=clientfd;
            clients.Add(clientfd,state);
        }

        public static bool ReadClientfd(Socket clientfd)
        {
            ClientState state=clients[clientfd];
            int count=0;
            try
            {
                count=clientfd.Receive(state.readBuff);
            }
            catch(SocketException ex)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine($"Receive Socket Exception {ex.ToString()}");
                return false;
            }
            if(count==0)
            {
                clientfd.Close();
                clients.Remove(clientfd);
                Console.WriteLine("Socket close");
                return false;
            }

            string recvStr=System.Text.Encoding.Default.GetString(state.readBuff,0,count);
            Console.WriteLine($"Rec {recvStr}");
            string strFromClientWithTime= DateTime.Now.ToString("hh:mm")+recvStr;
            byte[]sendBytes=System.Text.Encoding.Default.GetBytes(strFromClientWithTime);
            foreach(ClientState cs in clients.Values)
            {
                cs.socket.Send(sendBytes);
            }
            return true;
        }
    }
}

Celui utilise Async:

Socket.Select(checkRead,null,null,1000);

Dans les deux exemples, la classe ClientState est

listenFd.BeginAccept(AcceptCallback, listenFd);

Les deux exemples devraient bien fonctionner. Mais je pensais qu'async devrait être mieux comme l'a dit Damien_The_Unbeliever .

Cependant, l'auteur du tutoriel dans la deuxième édition préfère utiliser select uniquement pour dire que la logique est plus claire.

J'ai fait des heures de recherche mais toujours confus. Est-ce juste une préférence ou y a-t-il quelque chose qui me manque ici.


4 commentaires

Vous devez toujours appeler Accepter (ou similaire) après le retour de Select . Je ne sais pas pourquoi vous pensez que l'un est une alternative à l'autre.


sélectionnez sous Windows: dites simplement non.


@JeroenMostert Y a-t-il une raison typique pour laquelle dire non sur Windows? Je suis nouveau dans la programmation de socket. Difficile pour moi de trouver une ressource ou une référence pertinente. Ou peut-être juste me donner un lien. Merci.


@Damien_The_Unbeliever Veuillez voir mes modifications. Je suis assez nouveau dans la programmation réseau. Peut-être que le titre ou les énoncés de la question sont trompeurs.


3 Réponses :


4
votes

L'utilisation de Select signifie presque toujours que vous interrogez - attacher un fil de discussion pour simplement passer cet appel à plusieurs reprises, traiter les résultats (via une forme d ' Accepter ), peut-être dormir pendant un moment, puis recommencer.

BeginAccept vous permet de dire au "système" de vous avertir lorsque quelque chose d'intéressant s'est produit. En attendant, vous n'utilisez vous-même aucune ressource de traitement, et si tout est correctement conçu, il y a pas de fil du tout faisant un sondage ou une attente.

Je ne pourrais utiliser Select que si j'ai une situation étrange où il y a des "bons" et des "mauvais" moments dans lesquels effectuer accepte. De cette façon, vous pouvez vous assurer qu'il n'y a pas d'appels d'acceptation en suspens pendant les «mauvais» moments. Mais ce serait un domaine de niche assez rare et j'espère que vous auriez déjà identifié cette situation.


0 commentaires

2
votes

La méthode Select reçoit une liste de sockets qui lient et écoutent les demandes entrantes. Lorsque l'appel revient, cette liste n'aura que des sockets qui ont des demandes entrantes en attente d'être acceptées comme indiqué ici .

L'une des principales différences que je peux mettre en évidence est le thread exclusif qui est mis en place sur le gestionnaire d'événement OnCompleted lors de l'utilisation de AcceptAsync au lieu de Accepter qui est le thread actuel qui sera utilisé pour travailler avec le socket qui a été créé avec ce résultat d'acceptation.


0 commentaires

3
votes

Vous pouvez comparer les comportements 1.synchrones et 2.asynchrones dans votre salon de discussion avec ceci:

Imaginez que vous ayez une vente aux enchères, une salle de 1000 personnes, des acheteurs potentiels.

  1. Synchrone: le commissaire-priseur parcourt tous les sièges en utilisant un modèle / ordre fixe [votre boucle de sondage], et demande à [sondages] chacun s'il souhaite placer une enchère plus élevée [vérifie la connexion entrante / les données entrantes ]. Si oui, le commissaire-priseur l'enregistre [accepte la connexion ou traite la phrase de discussion tapée], puis passe à la suivante et demande, et ainsi de suite. Une fois que tous les 1000 sont terminés, il répète. Notez qu'aucune conversation n'est autorisée, c'est à dire. personne ne peut enregistrer une offre jusqu'à ce qu'on lui demande, bien que le commissaire-priseur annonce immédiatement les nouvelles offres au fur et à mesure qu'il les reçoit [envoyer tout ce qui est nouveau du serveur de chat à tous les clients].

  2. Asynchrone: la conversation est autorisée. N'importe qui peut crier son offre à tout moment [rappel à votre fn de rappel], même simultanément [le système d'exploitation crée plusieurs threads simultanés et parallèles] - le commissaire-priseur est très alerte, rapide et compétent, il entend donc chaque enchère [ le système d'exploitation gère les entrées] et l'annonce de la meilleure façon qu'il puisse faire [tous les threads placent des données dans votre référentiel de données commun et partagé, dans l'ordre dans lequel les threads se déclenchent, et vous envoyez ces données communes dès que possible] ET il est capable d'entendre même 200 offres simultanées [plusieurs threads]. Aucune marche nécessaire et aucun ordre fixe pour le placement des enchères.

Dans le cas 1, vous auriez probablement une plainte d'un utilisateur aléatoire John: "Pourquoi Lisa commente-t-elle toujours avant moi? Injuste!" [votre ordre de scrutin fixe, de 1 au maximum, Lisa est assise devant John]. Et de tout utilisateur nommé "X" (étant donné que vous alimentez toutes les entrées de chat à partir d'un serveur commun, c'est-à-dire que l'entrée de X fait un aller-retour avec le serveur): "Pourquoi mon entrée de chat apparaît-elle parfois immédiatement [le commissaire-priseur a demandé à la personne X-1 , il demandera à X en nanosec] mais parfois cela prend 2 secondes? [le commissaire-priseur a demandé à la personne X + 1, prend du temps avant qu'il ne revienne, la pile du système d'exploitation maintient X en attente] "

Dans des conditions de forte charge, l'alternative de synchronisation serait une boîte de vitesses à rotation lente et mauvaise. Genre de ... :-)


Petit addendum re. codage asynchrone (pas trop scientifique)

Async est plus difficile à coder, tester et déboguer. Il nécessite un style de codage totalement différent, bien que la partie asynchrone puisse exister dans (à côté) une application autrement synchrone (une qui est toujours pilotée par les événements par les actions de l'utilisateur, comme dans Windows).

La partie asynchrone peut être considérée comme un moteur bien isolé dans votre code; il fonctionne sur des données de gestion globale et persistantes que vous maintenez autour de lui, mais à l'intérieur de la boîte, il est libre de "se déchaîner", en fonction de ce qui vient du monde extérieur = les connexions réseau et les données, qui bombardent le plus profond de la boîte directement, indépendamment de vos propres actions. Ce sont les événements de déclenchement du système d'exploitation, provenant de clients.

Il y a deux caractéristiques importantes à comprendre:

  1. La boîte se compose d'un seul ensemble de code (quelques fonctions), mais cet ensemble peut être déclenché plusieurs fois, simultanément. Chaque fois qu'il est déclenché, une nouvelle instance isolée de celui-ci s'exécute. La seule chose qui l'isole des autres threads similaires est l'ID de thread, qui est unique par instance. Les instances fonctionnent indépendamment les unes des autres, aussi rapidement que le processeur / système d'exploitation peut fonctionner. Cependant, chacun terminera le travail à un moment donné et voudra probablement livrer le résultat (chacun à son tour, dans un ordre inconnu à l'avance) dans vos données globales (par exemple une liste de clients, le chat total de tous les clients, etc.). Cela signifie qu'il doit y avoir un mécanisme de verrouillage, car une seule instance (thread) peut accéder aux données globales à la fois, sinon ce serait un désordre. Votre langage de codage a des outils pour cela (maintenir, verrouiller).

  2. Comme cet ensemble unique de code est piloté par les événements, il ne peut pas savoir à l'avance combien de travail il y aura. Par conséquent, il doit avoir la capacité a) de commencer à faire le travail b) de continuer à faire le travail et c) de terminer de manière propre lorsque le système d'exploitation dit «c'était tout». Mais en cas d'échec, si le système d'exploitation d'une raison quelconque ne dit jamais "c'était tout", il doit y avoir un timeout qui termine les choses de toute façon.

Inutile de mentionner que tout cela est difficile à faire. S'il y a un bogue qui arrête un thread, les autres threads continueront-ils? Comment accédez-vous aux composants internes du thread ayant échoué? Comment marchez-vous en arrière pour voir pourquoi / comment ce thread échoué a été engendré en premier lieu? Etc.

Lors du codage asynchrone, il faut le faire pas à pas; commencez par les implémentations les plus simples; et avoir un environnement externe approprié qui alimente des données de test riches, d'une cadence pas à pas (par exemple une pression sur une touche) à une cadence automatisée très élevée, même simultanément (par exemple, d'autres ordinateurs du LAN, qui alimentent les données sur une base de démarrage / arrêt de l'alimentation).


1 commentaires

Il raconte très clairement les inconvénients de l'utilisation de l'interrogation de synchronisation. Cependant, votre réponse ne mentionne pas les inconvénients de l'utilisation d'async. Je marquerai votre réponse comme acceptée car elle m'aide en effet à bien le savoir. J'espère que vous avez le temps d'assouplir la partie manquante.