web-dev-qa-db-fra.com

Envoi et réception de données sur un réseau à l'aide de TcpClient

Je dois développer un service qui se connecte à un serveur TCP. Les tâches principales consistent à lire les messages entrants et à envoyer des commandes au serveur en dix minutes, comme une commande de synchronisation. Par exemple, j'ai utilisé l'objet TcpClient comme indiqué ci-dessous:

...
TcpClient tcpClient = new TcpClient();
tcpClient.Connect("x.x.x.x", 9999);
networkStream = tcpClient.GetStream();
clientStreamReader = new StreamReader(networkStream);
clientStreamWriter = new  StreamWriter(networkStream);
while(true)
{
   clientStreamReader.Read()
}

De plus, quand j'ai besoin d'écrire quelque chose dans n'importe quelle méthode, j'utilise:

 clientStreamWriter.write("xxx");

Est-ce que cet usage est correct? Ou y a-t-il un meilleur moyen?

35
dankyy1

Soyez averti - c'est une "solution" très ancienne et encombrante.

En passant, vous pouvez utiliser la technologie de sérialisation pour envoyer des chaînes, des nombres ou tout objet prenant en charge la sérialisation (la plupart des classes et des structures de stockage de données .NET sont [Serializable]). Dans ce cas, vous devez d’abord envoyer au flux la longueur de 32 bits d’Int32, puis lui envoyer des données sérialisées (System.Runtime.Serialization.Formatters.Binary.BinaryFormatter).

De l’autre côté ou de la connexion (des deux côtés en fait), vous devriez définitivement avoir un tampon d’octets [] que vous ajouterez et un trim-left au moment de l’exécution lorsque les données arriveront.

Quelque chose comme ça j'utilise:

namespace System.Net.Sockets
{
    public class TcpConnection : IDisposable
    {
        public event EvHandler<TcpConnection, DataArrivedEventArgs> DataArrive = delegate { };
        public event EvHandler<TcpConnection> Drop = delegate { };

        private const int IntSize = 4;
        private const int BufferSize = 8 * 1024;

        private static readonly SynchronizationContext _syncContext = SynchronizationContext.Current;
        private readonly TcpClient _tcpClient;
        private readonly object _droppedRoot = new object();
        private bool _dropped;
        private byte[] _incomingData = new byte[0];
        private Nullable<int> _objectDataLength;

        public TcpClient TcpClient { get { return _tcpClient; } }
        public bool Dropped { get { return _dropped; } }

        private void DropConnection()
        {
            lock (_droppedRoot)
            {
                if (Dropped)
                    return;

                _dropped = true;
            }

            _tcpClient.Close();
            _syncContext.Post(delegate { Drop(this); }, null);
        }

        public void SendData(PCmds pCmd) { SendDataInternal(new object[] { pCmd }); }
        public void SendData(PCmds pCmd, object[] datas)
        {
            datas.ThrowIfNull();
            SendDataInternal(new object[] { pCmd }.Append(datas));
        }
        private void SendDataInternal(object data)
        {
            if (Dropped)
                return;

            byte[] bytedata;

            using (MemoryStream ms = new MemoryStream())
            {
                BinaryFormatter bf = new BinaryFormatter();

                try { bf.Serialize(ms, data); }
                catch { return; }

                bytedata = ms.ToArray();
            }

            try
            {
                lock (_tcpClient)
                {
                    TcpClient.Client.BeginSend(BitConverter.GetBytes(bytedata.Length), 0, IntSize, SocketFlags.None, EndSend, null);
                    TcpClient.Client.BeginSend(bytedata, 0, bytedata.Length, SocketFlags.None, EndSend, null);
                }
            }
            catch { DropConnection(); }
        }
        private void EndSend(IAsyncResult ar)
        {
            try { TcpClient.Client.EndSend(ar); }
            catch { }
        }

        public TcpConnection(TcpClient tcpClient)
        {
            _tcpClient = tcpClient;
            StartReceive();
        }

        private void StartReceive()
        {
            byte[] buffer = new byte[BufferSize];

            try
            {
                _tcpClient.Client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, DataReceived, buffer);
            }
            catch { DropConnection(); }
        }

        private void DataReceived(IAsyncResult ar)
        {
            if (Dropped)
                return;

            int dataRead;

            try { dataRead = TcpClient.Client.EndReceive(ar); }
            catch
            {
                DropConnection();
                return;
            }

            if (dataRead == 0)
            {
                DropConnection();
                return;
            }

            byte[] byteData = ar.AsyncState as byte[];
            _incomingData = _incomingData.Append(byteData.Take(dataRead).ToArray());
            bool exitWhile = false;

            while (exitWhile)
            {
                exitWhile = true;

                if (_objectDataLength.HasValue)
                {
                    if (_incomingData.Length >= _objectDataLength.Value)
                    {
                        object data;
                        BinaryFormatter bf = new BinaryFormatter();

                        using (MemoryStream ms = new MemoryStream(_incomingData, 0, _objectDataLength.Value))
                            try { data = bf.Deserialize(ms); }
                            catch
                            {
                                SendData(PCmds.Disconnect);
                                DropConnection();
                                return;
                            }

                        _syncContext.Post(delegate(object T)
                        {
                            try { DataArrive(this, new DataArrivedEventArgs(T)); }
                            catch { DropConnection(); }
                        }, data);

                        _incomingData = _incomingData.TrimLeft(_objectDataLength.Value);
                        _objectDataLength = null;
                        exitWhile = false;
                    }
                }
                else
                    if (_incomingData.Length >= IntSize)
                    {
                        _objectDataLength = BitConverter.ToInt32(_incomingData.TakeLeft(IntSize), 0);
                        _incomingData = _incomingData.TrimLeft(IntSize);
                        exitWhile = false;
                    }
            }
            StartReceive();
        }


        public void Dispose() { DropConnection(); }
    }
}

C'est juste un exemple, vous devriez le modifier pour votre usage.

21
AgentFire

Tout d'abord, je vous recommande d'utiliser WCF, .NET Remoting ou une autre abstraction de communication de niveau supérieur. La courbe d'apprentissage pour les sockets "simples" est presque aussi élevée que celle de WCF, car il existe de nombreux pièges non évidents lors de l'utilisation directe de TCP/IP.

Si vous décidez de continuer sur le chemin TCP/IP, consultez ma . NET TCP/IP FAQ , en particulier les sections relatives à message encadré et spécifications du protocole d'application). .

Utilisez également des API de socket asynchrones. Les API synchrones ne sont pas mises à l'échelle et, dans certaines situations d'erreur, peuvent provoquer des blocages. Les API synchrones ne donnent que peu de code d'exemple, mais le code de qualité de la production du monde réel utilise les API asynchrones.

21
Stephen Cleary

J'ai eu de la chance en utilisant directement l'objet socket (plutôt que le client TCP). Je crée un objet Server qui ressemble à ceci (j'ai édité des éléments tels que le traitement des exceptions par souci de brièveté , mais j’espère que l’idée se présentera.) ...

public class Server()
{
    private Socket sock;
    // You'll probably want to initialize the port and address in the
    // constructor, or via accessors, but to start your server listening
    // on port 8080 and on any IP address available on the machine...
    private int port = 8080;
    private IPAddress addr = IPAddress.Any;

    // This is the method that starts the server listening.
    public void Start()
    {
        // Create the new socket on which we'll be listening.
        this.sock = new Socket(
            addr.AddressFamily,
            SocketType.Stream,
            ProtocolType.Tcp);
        // Bind the socket to the address and port.
        sock.Bind(new IPEndPoint(this.addr, this.port));
        // Start listening.
        this.sock.Listen(this.backlog);
        // Set up the callback to be notified when somebody requests
        // a new connection.
        this.sock.BeginAccept(this.OnConnectRequest, sock);
    }

    // This is the method that is called when the socket recives a request
    // for a new connection.
    private void OnConnectRequest(IAsyncResult result)
    {
        // Get the socket (which should be this listener's socket) from
        // the argument.
        Socket sock = (Socket)result.AsyncState;
        // Create a new client connection, using the primary socket to
        // spawn a new socket.
        Connection newConn = new Connection(sock.EndAccept(result));
        // Tell the listener socket to start listening again.
        sock.BeginAccept(this.OnConnectRequest, sock);
    }
}

Ensuite, j'utilise une classe de connexion distincte pour gérer la connexion individuelle avec l'hôte distant. Cela ressemble à quelque chose comme ça ...

public class Connection()
{
    private Socket sock;
    // Pick whatever encoding works best for you.  Just make sure the remote 
    // Host is using the same encoding.
    private Encoding encoding = Encoding.UTF8;

    public Connection(Socket s)
    {
        this.sock = s;
        // Start listening for incoming data.  (If you want a multi-
        // threaded service, you can start this method up in a separate
        // thread.)
        this.BeginReceive();
    }

    // Call this method to set this connection's socket up to receive data.
    private void BeginReceive()
    {
        this.sock.BeginReceive(
                this.dataRcvBuf, 0,
                this.dataRcvBuf.Length,
                SocketFlags.None,
                new AsyncCallback(this.OnBytesReceived),
                this);
    }

    // This is the method that is called whenever the socket receives
    // incoming bytes.
    protected void OnBytesReceived(IAsyncResult result)
    {
        // End the data receiving that the socket has done and get
        // the number of bytes read.
        int nBytesRec = this.sock.EndReceive(result);
        // If no bytes were received, the connection is closed (at
        // least as far as we're concerned).
        if (nBytesRec <= 0)
        {
            this.sock.Close();
            return;
        }
        // Convert the data we have to a string.
        string strReceived = this.encoding.GetString(
            this.dataRcvBuf, 0, nBytesRec);

        // ...Now, do whatever works best with the string data.
        // You could, for example, look at each character in the string
        // one-at-a-time and check for characters like the "end of text"
        // character ('\u0003') from a client indicating that they've finished
        // sending the current message.  It's totally up to you how you want
        // the protocol to work.

        // Whenever you decide the connection should be closed, call 
        // sock.Close() and don't call sock.BeginReceive() again.  But as long 
        // as you want to keep processing incoming data...

        // Set up again to get the next chunk of data.
        this.sock.BeginReceive(
            this.dataRcvBuf, 0,
            this.dataRcvBuf.Length,
            SocketFlags.None,
            new AsyncCallback(this.OnBytesReceived),
            this);

    }
}

Vous pouvez utiliser votre objet Connection pour envoyer des données en appelant directement son Socket, comme suit ...

this.sock.Send(this.encoding.GetBytes("Hello to you, remote Host."));

Comme je l'ai dit, j'ai essayé de modifier le code ici pour publication, aussi je m'excuse pour la présence d'erreurs.

12
Pat Daburu

Tout d’abord, TCP ne garantit pas que tout ce que vous envoyez sera reçu avec la même lecture à l’autre bout. Il garantit uniquement que tous les octets que vous aurez envoyés arriveront dans le bon ordre. .

Par conséquent, vous devrez continuer à créer un tampon lors de la lecture du flux. Vous devrez également connaître la taille de chaque message.

Le plus simple consiste à utiliser un caractère non typable ASCII) pour marquer la fin du paquet et le rechercher dans les données reçues.

4
jgauffin

J'ai développé une bibliothèque de réseaux de points qui pourrait s'avérer utile. J'ai résolu le problème de ne jamais obtenir toutes les données si elles dépassent la mémoire tampon, ce que de nombreuses publications ont actualisé. Encore quelques problèmes avec la solution, mais ça marche plutôt bien https://github.com/NicholasLKSharp/DotNet-TCP-Communication

1
NicholasLKSharp