Сокет не читает все поступающие пакеты

Я работаю на клиенте C# TCP и наблюдаю за своими пакетами с помощью Microsoft Network Monitor. Есть сервер, который в основном черный ящик отправляет N пакетов (сейчас это порядка 10-20) с задержкой от 0,1 мс до 1 мс между каждым из них и следующим.

Пакеты, которые читает мой клиент, находятся в порядке и, конечно, большинство поступают большими порциями, чем появляются в Network Monitor, так как TCP получает поток. Моя проблема в том, что некоторые пакеты не приходят (я проверил предыдущий кусок информации, чтобы убедиться, что его там нет. Они также хранятся не в том порядке).

Так возможно ли, что мой клиент каким-то образом упускает часть информации? Пакеты отправляются слишком часто? К сожалению, я не могу вмешиваться в их частоту. Я добавлю сюда часть своего кода, если бы вы могли объяснить мне, почему поступающие пакеты не читаются и как это решить, я был бы очень признателен.

Здесь я впервые вызываю BeginReceive:

private static void AcceptCallback(IAsyncResult result)
{
    ConnectionInfo connection = new ConnectionInfo();
    MensajeRecibido msj = new MensajeRecibido();
    try
    {
        // Finish Accept

        Socket s = (Socket)result.AsyncState;
        connection.Socket = s.EndAccept(result);
        msj.workSocket = connection.Socket;
        connection.Socket.Blocking = false;
        connection.Buffer = new byte[255];
        lock (connections) connections.Add(connection);

        // Start Receive

        connection.Socket.BeginReceive(msj.buffer, 0,
        msj.buffer.Length, SocketFlags.None,
        new AsyncCallback(ReceiveCallback), msj);
        // Start new Accept
        serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), result.AsyncState);
    }
    catch (SocketException exc)
    {
        //Log here
    }
    catch (Exception exc)
    {
        //Log here
    }
}

Это обратный вызов:

private async static void ReceiveCallback(IAsyncResult result)
{
    MensajeRecibido mensaje = new MensajeRecibido();
    mensaje = (MensajeRecibido)result.AsyncState;

    try
    {
        mensaje.workSocket.EndReceive(result);
        mensaje.EstaCompleto();
        mensaje.workSocket.BeginReceive(mensaje.buffer, 0,
        mensaje.buffer.Length, SocketFlags.None,
        new AsyncCallback(ReceiveCallback), mensaje);
    }
    catch (SocketException)
    {
        //Log
    }
    catch (Exception)
    {
        //Log
    }
}

И это метод EstaCompleto() который в основном преобразует сообщение и добавляет его в список. (Он возвращает истину или ложь, потому что на самом деле он должен входить в предложение if, но пока я не избавлюсь от этой проблемы, которая действительно не имеет смысла)

public bool EstaCompleto()
{
    MensajeActual = Destuffing(ByteToFrame_Decoder(buffer)); //This translates the message to an understandable string
    Mensajes.Enqueue(MensajeActual);
    if(MensajeActual.Contains("<ETX>"))
    {
        return true;
    }
    else return false;
}

Изменить 25/3/15: Вот остальные из класса MensajeRecibido.

 public class MensajeRecibido
{

    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 25500; 
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    public string UltimoMensajeLeido = "0";
    public string MensajeActual = "0";
    public Queue<string> Mensajes = new Queue<string>();
    public IPAddress IPRack;


    //*******************************************************************

    public bool EstaCompleto()
    ///See code in the previous sample


    //*******************************************************************
    public string ByteToFrame_Decoder(byte[] frame)
    {
        string answer = null;
        UTF8Encoding ObjDecoder = new System.Text.UTF8Encoding();
        char[] array_chares = new char[frame.Length];
        string msj_neg = null;
        string titlemsg = "Atención";
        try
        {
            int cant = ObjDecoder.GetChars(frame, 0, frame.Length, array_chares, 0);
        }
        catch (EncoderFallbackException EncFbackEx)
        {
            msj_neg = "No hay comunicación";
            //   System.Windows.Forms.MessageBox.Show(msj_neg, titlemsg, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        }
        answer = decode_string(array_chares);
        return answer;
    } // fin método ByteToFrame_Decoder()

    //*******************************************************************
    public string Destuffing(string msjstuff)
    {
        string destuffed = null;
        string matched = null;
        string original = null;
        int largo = msjstuff.Length;

        for (int i = 0; i < (largo - 1); i++)
        {
            matched = msjstuff.Substring(i, 2);
            original = msjstuff.Substring(i, 1);
            if (original != " ")
            {
                switch (matched)
                {
                    case "EX":
                        { original = "D"; i++; } break;

                    case "ex":
                        { original = "d"; i++; } break;

                    case "EE":
                        { original = "E"; i++; } break;

                    case "ee":
                        { original = "e"; i++; } break;

                    case "  ":
                        { original = ""; i += 2; } break;
                }
                destuffed = destuffed + original;
            }
            else
            {
                i++;
            }

        }
        destuffed = destuffed + ">";
        return destuffed;

    } //fin método Destuffing()
    //*******************************************************************
    public static string decode_string(char[] ArrChar)
    {
        string text = null;
        string reply = null;
        foreach (char letter in ArrChar)
        {
            int value = Convert.ToInt32(letter);
            string hexOutput = String.Format("{0:X}", value); // Convert the decimal value to a hexadecimal value in string form.
            switch (hexOutput)
            {
                case "20":
                    text = " ";
                    break;
                case "1":
                    text = "<SOH>";
                    break;
                case "2":
                    text = "<STX>";
                    break;
                case "3":
                    text = "<ETX>";
                    break;
                case "4":
                    text = "<EOT>";
                    reply = reply + text;
                    goto Finish;
                case "5":
                    text = "<ENQ>";
                    break;
                case "6":
                    text = "<ACK>";
                    break;
                case "15":
                    text = "<NAK>";
                    break;
                case "17":
                    text = "<ETB>";
                    break;
                case "1E":
                    text = "<RS>";
                    break;
                /*case "23":
                    text = "#";
                    break;
                case "24":
                    text = "$";
                    break;
                case "26":
                    text = "&";
                    break;*/
                default:
                    text = letter.ToString();
                    break;
            }
            reply = reply + text;
        }
    Finish: ; //salimos del foreach
        return reply;
    } //fin método decode_string()
    //*******************************************************************

}

1 ответ

Без хорошего, минимального, полного примера кода, который надежно демонстрирует проблему, невозможно обеспечить точное исправление ошибки.

Однако из этого утверждения в вашем ReceiveCallback() Метод, понятно, в чем проблема:

mensaje.workSocket.EndReceive(result);

EndReceive() Метод возвращает количество байтов по причине: нет гарантии количества полученных байтов.

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

  1. Их код получает только часть "пакета" (или "сообщения", или подобной терминологии).
  2. Их код не может получить некоторые из "пакетов", которые были отправлены.

Обе проблемы проистекают из одного источника: неспособность понять, что в протоколе TCP не существует такого понятия, как "пакет".

Приложение должно определить границу сообщения. Все, что дает вам TCP, - это гарантия того, что если отправленные байты действительно будут получены, они будут получены в том же порядке, в котором они были отправлены, и, если получен какой-либо данный байт, также были получены все ранее отправленные байты (т.е. нет пробелов в данных).

Проблема № 1 выше возникает, когда TCP доставляет только часть того, что начинающий программист отправил в виде "пакета". Это совершенно законное поведение со стороны TCP, и приложение должно отслеживать данные, полученные до сих пор, и выяснять, когда они получили целый "пакет".

Проблема №2 (с которой вы столкнулись) возникает, когда TCP доставляет два или более "пакетов" за одну операцию приема. Опять же, это совершенно законно со стороны TCP, и приложение должно обработать полученные данные и определить, где заканчивается один "пакет" и начинается следующий.

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

Как вы должны справиться с этими границами? Я понятия не имею. Это зависит от того, как вы отправляете данные, и как вы хотите обработать результаты. Без полного примера кода (как описано в приведенной выше ссылке) это невозможно исправить. Однако обратите внимание, что существует множество примеров того, как это сделать правильно. Надеюсь, теперь, когда вы знаете, что искать, вы сможете найти решение самостоятельно. Если нет, не стесняйтесь создать хороший пример кода и опубликовать новый вопрос с просьбой помочь с этим.

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