11 Stimmen

C# Socket.BeginReceive/EndReceive

In welcher Reihenfolge werden die Funktionen Socket.BeginReceive/EndReceive aufgerufen?

Ich rufe zum Beispiel BeginReceive zweimal, einmal, um die Länge der Nachricht zu erhalten, und das zweite Mal, um die Nachricht selbst zu erhalten. Das Szenario sieht nun so aus, dass ich für jede Nachricht, die ich sende, auf ihre Fertigstellung warte (eigentlich auf die Bestätigung der gesendeten Nachricht, außerdem warte ich auf die Fertigstellung der Aktion nach Erhalt der Bestätigung), also rufe ich BeginReceive mit jedem BeginSend aber in jedem BeginReceive überprüfe ich, ob ich die Länge oder die Nachricht erhalte. Wenn ich die Nachricht erhalte und sie vollständig empfangen habe, rufe ich einen weiteren BeginReceive um den Abschluss der Aktion zu erhalten. Jetzt ist dies, wo die Dinge aus der Synchronisation erhalten. Denn einer meiner Empfangs-Callback empfängt Bytes, die es als die Länge von ihnen Nachricht interpretiert, wenn es in der Tat die Nachricht selbst ist.

Wie kann ich das Problem nun lösen?

EDIT : Dies ist eine C#.NET Frage :)

Hier ist der Code, im Grunde ist er zu groß, sorry dafür

public void Send(string message)
{
    try
    {
        bytesSent = 0;

        writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
        writeDataBuffer = WrapMessage(writeDataBuffer);
        messageSendSize = writeDataBuffer.Length;

        clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void WaitForData()
{
    try
    {
        if (!messageLengthReceived)
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
}

public void Send(string message)
{
    try
    {
        bytesSent = 0;

        writeDataBuffer = System.Text.Encoding.ASCII.GetBytes(message);
        writeDataBuffer = WrapMessage(writeDataBuffer);
        messageSendSize = writeDataBuffer.Length;

        clientSocket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void WaitForData()
{
    try
    {
        if (! messageLengthReceived)
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, MESSAGE_LENGTH_SIZE - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
        else 
        {
            clientSocket.BeginReceive(receiveDataBuffer, bytesReceived, messageLength - bytesReceived,
                                    SocketFlags.None, new AsyncCallback(RecieveComplete), clientSocket);
        }
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

public void RecieveComplete(IAsyncResult result)
{
    try
    {
        Socket socket = result.AsyncState as Socket;
        bytesReceived = socket.EndReceive(result);

        if (! messageLengthReceived)
        {
            if (bytesReceived != MESSAGE_LENGTH_SIZE)
            {
                WaitForData();
                return;
            }

            // unwrap message length
            int length = BitConverter.ToInt32(receiveDataBuffer, 0);
            length = IPAddress.NetworkToHostOrder(length);

            messageLength = length;
            messageLengthReceived = true;

            bytesReceived = 0;

            // now wait for getting the message itself
            WaitForData();
        }
        else
        {
            if (bytesReceived != messageLength)
            {
                WaitForData();
            }
            else
            {
                string message = Encoding.ASCII.GetString(receiveDataBuffer);

                MessageBox.Show(message);

                bytesReceived = 0;
                messageLengthReceived = false;

                // clear buffer
                receiveDataBuffer = new byte[AsyncClient.BUFFER_SIZE];

                WaitForData();
            }
        }
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }

}

public void SendComplete(IAsyncResult result)
{
    try
    {
        Socket socket = result.AsyncState as Socket;
        bytesSent = socket.EndSend(result);

        if (bytesSent != messageSendSize)
        {
            messageSendSize -= bytesSent;

            socket.BeginSend(writeDataBuffer, bytesSent, messageSendSize, SocketFlags.None,
                            new AsyncCallback(SendComplete), clientSocket);
            return;
        }

        // wait for data
        messageLengthReceived = false;
        bytesReceived = 0;

        WaitForData();
    }
    catch (SocketException socketException)
    {
        MessageBox.Show(socketException.Message);
    }
}

22voto

Richard Punkte 103159

Die zeitliche Reihenfolge sollte sein:

  1. BeginReceive für die Nachrichtenlänge
  2. EndReceive für die Fertigstellung von #1
  3. BeginReceive für den Nachrichtentext
  4. EndReceive für die Fertigstellung von #3

Z.B. könnten Sie keine Rückrufe verwenden:

var sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res = socket.EndReceive(sync);
sync = socket.BeginReceive(....);
sync.AsyncWaitHandle.WaitOne();
var res2 = socket.EndReceive(sync);

Aber dann wäre es besser, wenn Sie einfach Receive !

Ich denke, es ist einfacher, für die beiden verschiedenen Empfangsvorgänge separate Handler zu verwenden:

... Start(....) {
    sync = socket.BeginReceive(.... MessageLengthReceived, null);
}

private void MessageLengthReceived(IAsyncResult sync) {
  var len = socket.EndReceive(sync);
  // ... set up buffer etc. for message receive

 sync = socket.BeginReceive(... MessageReceived, null);
}

private void MessageReceived(IAsyncResult sync) {
  var len = socket.EndReceive(sync);
  // ... process message
}

Letztendlich werden alle zugehörigen Daten in ein Zustandsobjekt geschrieben und dieses weitergegeben (im Abschlussdelegat Zugriff über IAsyncResult.AsyncState ) von BeginReceive kann die Sache vereinfachen, erfordert aber eine Abkehr vom linearen Denken des imperativen Codes und eine vollständige Umstellung auf einen ereignisorientierten Ansatz.


2012 Nachtrag :

.NET 4.5 Version

Mit der asynchronen Unterstützung in C#5 gibt es eine neue Option. Diese verwendet den Compiler, um die manuellen Fortsetzungen (die separaten Callback-Methoden) und Closures (Status) aus dem Inline-Code zu generieren. Allerdings gibt es zwei Dinge zu umgehen:

  1. Während System.Net.Sockets.Socket hat verschiedene …Async Methoden sind diese für das ereignisbasierte asynchrone Muster, nicht für das Task basierte Muster, das C#5's await verwendet. Lösung: Verwendung TaskFactory.FromAsync um eine einzelne Task<T> von einer Begin… End… Paar.

  2. TaskFactory.FromAsync unterstützt nur die Übergabe von bis zu drei zusätzlichen Argumenten (neben dem Rückruf und dem Status) an Begin… . Lösung: Ein Lambda, das keine zusätzlichen Argumente benötigt, hat die richtige Signatur, und C# gibt uns die richtige Closure, um die Argumente zu übergeben.

Daher (und in noch stärkerem Maße realisiert mit Message ist ein weiterer Typ, der die Umwandlung von einer anfänglichen Übertragung der Länge, die in einer festen Anzahl von Bytes kodiert ist, in eine Länge für den Puffer des Inhalts vornimmt):

private async Task<Message> ReceiveAMessage() {
  var prefix = new byte[Message.PrefixLength];

  var revcLen = await Task.Factory.FromAsync(
                         (cb, s) => clientSocket.BeginReceive(prefix, 0, prefix.Length, SocketFlags.None, cb, s),
                         ias => clientSocket.EndReceive(ias),
                         null);
  if (revcLen != prefix.Length) { throw new ApplicationException("Failed to receive prefix"); }

  int contentLength = Message.GetLengthFromPrefix(prefix);
  var content = new byte[contentLength];

  revcLen = await Task.Factory.FromAsync(
                         (cb, s) => clientSocket.BeginReceive(content, 0, content.Length, SocketFlags.None, cb, s),
                         ias => clientSocket.EndReceive(ias),
                         null);
  if (revcLen != content.Length) { throw new ApplicationException("Failed to receive content"); }

  return new Message(content);
}

6voto

Timothy Pratley Punkte 10356

Vielleicht sollten Sie Ihre Rückrufe verketten:

Pseudo-Code:

// read the first 2 bytes as message length
BeginReceive(msg,0,2,-,-,new AsyncCallback(LengthReceived),-)

LengthReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  msg_length = GetLengthFromBytes(so.buffer);
  BeginReceive(so.buffer,0,msg_length,-,-,new AsyncCallback(DataReceived),-)
}

DataReceived(ar) {
  StateObject so = (StateObject) ar.AsyncState;
  Socket s = so.workSocket;
  int read = s.EndReceive(ar);
  ProcessMessage(so.buffer);
  BeginReceive(so.buffer,0,2,-,-,new AsyncCallback(LengthReceived),-)
}

siehe: http://msdn.microsoft.com/en-us/library/system.asynccallback.aspx für korrekte Beispiele

1voto

Mihai Lazar Punkte 2159

Normalerweise deuten BeginXXX-Methoden auf einen asynchronen Vorgang hin, und Sie scheinen dies auf eine synchrone Weise tun zu wollen.

Wenn Sie tatsächlich einen synchronen Client/Server wünschen, hilft Ihnen vielleicht Folgendes http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

1voto

feroze Punkte 7008

Es ist hilfreich, wenn Sie die Struktur der Nachricht, die Sie senden, beschreiben.

Solange nur eine BeginReceive()-Anweisung aussteht, wird sie abgeschlossen und die nächsten verfügbaren Datenbytes auf der Leitung übertragen. Wenn Sie mehr als einen Befehl gleichzeitig ausstehen haben, ist alles verloren, da .net nicht garantiert, dass die Ausführung in einer bestimmten Reihenfolge erfolgt.

0voto

Jess Punkte 2943

Wie die anderen gesagt haben, verwenden Sie keine globalen Variablen hier - verwenden Sie eine Klasse für Socket-Status. Etwas wie:

public class StateObject
{
    public const int DEFAULT_SIZE = 1024;           //size of receive buffer

    public byte[] buffer = new byte[DEFAULT_SIZE];  //receive buffer
    public int dataSize = 0;                        //data size to be received
    public bool dataSizeReceived = false;           //received data size?
    public StringBuilder sb = new StringBuilder();  //received data String
    public int dataRecieved = 0;

    public Socket workSocket = null;                //client socket.
    public DateTime TimeStamp;                      //timestamp of data
} //end class StateObject

Bevor Sie versuchen, die Nachricht erneut zu senden, sollten Sie den Socket überprüfen... es könnte eine Socket-Ausnahme vorliegen.

Sie sollten wahrscheinlich ein Return; nach Ihrem WaitForData-Aufruf im "if"-Block von ReceiveComplete haben.

Timothy Pratley hat es oben gesagt: Ein Fehler wird beim zweiten Durchgang in bytesRecieved sein. Jedes Mal messen Sie nur die BytesReceived von diesem EndReceive und vergleichen es dann mit messageLength. Sie müssen eine Summe aller BytesRecieved halten.

Und Ihr größter Fehler ist, dass Sie in Ihrem ersten Aufruf von ReceiveComplete die Tatsache berücksichtigen, dass die Nachricht (höchstwahrscheinlich) mehr Daten als nur die Größe der Nachricht enthalten könnte - sie wird wahrscheinlich auch die Hälfte der Nachricht enthalten. Sie müssen die Datengröße abziehen und dann auch den Rest der Nachricht in Ihrer Nachrichtenvariablen speichern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X