A simple client server example


Thrift IDL

To create a service in thrift, you need to first define the service in IDL file. The IDL file is then used to generate skeleton code/interface in different languages. Then you need to implement the skeleton code or the interface to implement the service you need to create. In this tutorial we will learn how to implement services in C++


Calculator service

Let us create a simple calculator service. The service will provide an add function. The function will take two numbers as argument and return sum of these two numbers as result. The server will implement the add function and a client from different process will call the add function.

The IDL for the calculator service (calculator.thrift)

namespace cpp example // The generated c++ code will be put inside example namespace

// Defines a service with name Calculator
service Calculator {
  // Add a function with name add in the Calculator service.
  // This has two argument num1 and num2 of type i32 and return
  // a value of type i64
  i64 add(1:i32 num1, 2:i32 num2);
}

Compile this as

python -m thrift_compiler.main --gen cpp2 calculator.thrift

This will generate gen-cpp2 folder which contains all generated cpp files which we will use to build server and client.


Server code (server.cpp)

#include <iostream>

#include "gen-cpp2/Calculator.h" // This is included from generated code
#include <thrift/lib/cpp2/server/ThriftServer.h>

using namespace std;
using namespace apache::thrift;
using namespace example::cpp2;

// The thrift has generated service interface with name CalculatorSvIf
// At server side we have to implement this interface
class CalculatorSvc : public CalculatorSvIf
{
 public:
  virtual ~CalculatorSvc() {}
  // We have to implement async_tm_add to implement the add function
  // of the Calculator service which we defined in calculator.thrift file
  void async_tm_add(
    // callback is passed to this function to return result of the add
    // operation. Since we are implementing the service in async way
    // the return type of function is void and result is returned via
    // callback. The function may immediately return and later when
    // the result is ready then it will return the result via
    // callback. In this example, we are returning the result
    // before returning the function but this may not be always true.
    std::unique_ptr<apache::thrift::HandlerCallback<int64_t>> callback,
    // num1 is a parameter of add function. Its value will be passed from client
    int32_t num1,
    // num2 is another parameter of add function. Its value will be passed from client
    int32_t num2)
  {
    cout << "Got async request " << num1 << " + " << num2 << endl;
    callback->result(num1 + num2); // return the result via callback
  }
};

int main(int argc, char **argv) {
  // To create a server, we need to first create server handler object.
  // The server handler object contains the implementation of the service.
  std::shared_ptr<CalculatorSvc> ptr(new  CalculatorSvc());
  // Create a thrift server
  ThriftServer* s = new ThriftServer();
  // Set server handler object
  s->setInterface(ptr);
  // Set the server port
  s->setPort(8080);
  // Start the server to serve!!
  s->serve();

  return 0;
}

Compile the server with command

g++ -o server server.cpp gen-cpp2/Calculator.cpp gen-cpp2/calculator_constants.cpp gen-cpp2/calculator_types.cpp -std=c++11 -I gen-cpp -lboost_system -lpthread -lglog -lfolly -lthrift -lthriftcpp2


Client code (client.cpp)

#include "gen-cpp2/Calculator.h" // From generated code
#include <iostream>

using namespace std;
using namespace apache::thrift;
using namespace apache::thrift::async;
using namespace example::cpp2;
using namespace folly::wangle;

int main()
{
  TEventBase base;
  int port = 8080; //The port on which server is listening

  // Create a async client socket and connect it. Change
  // the ip address to where the server is listening
  std::shared_ptr<TAsyncSocket> socket(
    TAsyncSocket::newSocket(&base, "127.0.0.1", port));

  // Create a HeaderClientChannel object which is used in creating
  // client object
  auto client_channel = HeaderClientChannel::newChannel(socket);
  // Create a client object
  CalculatorAsyncClient client(std::move(client_channel));
  // Invoke the add function on server. As we are doing async
  // invocation of the function, we do not immediately get
  // the result. Instead we get a future object.
  folly::wangle::Future<int64_t> f = client.future_add(2, 3);
  // Set the callback on the future object. The callback is called
  // when the result is received from server
  f.then(
      [](Try<int64_t>&& t) {
        // We received the result from server
        cout << "result = " << t.value() << endl;
      });
  base.loop();
}

Compile the client with command

g++ -o client client.cpp gen-cpp2/Calculator.cpp gen-cpp2/calculator_constants.cpp gen-cpp2/calculator_types.cpp -std=c++11 -I gen-cpp -lboost_system -lpthread -lglog -lfolly -lthrift -lthriftcpp2


Now to see the thrift example in action: Open two bash shell and from one run server and from another run client


If you get compilation errors like thrift header not found or undefined references then check if you have correctly installed the thrift


Function names in generated code

In the above example, we have implemented async_tm_add function on server side and called future_add from client side. Actually there are various names generated for the same service you define in IDL file. At very broad level, you can group these functions in two groups: sync and async. From both server and client side, you are given sync async versions of the function. You can implement and call(from client) both sync and async version. Also there are many variation of a function in async group but they do exactly the same thing. Depending on your choice, you should pick one of them. In the above example, only async functions are used. If you want to use sync version, please try yourself.


up