Отправка файлов через TCP/.NET SSLStream работает медленно/не работает

Я пишу серверное/клиентское приложение, которое работает с SSL (через SSLStream), которое должно делать много вещей (не только получать/отправлять файлы). В настоящее время это работает так: есть только одно соединение. Я всегда отправляю данные с клиента/сервера, используя SSLStream.WriteLine(), и получаю их, используя SSLStream.ReadLine(), потому что я могу отправлять всю информацию по одному соединению и могу отправлять из всех потоков, не уничтожая данные.

Теперь мне захотелось реализовать отправку и получение файлов. Как и другие вещи в моих клиент-серверных приложениях, каждое сообщение имеет префикс (например, cl_files или sth) и часть контента в кодировке base64 (префикс и контент разделены |). Я реализовал обмен файлами так: загрузчик отправляет получателю сообщение об общем размере файла, после чего загрузчик отправляет части файла в кодировке base64 с префиксом r.

Моя проблема в том, что обмен файлами очень медленный. Я получил около 20 КБ/с от локального хоста к локальному хосту. У меня есть и другая проблема. Если я увеличу размер частей файла, закодированных в base64 (что ускорит обмен файлами), префикс r больше не будет передаваться получателю (поэтому данные не могут быть идентифицированы).

Как я могу сделать это быстрее?

Любая помощь будет принята с благодарностью.

Мой (вероятно, плохой) код для клиента:

//its running inside a Thread
FileInfo x = new FileInfo(ThreadInfos.Path);
long size = x.Length; //gets total size
long cursize = 0;
FileStream fs = new FileStream(ThreadInfos.Path, FileMode.Open);
Int16 readblocks = default(Int16);
while (cursize < size) {
    byte[] buffer = new byte[4096];
    readblocks = fs.Read(buffer, 0, 4096);
    ServerConnector.send("r", getBase64FromBytes(buffer));//It sends the encoded Data with the prefix r over SSLStream.WriteLine
    cursize = cursize + Convert.ToInt64(readblocks);
    ThreadInfos.wait.setvalue((csize / size) * 100);//outputs value to the gui
}
fs.Close();

Для сервера:

case "r"://switch case for prefixes
           if (isreceiving)
           {
              byte[] buffer = getBytesFromBase64(splited[1]);//splited ist the received Line over ReadLine splitted by the seperator "|"
              rsize = rsize + buffer.LongLength;
              writer.Write(buffer, 0, buffer.Length);//it writes the decoded data into the file
              if (rsize == rtotalsize)//checks if file is completed
              {
                 writer.Close();
              }
           }
break;

person Tearsdontfalls    schedule 10.09.2012    source источник
comment
Можете ли вы читать куски большего размера, чем 4096? Насколько большими будут ваши файлы в среднем?   -  person mj_    schedule 10.09.2012
comment
Пробовал с 8000, некоторые сообщения были уничтожены, пробовал и с 2048, работало, но очень медленно. В среднем файлы будут от 500кб-10мб   -  person Tearsdontfalls    schedule 10.09.2012
comment
Всякий раз, когда мне приходилось отправлять сообщения, я устанавливал очень большой размер (сто мегабайт) и позволял основному потоку TCP управлять потоком для вас. Можете ли вы добавить свою функцию getBytesFromBase64? Это ты специально написал? Попробуйте временно удалить его и отправить простые байты и посмотреть, улучшит ли это его.   -  person mj_    schedule 10.09.2012
comment
getBytesFromBase64 — это не что иное, как return System.Convert.FromBase64String(input); Завтра я попытаюсь удалить Base64, но как я могу отправить массив байтов через Stream.ReadLine()?   -  person Tearsdontfalls    schedule 10.09.2012
comment
Когда вы конвертируете что-то в base 64, как вообще работает ReadLine? Получают ли символы \n кодировку base-64. Я беспокоюсь, что ReadLine может прочитать слишком мало ваших данных, которые приходят, если вероятностно, что кодировка base-64 случайно помещает \n. Например, если вы отправляете гамбургер\n и он превращается в 3df232\n42sdf (я только что это придумал), возможно ли это?   -  person mj_    schedule 11.09.2012
comment
Альтернативный способ сделать это — создать StreamWriter/StreamReader и поместить SslStream в параметр конструктора для потока. Тогда вы должны получить Write/ReadLine. (см. также комментарий выше)   -  person mj_    schedule 11.09.2012
comment
Ваша кодировка base64 не может работать правильно, если вы не предоставите ей счетчик чтения, а также буфер. Вы не можете предполагать, что буфер заполнялся при каждом чтении.   -  person user207421    schedule 11.09.2012
comment
@mj_ Например, гамбургер\n возвращается в c# и php aGFtYnVyZ2VyXFxu В Википедии есть таблица, которая показывает, какие символы могут быть в строке base64 (обратная косая черта отсутствует в списке, так что это не может быть ошибкой).   -  person Tearsdontfalls    schedule 11.09.2012
comment
@EJP Как я могу это сделать? Перепробовал много вещей, но ничего из этого не сработало (инициализация буфера без количества массивов вызывает исключение, создание нового буфера и создание нового буфера = старый буфер; также не работает)   -  person Tearsdontfalls    schedule 11.09.2012
comment
Как кто-нибудь может ответить на это, если вы не показываете свой код base 64?   -  person user207421    schedule 15.09.2012
comment
Вот код: pastebin.com/GVcETGv2   -  person Tearsdontfalls    schedule 16.09.2012
comment
На самом деле, можете ли вы добавить код, в котором вы создаете SSLStream на клиенте/сервере?   -  person mj_    schedule 19.09.2012
comment
@mj_ pastebin.com/JvLjPRcK   -  person Tearsdontfalls    schedule 19.09.2012
comment
Получил еще один факт. Передача файла не удалась, потому что Convert.FromBase64 выдает исключение с сообщением: неверная длина для массива символов base-64. С частями по 50 байт ошибки не происходит. Может ли быть максимальная длина для массивов символов base64?   -  person Tearsdontfalls    schedule 19.09.2012


Ответы (4)


Ваша проблема связана с тем, что вы выполняете то, что по сути является бинарной операцией через текстовый протокол, и вы усугубляете эту проблему, делая это по зашифрованному каналу. Я не собираюсь изобретать это для вас, но вот несколько вариантов...

  1. Подумайте о переходе на модель клиент/сервер HTTPS вместо того, чтобы изобретать велосипед. Это даст вам четко определенную модель для операций PUT/GET над файлами.

  2. Если вы не можете (или не хотите) конвертировать в HTTPS, рассмотрите другие клиент-серверные библиотеки, которые обеспечивают безопасный транспорт и четко определенный протокол для двоичных данных. Например, я часто использую protobuf-csharp-port и protobuf-csharp-rpc для обеспечения безопасного протокола и транспорта в нашем центре обработки данных или локальной сети.

  3. Если вы застряли с вашим транспортом, являющимся необработанным SslStream, попробуйте использовать четко определенную и проверенную структуру двоичной сериализации, например protobuf-csharp-port или protobuf-net для определите свой протокол.

  4. И, наконец, если вам необходимо продолжить работу с имеющейся у вас структурой, попробуйте некоторые приемы, подобные http. Запишите пару имя/значение в виде текста, определяющего следующее необработанное двоичное содержимое.

person csharptest.net    schedule 19.09.2012
comment
Поскольку это мусорное программное обеспечение, я хотел бы сделать его с существующей структурой, которая у меня есть. Любая идея, что я мог бы отправлять байты напрямую вместо base64, не прерывая другую связь? - person Tearsdontfalls; 19.09.2012

Прежде всего, base64 через ssl в любом случае будет медленным, сам ssl медленнее, чем необработанный транспорт. В настоящее время передача файлов не осуществляется через base64, протокол http намного более стабилен, чем что-либо еще, и большинство библиотек на всех платформах очень стабильны. Base64 требует большего размера, чем фактические данные, плюс время на кодирование.

Кроме того, ваша следующая строка может быть проблемой.

ThreadInfos.wait.setvalue((csize / size) * 100);//outputs value to the gui

Если ваша эта линия блокируется, то это будет замедляться на каждые 4 КБ. Обновление на каждые 4кб тоже не правильно, если только значение прогресса от предыдущего значения не отличается на значительную величину, для него нет необходимости обновлять ui.

person Akash Kava    schedule 19.09.2012

Я бы попробовал сжать gzip до/после сети. По моему опыту помогает. Я бы сказал, что такой код может помочь:

using(GZipStream stream = new GZipStream(sslStream, CompressionMode.Compress)) 
{
    stream.Write(...);
    stream.Flush();
    stream.Close();
}

Предупреждение: это может помешать SSL, если Flush не будет выполнен. и для этого потребуются некоторые тесты... и я не пытался компилировать код.

person Frederic    schedule 19.09.2012

Я думаю, что Акаш Кава прав.

while (cursize < size) {
    DateTime start = DateTime.Now;
    byte[] buffer = new byte[4096];
    readblocks = fs.Read(buffer, 0, 4096);
    ServerConnector.send("r", getBase64FromBytes(buffer));
    DateTime end = DateTime.Now;
    Console.Writline((end-start).TotalSeconds);
    cursize = cursize + Convert.ToInt64(readblocks);
    ThreadInfos.wait.setvalue((csize / size) * 100);
    end = DateTime.Now;
    Console.Writline((end-start).TotalSeconds);
}

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

Также способ отправки пакетов данных на сервер ненадежен.

Можно ли вставить вашу реализацию

ThreadInfos.wait.setvalue((csize / size) * 100);

person Larry    schedule 19.09.2012