3 votes

C# Networking : Le serveur se bloque après avoir reçu plus de 65535 octets

UPDATE : En raison de problèmes avec les administrateurs ici sur Stackoverflow, j'ai posté une version très réduite du même problème sur le forum MSDN. Le texte ci-dessous utilisait MyNetworking.dll, mais ce n'est pas le problème. Voici un truc Client-Serveur très allégé et le problème est exactement le même. N'hésitez pas à l'essayer =) http://social.msdn.microsoft.com/Forums/en-US/netfxnetcom/thread/d3d33eb9-7dce-4313-929e-a8a63d0f1e03 /MISE À JOUR

J'ai donc une erreur étrange.

Normalement, nous avons une DLL qui gère notre réseau. Appelons-la MyNetworking.dll . Nous l'utilisons partout dans nos serveurs et chez nos clients, et ce depuis 5 ans. Je n'ai jamais eu de problème avec lui, jusqu'à présent.

J'ai un "XMLPoller" qui lit du XML à partir d'une base de données MySQL, le sérialise dans un tableau byte[] et l'envoie sur le réseau. Ces messages XML particuliers représentent 627 octets sous forme sérialisée.

Le XMLPoller se connecte à un port sur un "serveur distant" (qui se trouve être localhost) et envoie les paquets, un par un. La connexion est fermée au numéro de paquet 105. 104 paquets sont envoyés par XMLPoller et reçus par le serveur. 104 x 627 = 65208 octets. Mais au paquet 105, alors que le nombre total d'octets envoyés serait de 65835, la connexion est fermée avec cette erreur :

System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
       at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
       at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)

Voici l'erreur qui apparaît sur l'écran serveur . Cependant, j'ai parcouru le XMLPoller (client), et je vois quand les 627 derniers octets sont envoyés (donc jusqu'à 65835 octets) et je ne vois pas d'erreurs sur le client, il passe l'envoi sans problème.

MISE À JOUR 20:15 HEURE SUÉDOISE

J'obtiens également cette erreur dans le Client lorsque je débogue un peu plus :

Unable to read data from the transport connection: An established connection was aborted by the software in your host machine.

Je pense avoir confirmé que c'est dans le client que l'erreur existe. Je parcours le code et avant qu'aucune exception ne soit capturée sur le serveur, j'obtiens une exception sur le client comme indiqué ci-dessus.

/ENDUPDATE

Il me semble que le serveur ne le reçoit jamais et qu'il obtient l'erreur ci-dessus. Le serveur ferme la connexion à cause de quelque chose qui se passe sur le client. Cependant, l'erreur sur le client se trouve dans TCPInput ; le flux qui lit les données est mort pour une raison quelconque ?

Je ne mets rien en mémoire tampon dans MyNetworking.dll.

Lorsque j'obtiens une nouvelle connexion sur une socket (sur le serveur), je fais ce code :

public void setConnected(Socket thisClient)
{
    NetworkStream stream = new NetworkStream(thisClient);
 socket = thisClient;
 output = new TCPOutput(stream, outputHandler,this);
 remoteIP = this.socket.RemoteEndPoint.ToString();
 changeState(State.Connected);
    try
    {
        stream.BeginRead(inputBuffer, 0, 5000, new AsyncCallback(OnDataReceived), null);
    }
    catch (Exception e)
    {
        this.disconnect();
    }
}

et ensuite, le OnDataReceived (où les données sont effectivement reçues) :

public void OnDataReceived(IAsyncResult asyn)
        {
            int nbrRead = 0;
            byte[] tmp = null;
            try
            {
                nbrRead = stream.EndRead(asyn);
                tmp = new byte[nbrRead];
            }
            catch (Exception e)
            {
                // *** HERE IS WHERE THE EXCEPTION IS CAUGHT ***
                System.Diagnostics.Debugger.Log(0, "Bla1", e.ToString());
                this.disconnect();
            }
            if (nbrRead > 0)
            {
                try
                {
                    Array.Copy(inputBuffer, 0, tmp, 0, nbrRead);
                }
                catch(Exception e)
                {
                    System.Diagnostics.Debugger.Log(0, "Bla2", e.ToString());
                    this.disconnect();
                }
                preProcessMessage(tmp);
                try
                {
                    stream.BeginRead(inputBuffer, 0, 5000, new AsyncCallback(OnDataReceived), new object());
                }
                catch(Exception e)
                {
                    System.Diagnostics.Debugger.Log(0, "Bla3", e.ToString());
                    this.disconnect();
                }
            }
            else
                this.disconnect();

        }

Pour l'instant, je ne sais pas trop ce qui se passe... Des idées ?

UPDATE 1 :

Code client pour l'envoi de données :

public bool sendData(byte[] data)
        {
            if(this.state == State.Connected)
            {
                if (data != null && data.Length > 0)
                {
                    try
                    {
                        //data = Crypto.Encrypt("a1s2d3", data);
                        outputStream.Write(data, 0, data.Length);
                    }
                    catch(Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
                    }
                    //parent.outDataLog(data.Length);
                }
            }
            return true;
        }

Mise à jour 2

J'ai essayé de faire un Flush sur le flux sortant du client - aucun effet :

public bool sendData(byte[] data)
{
    if(this.state == State.Connected)
    {
        if (data != null && data.Length > 0)
        {
            try
            {
                //data = Crypto.Encrypt("a1s2d3", data);
                outputStream.Write(data, 0, data.Length);
                outputStream.Flush();
            }
            catch(Exception e)
            {
                System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
            }
            //parent.outDataLog(data.Length);
        }
    }
    return true;
}

MISE À JOUR 3 : Mise en ligne d'un code supplémentaire conformément à la demande

Ce code est ancien et n'est pas le plus beau du monde, je le sais. Mais il fonctionne très bien depuis 5 ans =)

ClientHandler.cs (ce que le client utilise pour l'envoi, etc.)

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace tWorks.tNetworking.tNetworkingCF
{
    /// <summary>
    /// Summary description for connectionHandler.
    /// </summary>
    public class ClientHandler
    {
        #region Fields (17) 

        string address;
        Connector connector;
        DataHandler dataHandler;
        int id;
        TCPInput input;
        int interval;
        string localAddress;
        IPEndPoint localPoint;
        int localPort;
        NetworkStream outputStream;
        public TTCPClientInterface parent;
        int port;
        tWorks.tNetworking.Protocol.Protocol protocol;
        bool reconnect;
        string remoteIP;
        Socket socket;
        public State state;

        #endregion Fields 

        #region Enums (1) 

        public enum State {Disconnected,Connecting,Connected}

        #endregion Enums 

        #region Constructors (4) 

        public ClientHandler(int id, TTCPClientInterface parent, Socket socket, tWorks.tNetworking.Protocol.Protocol protocol)
        {
            this.id=id;
            this.parent = parent;
            this.protocol = protocol;
            dataHandler = new DataHandler(protocol, this);
            setConnected(socket);
        }

        public ClientHandler(int id, TTCPClientInterface parent, Protocol.Protocol protocol)
        {
            this.id=id;
            this.parent = parent;
            this.protocol = protocol;
            dataHandler = new DataHandler(protocol, this);
            state = State.Disconnected;
        }

        public ClientHandler(int id, TTCPClientInterface parent, Socket socket)
        {
            this.id=id;
            this.parent = parent;
            setConnected(socket);
        }

        public ClientHandler(int id, TTCPClientInterface parent)
        {
            this.id=id;
            this.parent = parent;
            this.protocol = null;
            changeState(State.Disconnected);
        }

        #endregion Constructors 

        #region Delegates and Events (4) 

        // Delegates (2) 

        public delegate void ConnectionLostDelegate(string message);
        public delegate void exceptionDelegate(Exception ex);
        // Events (2) 

        public event exceptionDelegate ConnectionFailed;

        public event ConnectionLostDelegate ConnectionLostEvent;

        #endregion Delegates and Events 

        #region Methods (17) 

        // Public Methods (16) 

        public void connect(string address, int port, int retryInterval, bool reestablish)
        {
            System.Random rand = new Random();
            localPort = rand.Next(40000, 60000);
            IPAddress localIP = Dns.GetHostEntry(Dns.GetHostName()).AddressList[0]; // new IPAddress(Dns.GetHostByName(Dns.GetHostName()).AddressList[0].Address);
            connect(address, port, retryInterval, reestablish, localIP.ToString(), localPort);            
        }

        /// <summary>
        /// Will connect to the address and port specified. If connection failed a new attempt will be made according to the Interval parameter. 
        /// If connection is lost attempts to reastablish it will be made if Reestablish is set to true.
        /// </summary>
        /// <param name="address"></param>
        /// <param name="port"></param>
        /// <param name="retryInterval"></param>
        /// <param name="reestablish"></param>
        public void connect(string address, int port, int retryInterval, bool reestablish, string localAddress, int localPort)
        {
            this.reconnect = reestablish;
            this.address = address;
            this.port = port;
            this.interval = retryInterval;
            this.localAddress = localAddress;
            this.localPort = localPort;
            changeState(State.Connecting);
            connector = new Connector(address, port, this, interval, localPoint, reestablish);
            connector.Connect();
        }

        public void disconnect()
        {
            reconnect = false;
            if (connector != null)
            {
                connector.stopConnecting();
            }
            setDisconnected();
        }

        public void dispose()
        {

        }

        public void failedConnect(Exception e)
        {
            if (ConnectionFailed != null)
                ConnectionFailed(e);
        }

        public int getID()
        {
            return this.id;
        }

        public string getIP()
        {
            return remoteIP;
        }

        public bool isConnected()
        {
            return this.state == State.Connected;
        }

        public void outDataLog(int nbrBytes)
        {
            parent.outDataLog(nbrBytes, id);
        }

        public void preProcessMessage(byte[] data)
        {
            //data = Crypto.Decrypt("a1s2d3", data);
            if(protocol != null)
                dataHandler.addData(data);
            else
                processMessage(data);
        }

        public void processMessage(byte[] data)
        {

            parent.processMessage(data,this);
        }

        public bool sendData(byte[] data)
        {
            if(this.state == State.Connected)
            {
                if (data != null && data.Length > 0)
                {
                    try
                    {
                        //data = Crypto.Encrypt("a1s2d3", data);
                        outputStream.Write(data, 0, data.Length);
                        outputStream.Flush();
                    }
                    catch(Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine("ClientHandler.sendData> " + e.ToString());
                    }
                    //parent.outDataLog(data.Length);
                }
            }
            return true;
        }

        public void setConnected(Socket thisClient)
        {
            socket = thisClient;
            outputStream = new NetworkStream(thisClient);
            input = new TCPInput(outputStream, this);
            remoteIP = this.socket.RemoteEndPoint.ToString();
            changeState(State.Connected);
        }

        public void setDisconnected()
        {
            try
            {
                if (this.state == State.Connected)
                {
                    changeState(State.Disconnected);
                    //socket.Shutdown(SocketShutdown.Both);
                    socket.Close();
                }
            }
            catch { }
            if (reconnect)
                this.connect(address, port, interval, true, localAddress, localPort);
        }

        public void stopConnect()
        {
            connector.stopConnecting();
            changeState(State.Disconnected);
        }

        public override string ToString()
        {
            string returnString = "(D)";
            if(this.state == State.Connected)
                returnString = this.getIP();
            return returnString;
        }
        // Private Methods (1) 

        private void changeState(State state)
        {
            if (this.state == State.Connected && state == State.Disconnected)
            {
                if (ConnectionLostEvent != null)
                    ConnectionLostEvent("Uppkoppling bröts.");
            }
            this.state = state;
            parent.connStateChange(this);
        }

        #endregion Methods 
    }
}

Il s'agit de TCPInput.cs qui écoute les données entrantes et les transmet au ClientHandler (voir ci-dessus) :

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace tWorks.tNetworking.tNetworkingCF
{
    public class TCPInput
    {
        NetworkStream stream;
        ClientHandler client;

        public TCPInput(NetworkStream nS, ClientHandler client)
        {
            stream = nS;
            this.client = client;
            Thread t = new Thread(new ThreadStart(run));
            t.IsBackground = true;
            t.Name = "TCPInput";
            t.Start();
        }

        public void run()
        {
            bool continueRead = true;
            byte[] readBuffer = new byte[32768];
            byte[] receivedBuffer = null;

            int nbrBytesRead = 0;
            int receivedBufferPos = 0;
            while(continueRead)
            {
                try
                {
                    nbrBytesRead = 0;
                    nbrBytesRead = stream.Read(readBuffer, 0, 10000);
                    receivedBuffer = new byte[nbrBytesRead];
                }
                catch (Exception e)
                {
                    System.Diagnostics.Debug.WriteLine("TCPInput> Exception when stream.Read: " + e.ToString());
                    continueRead = false;
                }
                if(nbrBytesRead > 0)
                {
                    try
                    {
                        Array.Copy(readBuffer, 0, receivedBuffer, receivedBufferPos, nbrBytesRead);
                    }
                    catch (Exception e)
                    {
                        System.Diagnostics.Debug.WriteLine("TCPInput> Exception when Array.Copy: " + e.ToString());
                        continueRead = false; 
                    }
                    client.preProcessMessage(receivedBuffer);
                }
                else
                {
                                // *** I can break here, the nbrOfBytes read is 0 when this whole thing explodes =)
                    System.Diagnostics.Debug.WriteLine("TCPInput> Number of bytes read == 0! Setting continueRead = false");
                    continueRead = false;
                }

            }
            client.setDisconnected();
        }
    }
}

1voto

Ce chiffre ("envoyant ainsi jusqu'à 65835 octets") est magiquement proche de 2^16-1 (65535) -- on dirait qu'il n'y a qu'un paquet de plus !

(Je suppose que c'est simplement la taille plus grande qui a fait exploser les choses -- cela peut être testé de manière fiable).

Je soupçonne qu'une variable non signée de 16 bits est utilisée (dans la bibliothèque) lorsque vous avez besoin de quelque chose avec une plus grande portée. Peut-être pouvez-vous "vider" périodiquement les internes de la bibliothèque ou effectuer l'opération en plusieurs connexions ? (Ok, j'essaie juste de lancer quelques idées de 'hack' rapide :-)

1voto

Hans Passant Points 475940

Le problème se situe dans votre autre code, le "client". Il ferme la connexion après avoir envoyé tous les "paquets". Vous devez attendre que le serveur les ait tous reçus. Une approche simple, en dehors de la négociation explicite, consiste à attendre que le serveur ferme la connexion.

1voto

Ted Points 4433

Ainsi, après de nombreux tests et discussions avec mon complice, nous avons découvert qu'au lieu d'utiliser le port 21 et de prendre par exemple le port 22, le problème disparaît.

Je n'ai aucune idée de la raison pour laquelle il se comporte ainsi, mais c'est le cas...

0voto

Kirk Points 804

Votre article soulève des questions pour moi. Par exemple, pourquoi choisissez-vous des ports bien connus pour ce service ? Je ne crois pas aux coïncidences et je soupçonne que votre utilisation du terme "partenaire dans le crime" a plus de vérité que je ne voudrais y être associé.

Je me demande également pourquoi vous pensez qu'il s'agit d'un bogue de Windows et non d'un bogue de MyNetowrking.dll. Certes, vous l'utilisez depuis cinq ans. Mais il n'a toujours pas fait l'objet de l'examen minutieux auquel Microsoft soumet son code.

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