Pull to refresh

Использование Protocol Buffers на платформе .Net (Часть 2)

.NET *
В первой части мы познакомились с Protocol Buffers и попробовали использовать их в .Net приложении. Сегодня мы продолжим дискутировать и ответим на оставшиеся вопросы. Как и прежде читатель должен немного владеть языком C# и системой контроля версий SVN для лучшего усвоения материала. Также не помешает иметь общее представление о WCF, т.к. будет снова (не)много кода, но уже в контексте этой технологии!


Постойте, а то, что мы сериализовали на одной платформе может быть десериализовано на другой?


И да и нет. Protobuf описывает простые типы данных для которых ответ утвердительный, но, к сожалению, существуют типы данных, для которых ответ отрицательный. И наш пример доказывает это! Обратите внимание на тип DateTime. Он сам по себе является сложным типом данных и к тому же платформозависимым. Следовательно, если мы его сериализуем, то нет никакой гарантии, что на другой платформе всё будет восстановлено. Поэтому нужно придумать способ для устранения этого недостатка. Одно из решений уже реализовано в protobuf-net и заключается в следующем: тип DateTime сериализуется как интервал времени, прошедший с 01-01-1970 (точка отсчёта для unix систем). Соглашения для остальных типов здесь.

Для того чтобы защитить себя от таких ситуаций, рекомендуется использовать файлы с расширением .proto для описания вашей структуры данных (поддерживается интеграция с Visual Studio). Это поможет программе генерации исходного кода учесть особенности конечного языка программирования и получить переносимое представление ваших структурированных данных. В нашем случае мы будем иметь следующее описание (в примере тип DateTime соответствует собственному типу данных на основе этого обсуждения):
package Proto.Common;
message Task
{
    optional int32 Id = 1;
    optional DateTime CreatedAt = 2;
    optional string CreatedBy = 3;
    enum TaskPriority
    {        
        Low = 0;
        Medium = 1;
        High = 2;
    }
    optional TaskPriority Priority = 4;
    optional string Content = 5;
}

* This source code was highlighted with Source Code Highlighter.

Т.к. сгенерированный код достаточно объёмен и не предназначен для ручного редактирования, мы его опустим.

Я заметил в списке поддерживаемых языков Javascript, стоит ли отказываться от JSON в пользу protobuf?


Определённо, нет. JSON имеет встроенную поддержку в каждом браузере и вам не нужно выгружать скрипт для его сериализации и десериализации на клиента. Если вы испытываете проблемы с объёмом данных, попробуйте, например, включить сжатие. Однако, указанная реализация вам определённо пригодится, если вы разрабатываете используя серверный Javascript, например, в NodeJS.

Где ещё я могу использовать protobuf кроме описанного выше примера?


Как .Net разработчик вы найдете в protobuf-net поддержку таких платформ как Silverlight, Compact Framework, Mono и других. Давайте посмотрим, как легко интегрировать protobuf в WCF. Начнём традиционно с описания нашей сущности (для TaskPriority ничего не изменилось):
using System;
using System.Runtime.Serialization;

namespace Proto.Common
{
    [DataContract]
    public class Task
    {
        [DataMember(Order = 1)]
        public int Id { get; set; }

        [DataMember(Order = 2)]
        public DateTime CreatedAt { get; set; }

        [DataMember(Order = 3)]
        public string CreatedBy { get; set; }

        [DataMember(Order = 4)]
        public TaskPriority Priority { get; set; }

        [DataMember(Order = 5)]
        public string Content { get; set; }
    }
}


* This source code was highlighted with Source Code Highlighter.

Как вы видите, оно мало отличается от ранее приведённого описания. Но, честно говоря, оно не выглядит как пример использования protobuf! Контракт сервиса:
using System.ServiceModel;

namespace Proto.Common
{
    [ServiceContract]
    public interface ITaskManager
    {
        [OperationContract]
        Task[] GetTasks();
    }
}


* This source code was highlighted with Source Code Highlighter.

Снова не намёка на protobuf. Единственное, где можно упоминание – это конфигурационный файл сервиса:
<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        <services>
            <service name="Proto.Server.TaskManager"
                     behaviorConfiguration="Proto.Server.ServiceBehavior">
                <!-- Service Endpoints -->
                <endpoint name="Proto.Server.Endpoint"
                         address="net.tcp:\\localhost:9000"
                         binding="netTcpBinding"
                         contract="Proto.Common.ITaskManager"
                         behaviorConfiguration="Proto.Common.EndpointBehavior" />
            </service>
        </services>
        <behaviors>
            <serviceBehaviors>
                <behavior name="Proto.Server.ServiceBehavior">
                    <!-- To receive exception details in faults for debugging purposes, set the value below to true.
                         Set to false before deployment to avoid disclosing exception information -->
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                </behavior>
            </serviceBehaviors>
            <endpointBehaviors>
                <behavior name="Proto.Common.EndpointBehavior">
                    <protobuf/>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="protobuf"
                     type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net, Version=1.0.0.280, Culture=neutral, PublicKeyToken=257b51d87d2e4d67"/>
            </behaviorExtensions>
        </extensions>
    </system.serviceModel>
</configuration>


* This source code was highlighted with Source Code Highlighter.

И клиента:
<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        <client>
            <endpoint name="Proto.Client.Endpoint"
                     address="net.tcp:\\localhost:9000"
                     binding="netTcpBinding"
                     contract="Proto.Common.ITaskManager"
                     behaviorConfiguration="Proto.Common.EndpointBehavior" />
        </client>
        <behaviors>
            <endpointBehaviors>
                <behavior name="Proto.Common.EndpointBehavior">
                    <protobuf/>
                </behavior>
            </endpointBehaviors>
        </behaviors>
        <extensions>
            <behaviorExtensions>
                <add name="protobuf"
                     type="ProtoBuf.ServiceModel.ProtoBehaviorExtension, protobuf-net, Version=1.0.0.280, Culture=neutral, PublicKeyToken=257b51d87d2e4d67"/>
            </behaviorExtensions>
        </extensions>
    </system.serviceModel>
</configuration>


* This source code was highlighted with Source Code Highlighter.

Реализация клиента и сервиса тривиальна и не представляет интереса.

Если включить протоколирование сообщений, то можно увидеть следующую запись в логе:
<MessageLogTraceRecord Time="2011-05-12T16:17:03.2780000+04:00" Source="TransportSend" Type="System.ServiceModel.Dispatcher.OperationFormatter+OperationFormatterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
    <s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
        <s:Header>
            <a:Action s:mustUnderstand="1">
                tempuri.org/ITaskManager/GetTasksResponse
            </a:Action>
            <a:RelatesTo>
                urn:uuid:768e2f40-612c-4593-8cfb-eacb7b291a9c
            </a:RelatesTo>
            <a:To s:mustUnderstand="1">
                www.w3.org/2005/08/addressing/anonymous
            </a:To>
        </s:Header>
        <s:Body>
            <GetTasksResponse xmlns="http://tempuri.org/">
                <proto>
                    Ci4IARIJCPTs+c/8SxAEGgpTdGV2ZSBKb2JzIAIqEUludmVudCBuZXcgaVBob25lCi8IAhIJCPTclY/4SxAEGg1TdGV2ZSBCYWxsbWVyKhFJbnN0YWxsIG93biBTa3lwZQ==
                </proto>
            </GetTasksResponse>
        </s:Body>
    </s:Envelope>
</MessageLogTraceRecord>

* This source code was highlighted with Source Code Highlighter.


Насколько я помню — использование контрактов данных не ограничивается атрибутами, как обстоят дела с расширением этих контрактов?


Ровно также как в WCF. Вы знаете, что вы можете учитывать изменения контрактов с помощью IExtensibleDataObject. В protobuf-net есть похожий интерфейс и называется он IExtensible. Отличие не такое большое, так что вы можете его изучить самостоятельно здесь.

Почему многие разработчики отказываются от использования protobuf-net в WCF?


Это не больше, чем миф. Просто разработчики обычно находят protobuf, когда ищут решения для ускорения своего сетевого обмена. Некоторые из них идут дальше и убирают уровни абстракции, накладываемые WCF и используют, например, TcpClient для максимальной скорости. Если это ваш случай, то, возможно, вы сочтёте более полезным Thrift.

Вы меня убедили. Обязательно попробую protobuf-net в следующем проекте. Спасибо за дискуссию!


Пожалуйста. Проект активно развивается, так что, возможно, мы ещё не раз его обсудим.
Tags:
Hubs:
Total votes 31: ↑24 and ↓7 +17
Views 17K
Comments Comments 5