Использование Thrift в .NET

    Хочу поделиться с вами примером того как можно использовать такую удобную штуку как Thrift в своих .NET проектах.

    Для тех кто не знает, Thrift — это фреймворк для облегчения взаимодействия между кодом написанным на разных языках, а именно C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, Smalltalk и OCaml.

    Thrift используется и был изначально создан Facebook. Так же неоднократно упоминался здесь, на Хабре, но примеров для .NET я не нашел, кстати пошаговое руководство для .NET отсутствует и на официальном сайте. В гугле если честно, тоже не смог найти, хотя может плохо искал.

    Thrift позволяет один раз описать сервис, структуры данных и даже исключения, а потом сгенерировать код для всех поддерживаемых языков. Таким образом, если вы, например, напишете сервер с использованием Thrift на .NET, то вы
    1. Сэкономите кучу времени для написания клиент серверного приложения, например с использованием сокетов.
    2. Почти автоматически получаете клиентов на всех поддерживаемых языках.
    В своем примере я реализую простейший сервис, который просто будет возвращать время. Но имея такой макет достаточно легко расширить его до чего-нибудь полезного.


    Итак нам понадобится:
    1. Исходники Thrift — они нужны нам для того что бы собрать библиотеку (class library) для подключения в наш проект.
    2. Thrift компилятор — консольная утилита которая генерирует код на необходимом нам языке из .thrift файлов

    Оба можно взять отсюда

    Так же в конце статьи я дам исходники рабочего приложения, в них есть уже собранная библиотека и компилятор, можно взять все оттуда.

    Шаг 1: Подготовка
    Итак после того как вы скачали исходники, распаковывайте их в любую директорию и заходите в
    thrift-0.5.0\lib\csharp\src\
    Открывайте солюшен и собирайте проект. После чего у вас есть библиотека Thrift.dll.

    Шаг 2: Создаем наши проекты
    Создайте 3 проекта в Visual Studio и объедините их в солюшен.
    • TimeServer — консольное приложение. Сервер. Будет ждать соединения
    • TimeServerClient — консольное приложение. Клиент. Будет соединятся с сервером и спрашивать время.
    • TimeServerCore — библиотека (class library). В ней будут лежать сгенерированные C# классы и исходный файл .thrift(о нем далее)
    У меня получилось так:
    image

    Берете полученную ранее библиотеку Thrift.dll и скачанный Thrift компилятор и кладете их в директорию в солюшене.

    Шаг 3: Генерируем код из .thrift файла
    Опишем наш сервис и структуру данных. Для этого создадим файл TimeService.thrift с таким содержанием:
    namespace csharp TimeServer.Thrift

    //Structure for returning Time
    struct TimeInfoStruct{
    1: string Time
    }

    //Service
    service TimeService
    {
      TimeInfoStruct GetTime()
    }


    * This source code was highlighted with Source Code Highlighter.


    и положим его в проект TimeServerCore . Так же добавьте в проект ссылку(reference) на Thrift.dll полученную в Шаге №1.

    Важно: Не сохраняйте .thrift файл в Unicode кодировке, у меня Thrift компилятор не захотел ничего из него генерировать, до тех пор пока я в студии в File->Advanced Save Options не сменил Unicode на другую кодировку.

    В этом файле и содержится практически все описание нашего клиент-серверного приложения.

    Можно вручную вызвать Thrift компилятор указав ему .thrift файл и получить необходимые классы, но лучше добавить pre-build event в наш проект, на случай если мы будем менять TimeService.thrift

    С моей иерархией директорий, получился вот такой вызов
    $(SolutionDir)\Thrift\thrift-0.5.0.exe -gen csharp -o $(ProjectDir) $(ProjectDir)\TimeService.thrift

    «thrift-0.5.0.exe» — собственно сам компилятор, опция "-gen csharp" говорит ему что нам нужны классы для C#, опция "-o $(ProjectDir)" говорит куда положить результат, ну и остаток "$(ProjectDir)\TimeService.thrift" указывает какой собственно файл компилировать.

    Таким образом после билда(на самом деле до, у нас же pre-build event) TimeServerCore у нас создадутся 2 класса
    TimeInfoStruct.cs — структура для передачи времени, в принципе метод сервиса мог просто вернуть строку, но со структурой интерестнее.
    TimeService.cs — сервис, с нашим единственным методом.

    Так же в них есть код отвечающий за сериализацию/десериализайию, наглядный ToString, и кое-что еще.

    Лежать они будут в \TimeServerCore\gen-csharp\TimeServer\Thrift. Директория gen-csharp будет всегда, видимо для того что бы раскидать по разным папкам код для разных языков, если указано больше одного языка, а две директории ниже по иерархии (\TimeServer\Thrift) создаются из за namespace, указанного в .thrift файле.

    Необходимо добавить эти 2 файла в проект, у меня после этого получилось так:
    image

    Шаг 4: Сервер
    Займемся сервером. Добавьте в TimeServer ссылку(reference) на Thrift.dll, а так же на проект TimeServerCore в нашем солюшене. После чего создайте новый класс TimeServiceImplementation.cs.

    В этом классе мы реализуем методы нашего сервиса. В сгенерированном файлике TimeService.cs Thrift для нас создал специальный интерфейс, который мы должны реализовать.

    Вот интерфейс:
     public class TimeService { //лежит внутри класса

      public interface Iface {
       TimeInfoStruct GetTime();
      }
    //...
    }


    * This source code was highlighted with Source Code Highlighter.


    как видим там есть всего 1 функция, которую мы описали в .thrift файле.

    Вот моя реализация этого интерфейса:
    class TimeServiceImplementation : TimeService.Iface
      {
        public TimeInfoStruct GetTime()
        {
          return new TimeInfoStruct() { Time = DateTime.Now.ToString() };
        }
      }


    * This source code was highlighted with Source Code Highlighter.


    И так все почти готово, необходимо заставить сервер запускаться при старте приложения и ждать клиентов:
    static void Main(string[] args)
        {
          TimeServiceImplementation service = new TimeServiceImplementation();  
          TProcessor processor = new TimeService.Processor(service);
          TServerTransport transport = new TServerSocket(1337, 1000);
          TServer server = new TSimpleServer(processor, transport);

          server.Serve();
        }


    * This source code was highlighted with Source Code Highlighter.


    Мы создаем нашу реализацию интерфейса сервера, после чего запускаем сервер на 1337 порту и ждем соединений.Вот и весь код необходимый для создания простейшего сервера.
    Помимо реализации сервера TSimpleServer в библиотеке Thrift.dll есть TThreadedServer и TThreadPoolServer.

    Шаг 5: Клиент

    Реализация клиента еще проще:

      static void Main(string[] args)
        {
          TTransport transport = new TSocket("localhost", 1337);
          TProtocol proto = new TBinaryProtocol(transport);
          TimeService.Client client = new TimeService.Client(proto);
          
          transport.Open();
          TimeInfoStruct result = client.GetTime();

          Console.WriteLine(result.ToString());
          Console.ReadKey();
        }


    * This source code was highlighted with Source Code Highlighter.


    Не забудьте добавить в проект клиента ссылку(reference) на Thrift.dll и проект TimeServerCore.

    После чего, можно запустить сначала сервер, а затем клиент. Который должен вывести текущее время.

    Вот и все. Причем большую часть времени мы потратили на первоначальную настройку и знакомство с Thrift, да и то не так много. Теперь можно добавлять в наш .thirft файл новые структуры и методы в сервис, после чего лишь писать реализации серверных методов.
    Ну а если понадобятся клиенты или серверы написанные на других языках, то классы для них так же можно сгенерировать c помошью Thrift.

    Например, с использованием Thrift был получен .NET клиент для БД Cassandra. Хотя создавая Cassandra вряд ли Facebook планировали поддерживать .NET клиентов :)

    Вот обещанные исходники:
    dl.dropbox.com/u/3945288/Thirft-Time-Server-.NET-Sample.zip
    github.com/kmuzykov/Thirft-Time-Server-.NET-Sample

    Вот ссылка на Thrift Wiki, хотя там не много информации для .NET.
    Метки:
    Поделиться публикацией
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 28
    • +1
      Автору — спасибо!
      Как-раз вовремя. Есть подходящая задача :)
      • +1
        Пожалуйста. Мне бы самому статью про Thrift пару месяцев назад, сэкономил бы много времени.
      • 0
        Это типа ru.wikipedia.org/wiki/ASN.1 + zeroc.com/, только от Facebook?
        • 0
          По описанию, похоже на то.
        • 0
          Подобное решение я делал на php, для работы с hadoop, забавная вещь)
          • +1
            Не понял одного. А как «Почти автоматически получаете клиентов на всех поддерживаемых языках.»? По-моему, вы весь код клиента вручную написали…
            • 0
              Нет, не вручную. Я просто создал экземпляр класса
              TimeService.Client
              и вызвал у него
              GetTime

              Для сравнения можно написать этот же клиент вручную с использованием сокетов, и сравнить размер и затраченные усилия.

              Ну а потом еще портировать его на остальные языки при необходимости, задумываясь о размерах типов данных, big/little endian и прочем.
              • 0
                А можно, ни о чем не думая, даже о том, что может понадобится перейти с сокетов на шаред мемори или http, сделать это на WCF?
                • 0
                  Я думаю ниже я ответил и на этот комментарий.
              • +1
                под клиентом тут имелась ввиду реализация TimeService.Client
              • 0
                Мы использовали эту штуку тут.
                • 0
                  правильно ли я понял, что это некоторая альтернатива WCF? Если да, то не могли бы вы описать преимущества Thrift перед WCF? Кроме возможности бесплатно генерировать клиентов на разных языках.
                  • 0
                    Это не совсем альтернатива WCF, если у вас .NET <-> .NET то вряд ли имеет смысл.
                  • 0
                    Вот люди мучаются, разрабатывают стандарты, спецификации, протоколы, придумывают всякие SOAP-ы, REST-ы… Но это все не для нас, нам все равно, что под .Net есть WCF, который реализует все это в десятки раз лучше, чем мы когда-либо напишим в ручную…
                    Нет, мы полезем ковыряться в сокеты, с помощю новомодного инструмента, написанного от безысходности и совсем для другого, и нас не остановит отсутствие документации, нам просто лень прочитать что такое WCF и хорошенько изучить как такие вещи делаются в .Net
                    =))))
                    • +2
                      Сюрприз! Система может быть написана на нескольких языках.
                      • 0
                        Сюрприз — SOAP и REST к языкам не привязаны и даже к транспорту.
                        • +1
                          REST ортогонален thrift-у при желании. SOAP — прошлый век.
                      • +1
                        В данном примере и клиент и сервер на C# поэтому приемущества не очевидны.

                        Я отлично знаю как работает WCF. Всякие SOAP'ы, REST'ы и HTTP идут лесом когда вам нужно выжать максимум скорости. Поэтому рано или поздно вы остаетесь со старыми добрыми сокетами. Редко, но так бывает. Это что касается скорости.

                        А теперь представьте что вы пишете БД, и хотите предоставить как можно больше адаптеров для разных языков программирования, используя Thrift этого можно добится проще. Потому как лично я считаю, что БД претендующая на высокую производительность работающая по HTTP это бред.

                        Я привел пример того, что например доступ к БД Cassandra можно получить из дотнета именно посредством Thrift, если бы Cassandra была написана по-другому то вряд ли бы мы дотнетчики могли ее пощупать, по крайней мере ждать пришлось бы дольше.

                        Можно сказать что Thrift изобрели как раз от безысходности, потому что в Facebook используется куча языков и нужно организовать коммуникацию. Но мне кажется это лучше чем сделать это все через HTTP.
                        • –4
                          > Всякие SOAP'ы, REST'ы и HTTP идут лесом когда вам нужно выжать максимум скорости.
                          У WCF нет проблем со скоростью. Транспорт можно выбрать любой, SOAP и REST это только протоколы, как раз и облегчающие кроссплатформенное взаимодействие между сервером и клиентом.

                          А теперь представьте что вы пишете БД, и хотите предоставить как можно больше адаптеров для разных языков программирования, используя Thrift этого можно добится проще. Потому как лично я считаю, что БД претендующая на высокую производительность работающая по HTTP это бред.
                          Ага, представил… =)) В БД основная проблема с производительностью это как раз сама БД, а ни как не транспорт. А дальше все зависит от того, что это за БД, для каких целей и как будет использоваться — если это общая база, клиенты к которой могут быть где угодно, то REST как раз для этих задач и создавался.

                          Я привел пример того, что например доступ к БД Cassandra можно получить из дотнета именно посредством Thrift, если бы Cassandra была написана по-другому то вряд ли бы мы дотнетчики могли ее пощупать, по крайней мере ждать пришлось бы дольше.
                          То есть основная область применения Thrift — если кто-то написал с его помощю серверный код и нужно реализовать клиента?
                          • +1
                            >У WCF нет проблем со скоростью.
                            У WCF есть проблемы со скоростью, просто потому что WCF написана поверх тех же самых сокетов, и основная ее цель предоставить более простую абстракцию, и чуть ли не все сдалть через GUI. Предлагаю вам написать 2 приложения одно на WCF, а другое на чистых сокетах и посмотреть разницу. В разных сценариях она будет разная, но вряд ли WCF хотя бы в 1м обгонит сокеты.

                            >Транспорт можно выбрать любой, SOAP и REST...
                            Можно, но любой из них медленнее чем простой TCP/IP, опять же потому что они реализованы поверх него.

                            >В БД основная проблема с производительностью это как раз сама БД, а ни как не транспорт
                            БД бывают разные, если это in-memory key-value storage и вам надо получить из него по списку ключей данных на 100Мб, то передача данных займет в разы больше времени чем их поиск по словарю в памяти БД.

                            >То есть основная область применения Thrift — если кто-то написал с его помощю серверный код и нужно реализовать клиента?
                            Это один из сценариев. Другой сценарий, наоборот, если вы хотите не заморачиваясь предоставить доступ из других языков.

                            А вообще это не альтернатива WCF, я ни в коей мере не агитирую всех бросить WCF, REST и прочее и перейти на Thrift, просто поделилися новой (для меня) технологией, может быть у кого то будет сценарий, когда мой пример пригодится.
                            • –2
                              У WCF есть проблемы со скоростью, просто потому что WCF написана поверх тех же самых сокетов, и основная ее цель предоставить более простую абстракцию, и чуть ли не все сдалть через GUI.
                              Ого! =)
                              1. не более простую, а более универсальную. Задача проста — передать данные от одного процесса к другому и обратно, все остальное — транспорт, транзакционность, контракт, прочие условия — детали реализации, которые можно собрать как из кирпичиков под текущую задачу. Это не просто, зато очень гибко.
                              2. GUI тут вообще не причем, в WCF через GUI не делается ничего :)

                              Предлагаю вам написать 2 приложения одно на WCF, а другое на чистых сокетах и посмотреть разницу. В разных сценариях она будет разная, но вряд ли WCF хотя бы в 1м обгонит сокеты.
                              Как минимум не отстанет. :) Расходы на сериализацию/десериализацию — минимальны, там весь код эммитится (или LWCG если быть точным, в зависимости от сериализатора), других накладных расходов нет. Чтобы угнаться, придется каждый объект в ручную сериализовать или тоже какой кодогенератор использовать.

                              Можно, но любой из них медленнее чем простой TCP/IP, опять же потому что они реализованы поверх него.
                              Так и ваш код тоже будет поверх него. :)

                              Другой сценарий, наоборот, если вы хотите не заморачиваясь предоставить доступ из других языков.
                              Для доступа из других языков есть стандартые протоколы, зачем еще один?

                              … может быть у кого то будет сценарий, когда мой пример пригодится.
                              Вот и хотелось бы понять, что это за сценарии.

                              • +1
                                1. Все дело в том, что WCF создан для решения круга типовых задач. Поэтому всегда будет проигрывать в скорости качественной реализации заточенной под одну конкретную задачу. Плюс WCF в том что все можно сделать проще и с меньшим количеством кода.

                                2. Ну да, создать классик навесить на него аттрибуты а потом получить готовый клиент через «Add service reference» это конечно ни какого GUI и удобств, а конфигурационные файлики кстати можно править через WCF Service Configuration Editor который есть в студии.

                                Но неважно как вы создаете сервис через GUI, xml или еще как. Вы пишите на более высоком уровне абстракции, и настоящий код за вас генерят. А сгенерированный код не может быть оптимальным под все задачи, иначе мы бы давно из кирпичиков мышкой весь код собирали.

                                По этой же причине мой код поверх TCP/IP будет работать быстрее, потому что он заточен под одну конкретную задачу, а не под общий случай.

                                По поводу приложения, предлагаю вам написать простенькое приложение на WCF, например такой же TimeServer и клиент который запрашивает достаточно много раз время. А затем я перепишу его с использованием сокетов. Замерим и все станет ясно :)

                                Как вам такой вариант вместо того что бы спорить в теории?
                        • +3
                          «это в десятки раз лучше, чем мы когда-либо напишим в ручную…» советую чуть меньше религиозности. коллеги активно слезают с WCF в сторону socketов и protocol buffers. Потому что WCF — м е д л е н н ы й.
                          • НЛО прилетело и опубликовало эту надпись здесь
                            • –2
                              Может коллеги просто готовить его не умеют? Со скоростью у WCF все прекрасно, я проверял.
                              В десятки раз лучше — это не религиозность, я очень хорошо себе представляю во что может обойтись написание клиент-серверного взаимодействия с поддержкой двунаправленности, гарантии доставки, отсутствия дубликатов, смены транспорта, и прочей фигни которая еще может понадобится.
                              • +2
                                Все это перерастает в спор высокий уровень абстракции vs. контроль/скорость, я думаю тема близка к холивару.
                          • 0
                            у нас на БД-серверах Cassandra большая часть неприятностей из-за Thrift. Довольно нестабильная технология.
                            • 0
                              $(SolutionDir)\Thrift\thrift-0.5.0.exe -gen csharp -o $(ProjectDir) $(ProjectDir)\TimeService.thrift

                              было бы не плохо оформить в codegenerator для custom tool в visual studio.

                              дело нескольких часов, если найдется доброволец, то вот статья, которая должна помочь www.drewnoakes.com/snippets/WritingACustomCodeGeneratorToolForVisualStudio/

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.