Большие наборы данных и потоковая передача
Источник: http://msdn.microsoft.com/ru-ru/library/ms733742.aspx
Windows Communication Foundation (WCF) является инфраструктурой связи, основанной на XML. Поскольку данные XML часто кодируются в стандартном текстовом формате, определенном в спецификации XML 1.0, связанные с ним разработчики и архитекторы систем, как правило, большое внимание уделяют расстоянию передачи (или размеру) сообщений, отправляемых по сети, и кодирование XML на основе текста создает особые проблемы для эффективной передачи двоичных данных.
Основные вопросы
В рамках предоставления базовых сведений о следующей информации для WCF в этом разделе освещаются некоторые общие вопросы и соображения в отношении кодирования, двоичных данных и потоковой передачи, которые обычно применяются к связанным инфраструктурам систем.
Кодирование данных: текстовый формат в сравнении с двоичным
Общие вопросы разработчиков включают в себя понимание того, что для XML характерна значительная нагрузка по сравнению с двоичными форматами из-за повторяющейся сущности открывающих и закрывающих тегов, и кодирование числовых значений требует значительно больших ресурсов, поскольку они выражаются текстовыми значениями, и эффективное выражение двоичных данных невозможно, т.к. они требуют специального кодирования для внедрения в текстовый формат.
В то время как многие из этих и аналогичных вопросов действительно актуальны, фактическое различие между сообщениями с кодированием в виде текста XML в среде веб-служб XML и сообщениями с двоичным кодированием в предыдущей среде удаленного вызова процедур (RPC) часто намного менее значимо по сравнению с изначальными соображениями.
Тогда как сообщения с кодированием в виде текста XML прозрачны и имеют удобочитаемый вид, двоичные сообщения часто непонятны в сравнении с ними и сложны для декодирования без специальных средств. Такая разница в читаемости становится причиной игнорирования того факта, что двоичные сообщения часто содержат встроенные метаданные в полезной нагрузке, что приводит к увеличению объема служебных данных как и в случае сообщений в виде текста XML. Это особенно актуально для двоичных форматов, направленных на обеспечение возможностей слабого соединения и динамического вызова.
Однако двоичные форматы в основном содержат такие описательные метаданные в "заголовке", что также объявляет макет данных для следующих записей данных. Полезная нагрузка согласуется с таким общим объявлением блока метаданных при минимальном увеличении объема служебных данных. В противоположность этому, XML заключает каждый элемент данных в элемент или атрибут, чтобы заключенные метаданные постоянно указывались для каждого сериализованного объекта полезной нагрузки. В результате этого размер одного сериализованного объекта полезной нагрузки аналогичен, если сравнивать текстовое и двоичное представление, поскольку некоторые описательные метаданные должны быть выражены для обоих форматов, но двоичный формат более удобен из-за описания общих метаданных с каждым дополнительным объектом полезной нагрузки, передаваемым благодаря низкой общей нагрузке.
Однако для некоторых типов данных, например чисел, использование двоичных числовых представлений фиксированного размера (например, 128-разрядного десятичного типа вместо обычного текста) может показать свою неэффективность, поскольку текстовое представление может быть на несколько байт меньше. Текстовые данные также могут иметь преимущества в отношении размера благодаря обычно более гибким вариантам кодирования текста XML, тогда как некоторые двоичные форматы могут иметь по умолчанию 16-разрядную или даже 32-разрядную кодировку Юникод, что непригодно для формата двоичных XML-данных .NET.
В результате этого выбор между текстовым и двоичным форматом не так прост, как если бы принимался во внимание тот факт, что двоичные сообщения всегда меньше сообщений в виде текста XML.
Явное преимущество сообщений в виде текста XML заключается в том, что они основаны на стандартах и обеспечивают более широкие возможности взаимодействия и поддержки платформ. Дополнительные сведения см. в разделе в разделе "Кодирование" далее в этой теме.
Двоичное содержимое
Сферами, где двоичное кодирование превосходит кодирование на основе текста в отношении получаемого размера сообщений, являются большие элементы двоичных данных, например изображения, видеоролики, звукозаписи или другие виды непрозрачных, двоичных данных, обмен которыми осуществляется между службами и их пользователями. Чтобы преобразовать эти типы данных в текст XML, они, как правило, кодируются с помощью кодирования Base64.
В строке с кодированием Base64 каждый символ представляет 6-разрядные данные вместо исходных 8-разрядных, в результате чего соотношение кодирование-нагрузка для Base64 равно 4:3, не считая символы дополнительного форматирования (возврат каретки/переход на новую строку), которые обычно добавляются. Тогда как значимость различий между кодированием XML и двоичным кодированием обычно зависит от сценария, увеличение размера более чем на 33% при передаче полезной нагрузки 500 МБ, как правило, неприемлемо.
Чтобы избежать такой чрезмерной нагрузки при кодировании, стандарт подсистемы оптимизации передачи сообщений (MTOM) позволяет внешне выражать большие элементы данных, содержащиеся в сообщении, и передавать их с сообщением в качестве двоичных данных без какого-либо специального кодирования. При использовании MTOM обмен сообщениями аналогичен обмену сообщениями электронной почты с вложениями или внедренным содержимым (изображения и другое внедренное содержимое) по протоколу SMTP. Сообщения MTOM организуются в пакеты как многоэлементные/связанные последовательности MIME с корневой частью, являющейся фактическим сообщением SOAP.
Сообщение MTOM SOAP изменяется с некодированной версии таким образом, что специальные теги элементов, относящиеся к соответствующим частям MIME, занимают место исходных элементов в сообщении, содержащем двоичные данные. В результате этого сообщение SOAP относится к двоичному содержимому, указывая на отправленные с ним части MIME, но передает только текстовые данные XML. Поскольку эта модель тесно связана с надежной моделью SMTP, существует широкая поддержка в виде средств для кодирования и декодирования сообщений MTOM на многих платформах, что обеспечивает исключительные возможности взаимодействия.
Тем не менее, как и в случае с Base64, для MTOM также характерен некоторый объем служебных данных для формата MIME, поэтому преимущества использования MTOM проявляются только, когда размер элемента двоичных данных превышает 1 КБ. Из-за служебных данных сообщения с кодированием MTOM могут быть больше сообщений с кодировкой Base64 для двоичных данных, если двоичная полезная нагрузка остается в этом диапазоне. Дополнительные сведения см. в разделе в разделе "Кодирование" далее в этой теме.
Содержимое данных большого объема
Если не учитывать расстояние передачи, ранее упомянутая полезная нагрузка 500 МБ также создает большую проблему в локальной среде на уровне службы и клиента. По умолчанию WCF обрабатывает сообщения в режиме буферизации. Это означает, что все содержимое сообщения находится в памяти до его отправки или после получения. Хотя это и является хорошей стратегией для большинства сценариев и необходимо для таких функций обмена сообщениями, как цифровые сигнатуры и надежная доставка, большие сообщения могут исчерпать системные ресурсы.
Стратегией обработки больших полезных нагрузок является потоковая передача. Хотя сообщения, в особенности выраженные в XML, как правило, считаются относительно компактными пакетами данных, сообщение может иметь размер в несколько гигабайт и напоминать непрерывный поток данных, а не пакет данных. Когда данные передаются в потоковом, а не буферизованном режиме, отправитель открывает содержимое тела сообщения для получателя в виде потока, и инфраструктура сообщения непрерывно направляет данные от отправителя получателю по мере их поступления.
Наиболее распространенным сценарием, при котором осуществляется передача содержания данных такого большого объема, является передача объектов двоичных данных, которые:
-
невозможно легко разбить на последовательность сообщений;
-
должны быть своевременно доставлены;
-
недоступны полностью при начале передачи.
Данные, не соответствующие этим ограничениям, как правило, лучше отправлять в виде последовательностей сообщений в рамках одного сеанса, а не одним большим сообщением. Дополнительные сведения см. в разделе в разделе "Потоковая передача данных" далее в этой теме.
Кодирование
Кодирование определяет набор правил, относящихся к способу представления сообщений в сети. Кодировщик реализует такое кодирование и выполняет, на стороне отправителя, преобразование сообщения в памяти Message в поток байтов или буфер байтов, который можно передать по сети. На стороне получателя кодировщик преобразует последовательность байтов в сообщение в памяти.
WCF содержит три кодировщика и позволяет создавать и подключать собственные кодировщики по мере необходимости.
Каждая стандартная привязка включает в себя предварительно настроенный кодировщик, в соответствии с чем привязки с префиксом Net* используют двоичный кодировщик (путем включения класса BinaryMessageEncodingBindingElement), тогда как классы BasicHttpBinding и WSHttpBinding используют кодировщик текстовых сообщений (с помощью класса TextMessageEncodingBindingElement) по умолчанию.
Элемент привязки кодировщика | Описание |
---|---|
TextMessageEncodingBindingElement |
Кодировщик текстовых сообщений является кодировщиком по умолчанию для всех привязок на основе HTTP и представляет собой правильный выбор для всех пользовательских привязок, где наибольшее значение имеет взаимодействие. Этот кодировщик считывает и записывает текстовые сообщения стандарта SOAP 1.1/SOAP 1.2 без специальной обработки двоичных данных. Если MessageVersion сообщения задано как None, программа-оболочка конверта SOAP исключается из выхода, и сериализуется только содержимое тела сообщения. |
Кодировщик сообщений MTOM представляет собой текстовый кодировщик, реализующий специальную обработку двоичных данных и не используется по умолчанию в любой из стандартных привязок, поскольку он является только программой индивидуальной оптимизации. Если сообщение содержит двоичные данные, превышающие предел, при котором кодирование MTOM обладает преимуществом, данные внешне выводятся в часть MIME вслед за конвертом сообщения. См. "Реализация MTOM" далее в этом разделе. | |
BinaryMessageEncodingBindingElement |
Кодировщик двоичных сообщений является кодировщиком по умолчанию для привязок Net* и представляет собой правильный выбор, если обе взаимодействующие стороны основаны на WCF. Кодировщик двоичных сообщений использует формат двоичных XML-данных .NET, двоичное представление для информационных наборов XML (Infosets), относящееся к продуктам Майкрософт, для которого характерно меньшее занимаемое место по сравнению с аналогичным представлением XML 1.0, и этот кодировщик кодирует двоичные данные как поток байтов. |
Текстовое кодирование сообщений обычно идеальный выбор для любого пути передачи, требующего взаимодействия, а двоичное кодирование сообщений отлично подходит для любых других путей передачи. Двоичное кодирование, как правило, приводит к уменьшению размеров сообщений по сравнению с текстом применительно к одному сообщению, а на протяжении сеанса связи размеры сообщений неизменно становятся еще меньше. В отличие от текстового кодирования для двоичного не требуется специальная обработка двоичных данных, например использование Base64, и оно представляет байты как байты.
Если в решении не используется взаимодействие, но тем не менее следует использовать транспорт HTTP, можно создать BinaryMessageEncodingBindingElement в пользовательской привязке, использующей для транспорта класс HttpTransportBindingElement. Если для ряда клиентов службы необходимо обеспечить взаимодействие, рекомендуется открыть параллельные конечные точки, чтобы у каждой из них имелись правильные варианты транспорта и кодирования для соответствующих клиентов.
Реализация MTOM
Если требуется обеспечить взаимодействие и планируется отправка больших объемов двоичных данных, в качестве альтернативной стратегии можно использовать кодирование сообщений MTOM, которое можно реализовать для стандартных привязок BasicHttpBinding или WSHttpBinding, задав соответствующее свойство MessageEncoding как Mtom или создав MtomMessageEncodingBindingElement в CustomBinding. В следующем примере кода, взятом из образца Кодирование MTOM, показан способ реализации MTOM в конфигурации.
<system.serviceModel> … <bindings> <wsHttpBinding> <binding name="ExampleBinding" messageEncoding="Mtom"/> </wsHttpBinding> </bindings> … <system.serviceModel>
Как уже упоминалось ранее, решение об использовании кодирования MTOM зависит от объема отправляемых данных. Кроме того, поскольку MTOM реализуется на уровне привязки, MTOM влияет на все операции в указанной конечной точке.
Поскольку кодировщик MTOM всегда выдает кодированное MTOM MIME/многоэлементное сообщение независимо от того, заканчиваются ли двоичные данные во время внешнего вывода, в обычной ситуации необходимо реализовывать MTOM только для конечных точек, в которых осуществляется обмен сообщениями объемом более 1 КБ двоичных данных. Кроме того, контракты служб, разработанные для использования с конечными точками с MTOM, должны, где возможно, ограничиваться указанием таких операций передачи данных. Связанная функциональность управления должна находиться в отдельном контракте. Это правило "только MTOM" применимо только к сообщениям, отправленным через конечную точку с MTOM; кодировщик MTOM также может декодировать и анализировать входящие сообщения, отличные от MTOM.
Использование кодировщика MTOM соответствует всем остальным функциям WCF. Обратите внимание, что это правило может соблюдаться не во всех случаях, например оно неприменимо при необходимости поддержки сеансов.
Модель программирования
Независимо от того, какой из трех встроенных кодировщиков используется в приложении, программирование аналогично таковому в случае передачи двоичных данных. Различие заключается в том, как WCF обрабатывает данные на основе их типов данных.
[DataContract] class MyData { [DataMember] byte[] binaryBuffer; [DataMember] string someStringData; }
При использовании MTOM предыдущий контракт данных сериализуется в соответствии со следующими правилами.
-
Если
binaryBuffer
не null и по отдельности содержит достаточно данных, чтобы подтвердить служебные данные внешнего выделения MTOM (заголовки MIME и т. д.) при сравнении с кодированием Base64, данные внешне выводятся и передаются с сообщением как двоичная часть MIME. Если это пороговое значение не превышается, данные кодируются как Base64.
-
Строка (и все другие недвоичные типы) всегда представлена как строка внутри тела сообщения, независимо от размера.
Результат кодирования MTOM точно такой же независимо от того, используется ли явный контракт данных, как показано в предыдущем примере, используется ли список параметров в операции, имеются ли вложенные контракты данных или передается ли объект контракта данных в коллекции. Байтовые массивы всегда подлежат оптимизации и оптимизируются, если удовлетворяются пороги оптимизации.
Примечание |
---|
В контрактах данных не следует использовать унаследованные типы System.IO.Stream. Потоковые данные должны передаваться с помощью модели потоковой передачи, которая рассматривается в разделе "Потоковая передача данных". |
Потоковая передача данных
Если требуется передать значительный объем данных, режим потоковой передачи в WCF является выгодной альтернативой поведению по умолчанию, при котором сообщения буферизуются и обрабатываются в памяти целиком.
Как упоминалось ранее, потоковую передачу следует реализовать только для больших сообщений (с текстовым или двоичным содержимым), если данные невозможно сегментировать, если сообщение должно быть своевременно доставлено или если данные еще не полностью доступны на момент начала передачи.
Ограничения
Если включена потоковая передача данных, значительное число функций WCFнедоступно.
-
Цифровые сигнатуры для тела сообщения недоступны, поскольку для них требуется вычисление хэша по содержимому всего сообщения.
При потоковой передаче содержимое не полностью доступно на момент создания и отправки заголовков сообщений, поэтому цифровую сигнатуру вычислить невозможно.
-
Шифрование зависит от цифровых сигнатур, по которым проверяется правильность восстановления данных.
-
Во время надежных сеансов отправленные сообщения должны помещаться в буфер клиента для повторной передачи в случае потери сообщения во время передачи, а также сообщения должны сохраняться в службе до их обработки в реализации службы, чтобы сохранить порядок сообщений в случае непоследовательного получения сообщений.
Из-за этих функциональных ограничений параметры безопасности для потоковой передачи можно использовать только на транспортном уровне, и невозможно реализовать надежные сеансы. Потоковая передача доступна только в следующих, определенных системой привязках.
-
BasicHttpBinding
-
NetTcpBinding
-
NetNamedPipeBinding
-
WebHttpBinding
Поскольку для базового транспорта NetTcpBinding и NetNamedPipeBinding характерна заведомо надежная доставка и поддержка сеанса на основе подключения, в отличие от HTTP, на практике на эти две привязки оказывается минимальное влияние такими ограничениями.
Потоковая передача недоступна с транспортом очереди сообщений (MSMQ), и, соответственно, не может быть использована с классом NetMsmqBinding или MsmqIntegrationBinding. Транспорт очереди сообщений поддерживает только передачу буферизованных данных с ограниченным размером сообщения, тогда как все остальные транспорты не имеют какого-либо реального ограничения размера сообщений для большинства сценариев.
Потоковая передача также недоступна при использовании транспорта однорангового канала и не может использоваться с NetPeerTcpBinding.
Потоковая передача и сеансы
При потоковой передаче вызовов с привязкой, основанной на сеансе, может возникнуть непредвиденное поведение. Все потоковые вызовы выполняются через один канал (канал датаграммы), который не поддерживает сеансы, даже если используемая привязка настроена так, чтобы она использовала сеансы. Если несколько клиентов выполняют потоковые вызовы одного объекта службы через привязку, основанную на сеансе, и задан "одиночный" режим параллелизма объекта службы и режим контекста его экземпляра как PerSession, все вызовы должны проходить через канал датаграммы, а потому может обрабатываться не более одного вызова одновременно. При этом может истечь время ожидания для одного или нескольких клиентов. Эту проблему можно обойти, присвоив значение PerCall режиму контекста экземпляров объекта службы или задав параллелизм как "множественный".
Примечание |
---|
Свойство MaxConcurrentSessions в данном случае ни на что не влияет, поскольку имеется всего один сеанс. |
Реализация потоковой передачи
Потоковую передачу можно включить следующими способами.
-
Отправляйте и принимайте запросы в режиме потоковой передачи, принимайте и возвращайте ответы в режиме буферизации (StreamedRequest).
-
Отправляйте и принимайте запросы в режиме буферизации, принимайте и возвращайте ответы в режиме потоковой передачи (StreamedResponse).
-
Отправляйте и получайте запросы и ответы в режиме потоковой передачи в обоих направлениях.
(Streamed).
Потоковую передачу можно отключить, задав режим передачи как Buffered, что является параметром по умолчанию для всех привязок. В следующем коде показано, как задать режим передачи в конфигурации.
<system.serviceModel> … <bindings> <basicHttpBinding> <binding name="ExampleBinding" transferMode="Streaming"/> </basicHttpBinding> </bindings> … <system.serviceModel>
При инициализации привязки в коде следует задать соответствующее свойство TransferMode привязки (или элемент привязки транспорта, если создается пользовательская привязка) как одно из ранее упомянутых значений.
Потоковую передачу можно включить для запросов и ответов или обоих направлений по отдельности на любой стороне взаимодействующих сторон, не затрагивая функциональность. Однако следует всегда принимать во внимание, что размер передаваемых данных настолько важен, что включение потоковой передачи имеет смысл на обеих конечных точках канала связи. Для межплатформенной связи, где одна из конечных точек не реализована с помощью WCF, возможность использования потоковой передачи зависит от возможностей потоковой передачи платформы. Другим редким исключением может быть сценарий с зависимостью от потребления памяти, когда клиент или служба должны свести к минимуму рабочий набор и способны обработать только небольшие объемы данных в буфере.
Модель программирования для потоковой передачи
Модель программирования для потоковой передачи является простой. Для получения потоковых данных следует указать контракт операции с одним параметром ввода типа Stream. Для возвращения потоковых данных следует возвратить ссылку Stream.
[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")] public interface IStreamedService { [OperationContract] Stream Echo(Stream data); [OperationContract] Stream RequestInfo(string query); [OperationContract(OneWay=true)] void ProvideInfo(Stream data); }
Во время операции Echo в предыдущем примере осуществляется получение и возврат потока, поэтому она должна использоваться в привязке с Streamed. Для операции RequestInfo наилучшим образом подходит StreamedResponse, поскольку он только возвращает Stream. Односторонняя операция наилучшим образом подходит для StreamedRequest.
Обратите внимание, что добавление второго параметра в операции Echo или ProvideInfo станет причиной возвращения модели службы к стратегии буферизации и использованию представления сериализации в среде выполнения потока. Со сквозной потоковой передачей запросов совместимы только операции с одним параметром входного потока.
Применение этого правила аналогично контрактам сообщений. Как показано в следующем контракте сообщения, в нем (являющимся потоком) можно расположить только один элемент тела. Если вместе с потоком требуется передать дополнительную информацию, ее следует указать в заголовках сообщений. Тело сообщения зарезервировано исключительно для содержимого потока.
[MessageContract] public class UploadStreamMessage { [MessageHeader] public string appRef; [MessageBodyMember] public Stream data; }
Когда поток достигает конца файла (EOF), потоковая передача прекращается, и сообщение закрывается. При отправке сообщения (возврат значения или вызов операции) можно передать FileStream, и инфраструктура WCF впоследствии будет извлекать все данные из потока, пока он не будет полностью считан и не будет достигнут конец файла. Чтобы передать потоковые данные для источника, для которого не существует такого предварительно созданного унаследованного класса Stream, следует создать такой класс, наложить его на источник потока и использовать в качестве аргумента или возвращаемого значения.
Во время получения сообщения WCF создает поток поверх содержимого тела сообщения с кодированием Base64 (или соответствующей части MIME при использовании MTOM), и поток достигает конца файла после считывания содержимого.
Потоковая передача на транспортном уровне также используется с любым другим типом контракта сообщения (списками параметров, аргументами контракта данных и явным контрактом сообщения), но поскольку для сериализации и десериализации сообщений таких типов требуется буферизация сериализатором, использование таких вариантов контрактов не рекомендуется.
Особые вопросы по безопасности данных большого объема
Все привязки позволяют ограничивать размер входящих сообщений, чтобы избежать атак типа "отказ в обслуживании". Например, BasicHttpBinding раскрывает свойство MaxReceivedMessageSize, которое привязывает размер входящего сообщения, и также привязывает максимальный размер памяти, который выделяется при обработке сообщения. Эта единица задается в байтах и имеет значение по умолчанию 65 536 байт.
Угроза безопасности, относящаяся к сценарию потоковой передачи большого объема данных, вызывает отказ в обслуживании, приводя к буферизации данных, когда получатель ожидает их потоковой передачи. Например, WCF всегда буферизует заголовки SOAP сообщения, поэтому злоумышленник может создать большое вредоносное сообщение, полностью состоящее из заголовков, чтобы принудительно буферизовать данные. Если включена потоковая передача, MaxReceivedMessageSize может быть задано как крайне большое значение, поскольку получатель не ожидает одновременной буферизации всего сообщения в память. Если WCF принудительно буферизует сообщение, возникает переполнение памяти.
Поэтому в данном случае ограничения максимального размера входящего сообщения недостаточно. Свойство MaxBufferSize должно ограничивать память, в которую WCF выполняет буферизацию. Для потоковой передачи важно задать безопасное значение (или оставить значение по умолчанию). Например, допустим, что служба должна получить файлы размером до 4 ГБ и сохранить их на локальный диск. Также предположим, что память ограничена таким образом, что одновременно можно буферизовать только 64 КБ данных. В этом случае для MaxReceivedMessageSize следует задать 4 ГБ, а для MaxBufferSize — 64 КБ. Кроме того, в реализации службы следует обеспечить чтение из входящего потока только фрагментов по 64 КБ, и не считывать следующий фрагмент, пока предыдущий не будет записан на диск и удален из памяти.
Также важно понимать, что эта квота ограничивает только буферизацию, выполняемую WCF, и не может обеспечить защиту от какой-либо буферизации, которая осуществляется в собственной реализации службы или клиента. Дополнительные сведения см. дополнительные вопросы безопасности в разделе Вопросы безопасности для данных.
Примечание |
---|
Решение по использованию буферизованной или потоковой передачи является локальным решением конечной точки. Для транспортов HTTP режим передачи не распространяется через подключение или на прокси-серверы и других посредников. Выбор режима передачи не отражается в описании интерфейса службы. Чтобы задать режим, после создания клиента WCF для службы необходимо изменить файл конфигурации для тех служб, которые планируется использовать с потоковой передачей. Для транспортов TCP и именованного канала режим передачи распространяется в виде утверждения политики. |