C# Socket.BeginReceive/EndReceive

В каком порядке вызываются функции Socket.BeginReceive/EndReceive?

Например, я вызываю BeginReceive дважды, один раз, чтобы узнать длину сообщения, и второй раз, чтобы получить само сообщение. Теперь сценарий таков: для каждого отправляемого сообщения я начинаю ждать его завершения (фактически подтверждение отправленного сообщения, а также ожидаю завершения действия после получения подтверждения), поэтому я вызываю BeginReceive для каждого BeginSend, но в каждый обратный вызов BeginReceive, я проверяю, получаю ли я длину или сообщение. Если я получаю сообщение и получил его полностью, я вызываю другой BeginReceive, чтобы получить завершение действия. Теперь это где вещи не синхронизированы. Потому что один из моих обратных вызовов приема получает байты, которые он интерпретирует как длину сообщения, хотя на самом деле это само сообщение.

Теперь, как мне это решить?

РЕДАКТИРОВАТЬ: Это вопрос C#.NET:)

Вот код, в основном он слишком большой, извините за это

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

5 ответов

Порядок во времени должен быть:

  1. BeginReceive для длины сообщения
  2. EndReceive для завершения № 1
  3. BeginReceive для тела сообщения
  4. EndReceive для завершения № 3

Например, не используя обратные вызовы, вы могли бы иметь:

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

Но тогда вам лучше использовать Receive!

Я думаю, что вам может быть проще использовать отдельные обработчики для двух разных приемов:

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

Конечное размещение всех связанных в объекте состояния и их передача (в завершение делегировать доступ через IAsyncResult.AsyncState) из BeginReceive может упростить задачу, но она отходит от линейного мышления императивного кода и полного охвата подхода, основанного на событиях.


Приложение 2012 года:

Версия.NET 4.5

С поддержкой асинхронности в C#5 появилась новая опция. При этом компилятор генерирует ручные продолжения (отдельные методы обратного вызова) и замыкания (состояние) из встроенного кода. Однако есть две вещи, которые нужно обойти:

  1. В то время как System.Net.Sockets.Socket имеет различные …Async методы это для асинхронного шаблона, основанного на событиях, а не Task на основе шаблона, который C#5 await использует. Решение: использовать TaskFactory.FromAsync чтобы получить один Task<T> из Begin…End… пара.

  2. TaskFactory.FromAsync поддерживает передачу до трех дополнительных аргументов (в дополнение к обратному вызову и состоянию) для Begin…, Решение: лямбда, принимающая ноль дополнительных аргументов, имеет правильную сигнатуру, а C# даст нам правильное закрытие для передачи аргументов.

Следовательно (и более полно реализовано с Message будучи другим типом, который обрабатывает преобразование из начальной отправки длины, закодированной в некотором фиксированном количестве байтов, тогда байты контента в длину для буфера контента):

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

Возможно, что вы хотите сделать, это связать ваши обратные вызовы:

псевдокод:



// 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),-)
}

см. http://msdn.microsoft.com/en-us/library/system.asynccallback.aspx для правильных примеров

Это поможет, если вы опишите структуру сообщения, которое вы отправляете.

Пока у вас есть только один BeginReceive(), он завершится и даст вам следующие доступные байты данных в сети. Если у вас есть несколько неоплаченных одновременно, все ставки отменены, потому что.net не гарантирует, что завершение будет выполнено в любом заданном порядке.

Обычно методы BeginXXX указывают на асинхронную операцию, и вы, кажется, хотите сделать это синхронно.

Если вам действительно нужен синхронный клиент / сервер, может быть, это поможет http://sharpoverride.blogspot.com/2009/04/another-tcpip-server-client-well-it.html

Как уже говорили другие, не используйте глобальные переменные здесь - используйте класс для состояния сокета. Что-то вроде:

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

Перед повторной отправкой сообщения вы должны проверить сокет... у вас может быть исключение сокета.

Вы, вероятно, должны иметь возвращение; после вашего вызова WaitForData в блоке "if" объекта ReceiveComplete.

Тимоти Пратли сказал это выше, одна ошибка будет в байтах. Получена во второй раз. Каждый раз вы измеряете только bytesReceived из этого EndReceive, а затем сравниваете его с messageLength. Вам нужно сохранить сумму всех байтов, полученных.

И ваша самая большая ошибка заключается в том, что при первом вызове ReceiveComplete вы учитываете тот факт, что сообщение может (скорее всего) содержать больше данных, чем просто размер сообщения - вероятно, оно также будет содержать половину сообщения. Вам нужно очистить размер данных, а затем также сохранить оставшуюся часть сообщения в вашей переменной сообщения.

Другие вопросы по тегам