25 октября 2010 в 15:42

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

.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.
Кирилл Музыков @kmuzykov
карма
12,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (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/

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