wake-up-neo.com

Senden und Empfangen von Daten über ein Netzwerk mit TcpClient

Ich muss einen Dienst entwickeln, der eine Verbindung zu einem TCP) - Server herstellt. Die Hauptaufgaben bestehen darin, eingehende Nachrichten zu lesen und in zehn Minuten Befehle an den Server zu senden, beispielsweise einen Synchronisierungsbefehl das TcpClient-Objekt wie folgt:

...
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()
}

Auch wenn ich etwas auf irgendeine Weise ausschreiben muss, benutze ich:

 clientStreamWriter.write("xxx");

Ist diese Verwendung korrekt? Oder gibt es einen besseren Weg?

35
dankyy1

Seien Sie gewarnt - dies ist eine sehr alte und umständliche "Lösung".

Übrigens können Sie mithilfe der Serialisierungstechnologie Zeichenfolgen, Zahlen oder beliebige Objekte senden, die die Serialisierung unterstützen (die meisten .NET-Datenspeicherklassen und -Strukturen sind [Serialisierbar]). Dort sollten Sie zunächst Int32-Länge in vier Bytes an den Stream senden und dann binär serialisierte (System.Runtime.Serialization.Formatters.Binary.BinaryFormatter) Daten hinein senden.

Auf der anderen Seite oder der Verbindung (eigentlich auf beiden Seiten) sollten Sie auf jeden Fall einen Byte [] -Puffer haben, den Sie zur Laufzeit anhängen und abschneiden, wenn Daten kommen.

So etwas verwende ich:

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(); }
    }
}

Das ist nur ein Beispiel, Sie sollten es für Ihre Verwendung bearbeiten.

21
AgentFire

Zunächst empfehle ich die Verwendung von WCF, .NET Remoting oder einer anderen übergeordneten Kommunikationsabstraktion. Die Lernkurve für "einfache" Sockets ist fast so hoch wie für WCF, da es bei der direkten Verwendung von TCP/IP so viele nicht offensichtliche Fallstricke gibt.

Wenn Sie den TCP/IP-Pfad fortsetzen möchten, lesen Sie meine . NET TCP/IP-FAQ , insbesondere die Abschnitte zu Nachrichtenrahmen und Anwendungsprotokollspezifikationen) .

Verwenden Sie auch asynchrone Socket-APIs. Die synchronen APIs werden nicht skaliert und können in einigen Fehlersituationen zu Deadlocks führen. Die synchronen APIs ergeben nur einen kleinen Beispielcode, aber der Code mit realer Produktionsqualität verwendet die asynchronen APIs.

21
Stephen Cleary

Ich habe Glück gehabt, das Socket-Objekt direkt zu verwenden (anstelle des TCP Client). Ich erstelle ein Server-Objekt, das in etwa so aussieht (ich habe einige Dinge wie die Ausnahmebehandlung der Kürze halber bearbeitet) , aber ich hoffe, dass die Idee rüberkommt.) ...

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);
    }
}

Dann verwende ich eine separate Verbindungsklasse, um die einzelne Verbindung mit dem Remote-Host zu verwalten. Das sieht ungefähr so ​​aus ...

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);

    }
}

Sie können Ihr Connection-Objekt zum Senden von Daten verwenden, indem Sie direkt seinen Socket aufrufen, wie folgt ...

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

Wie gesagt, ich habe versucht, den Code zum Posten hier zu bearbeiten, daher entschuldige ich mich, wenn er Fehler enthält.

12
Pat Daburu

Zuallererst garantiert TCP nicht, dass alles, was Sie senden, mit demselben Lesevorgang am anderen Ende empfangen wird. Es garantiert nur, dass alle von Ihnen gesendeten Bytes in der richtigen Reihenfolge ankommen .

Daher müssen Sie beim Lesen aus dem Stream einen Puffer aufbauen. Sie müssen auch wissen, wie groß jede Nachricht ist.

Am einfachsten ist es, ein nicht typisierbares Zeichen ASCII zu verwenden, um das Ende des Pakets zu markieren und es in den empfangenen Daten zu suchen.

4
jgauffin

Ich habe eine Dotnet-Bibliothek entwickelt, die nützlich sein könnte. Ich habe das Problem behoben, dass nie alle Daten abgerufen werden, wenn der Puffer überschritten wird, den viele Posts abgezinst haben. Immer noch einige Probleme mit der Lösung, aber es funktioniert einwandfrei https://github.com/NicholasLKSharp/DotNet-TCP-Communication

1
NicholasLKSharp