Правильный способ получения сетевых данных в цикле

Я пишу клиентское приложение на C#, которое будет подключаться к серверу, написанному на python. Мой вопрос о получении данных в цикле. Структура приложения все о клиенте просит сервер -> сервер отвечает клиенту. Все работает нормально, когда сообщение меньше фактического размера буфера (установленного на сервере). Например: буфер на стороне сервера: 1024, размер клиентского буфера: 256, длина данных < 1 КБ. Я запускаю свое приложение со следующим кодом:

int datacounter = 0;
byte[] recived = new byte[256];
StringBuilder stb = new StringBuilder();
serverStream.ReadTimeout = 1500;
try
{
    while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
    }
}
catch { Console.WriteLine("Timeout!"); }

Затем приложение получает данные в 4 цикла (по 256 байт):

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 96

И затем тайм-аут, который завершает передачу и передает полные данные для последующего анализа (из объекта stb). Я не думаю, что использование тайм-аута является правильным, но я не знаю другого способа сделать это. Тем не менее, так оно и работает. Здесь мы рассмотрим пример, в котором нет: буфер на стороне сервера: 1024, буфер на стороне клиента: 256, длина данных ~ 8 Кбайт (сторона Python отправляет данные в цикле).

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 256

Затем тайм-аут тикает (и, очевидно, данные неполные - получили 1 КБ из 8 КБ). Иногда цикл даже заканчивается после 1 запуска с 28 полученными байтами, и это все до истечения времени ожидания. Python говорит, что данные были отправлены правильно. Вот как я создаю объект socket и serverStream:

TcpClient clientSocket = new TcpClient();
clientSocket.Connect("x.y.z.x", 1234);
NetworkStream serverStream = clientSocket.GetStream();

Это не ошибка TcpClient. Пробовал то же самое с прозрачными розетками, созданными как:

new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

Однако это работает аналогично. Есть ли способ, чтобы мой цикл работал без тайм-аута, получая все данные? Я хотел бы сохранить сокет синхронным, если это возможно.

3 ответа

Я не думаю, что что-то не так с вашим кодом получения функционально. Я собрал тест, и получатель получает столько, сколько вы можете отправить (например, 8 МБ), пока вы продолжаете отправлять без паузы 1,5 секунды, до истечения времени ожидания.

Похоже, ваш сервер просто не посылает достаточно быстро.

Чтобы ответить на ваш вопрос, время не является типичным способом узнать, когда вы получили полное сообщение. Один из простых и простых способов определения того, когда получено полное сообщение, состоит в том, чтобы поставить префикс длины полного сообщения на передающей стороне (например, 4-байтовое int). Затем на приемной стороне сначала прочитайте 4 байта, декодируйте до длины, а затем прочитайте еще много байтов.

Вы также можете добавить строку завершения сообщения, такую ​​как Environment.NewLine, в конец вашего сообщения. Это имеет то преимущество, что вы можете вызывать StreamReader.ReadLine(), который будет блокироваться до получения полного сообщения. Это работает только в том случае, если завершение НЕ может быть включено в само сообщение.

Если вы не можете изменить протокол сервера, есть ли другой способ узнать, что вы получили полное сообщение? (например, проверка NewLine в конце сообщения, конечного тега XML или какого-либо другого шаблона.) Если нет, возможно, вы могли бы подождать, пока сервер отключится, в противном случае, похоже, вы будете вынуждены найти правильное решение. Сроки баланса.

Я включил тестовый код ниже, если вы хотите поиграть с ним.

Сторона сервера / отправки:

        IPAddress localAddr = IPAddress.Parse("127.0.0.1");
        TcpListener server = new TcpListener(localAddr, 13579);
        server.Start();
        TcpClient clientSocket = server.AcceptTcpClient();
        NetworkStream stream = clientSocket.GetStream();

        int bytesSent = 0;
        int bytesToSend = 1 << 25;
        int bufferSize = 1024;
        string testMessage = new string('X', bufferSize);
        byte[] buffer = UTF8Encoding.UTF8.GetBytes(testMessage);

        while (bytesSent < bytesToSend)
        {
            int byteToSendThisRound = Math.Min(bufferSize, bytesToSend - bytesSent);
            stream.Write(buffer, 0, byteToSendThisRound);
            bytesSent += byteToSendThisRound;
        }

Клиент / Принимающая сторона:

        TcpClient client = new TcpClient("127.0.0.1", 13579);
        NetworkStream serverStream = client.GetStream();

        int totalBytesReceived = 0;
        int datacounter = 0;
        byte[] recived = new byte[256];
        StringBuilder stb = new StringBuilder();
        serverStream.ReadTimeout = 1500;
        try
        {
            while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
            {
                totalBytesReceived += 256;
                Console.WriteLine("RECIVED: {0}, {1}", datacounter, totalBytesReceived);
                stb.Append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
            }
        }
        catch { Console.WriteLine("Timeout!"); }

Всем, кому нужна помощь в этом

Просто чтобы добавить к ответу Chi_Town_Don, убедитесь, что вы используете stb.ToString()вне петли. И я обнаружил, что ничего не будет распечатано, если цикл не разорвется. Для этого if(!serverStream.DataAvailable()){break;}творит чудеса. Таким образом, вам не нужно передавать размер пакета или какое-либо другое запутанное условие.

Почему бы вам не сбросить исключение, которое заставляет ваш код перейти в ветку catch и выяснить это?:)

catch (Exception ex) { Console.WriteLine("Timeout because of... " + ex.Message); }

-EDIT Извините, я не видел тайм-аут. Вопрос, который вы спрашиваете, есть ли способ сделать это без тайм-аута. Да, не устанавливайте тайм-аут и проверьте, меньше ли полученное количество байтов, чем размер буфера.

То есть:

while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
        if(datacounter < 256) //you're good to go
           break; 
    }
Другие вопросы по тегам