Thrift结构分析及增加取客户端IP功能实现

来源:互联网 发布:foxmail邮件导入到mac 编辑:程序博客网 时间:2024/05/29 11:18

1.       前言

分析Thrift的结构动机是为了实现服务端能取到客户端的IP,因此需要对它的结构、调用流程有些了解。另外,请注意本文针对的是TNonblockingServer,不包含TThreadPoolServerTThreadedServerTSimpleServer

2.       示例Service

service EchoService

{

    void hello();

}

 

class EchoHandler: public EchoServiceIf

{

private:

    virtual void hello();

};

3.       网络部分类图

Thrift线程模型为若干IO线程TNonblockingIOThread(负责收发TCP连接的数据),以及主线程(负责监听TCP连接及接

受连接请求)组成。

主线程不一定就是进程的主线程,哪个线程调用了TServer::run()TServer::serve()就是本文所说的主线程。就当前最新版本(0.9.2)的Thrift而言,调用TServer::run()TServer::serve()均可以,原因是TServer::run()除无条件的调用外TServer::serve(),没有做任何其它事。对TServer::serve()的调用实际是对TServer的实现类TNonblockingServerserve()的调用。

简而言之,TNonblockingIOThread负责数据的收发,而TNonblockingServer负责接受连接请求。

在使用中需要注意,调用TServer::run()TServer::serve()的线程或进程会被阻塞,阻塞进入libevent的死循环,Linux上是死循环调用epoll_wait()


4.1.      启动准备

准备的工作包括:

1)       启动监听连接

2)       启动收发数据线程

3)       初始化运行环境

 

在这里,可以看到第一次对TServerEventHandler的回调:


4.2.      接受连接

从接受连接的时序过程可以看出:在该连接TConnection接收数据之前,先调用了TServerEventHandler::createContext(),这个就是获取客户端IP的机会之一,但是当前的实现没有将相关的信息作为参数传递给TServerEventHandler::createContext()


4.3.      收发数据:执行调用

这过程中对TServerEventHandler::processContext(connectionContext_, getTSocket())进行了回调,并传递了TSocket


5.       取客户端IP

为取得客户端的IP,有三个办法:

1)       网上博文http://blog.csdn.net/hbuxiaoshe/article/details/38942869介绍的方法也是可行的,不过让人有些纠结;

2)       修改Thrift的实现,为TServerEventHandler::createContext()增加一个参数,将TSocket作为参数传递,这样就可以非常轻易的取得客户端的IP了。最简单的修改为:

class TServerEventHandler {

public:

    virtual void* createContext(boost::shared_ptr<TProtocol> input,

                                boost::shared_ptr<TProtocol> output,

                                TTransport* transport); // 对于TNonblockingServer实际传递为TSocket

};

 

3)       不修改Thrift的实现。

在“收发数据:执行调用”的流程中,可以发现有对TServerEventHandler::processContext()的调用,而这里真好将TSocket作为第二个参数进行了传递,因此可以直接利用。

 

TServerEventHandler::createContext()TServerEventHandler::processContext()的不同在于:前者只在建立连接时被调用一次,而后者每一个RPC调用时都会调用一次。

 

// 封装对thrift服务端的公共操作

// ThriftHandler就是Thrift应用实现的,如前述出现的EchoHandler

// ServiceProcessor是由Thrift编译器生成的,如前前述中的EchoServiceProcessor

// EventHanlder则是本文的主角

template <class EventHandler, class ThriftHandler, class ServiceProcessor>

class CThriftServerHelper

{

public:

    // 启动rpc服务,请注意该调用是同步阻塞的,所以需放最后调用

    bool serve(uint16_t port);

    bool serve(uint16_t port, uint8_t num_threads);

    bool serve(const std::string& ip, uint16_t port, uint8_t num_threads);

    void stop();

 

private:

    boost::shared_ptr<ThriftHandler> _handler;

    boost::shared_ptr<thrift::TProcessor> _processor;

    boost::shared_ptr<thrift::protocol::TProtocolFactory> _protocol_factory;

    boost::shared_ptr<thrift::server::ThreadManager> _thread_manager;

    boost::shared_ptr<thrift::concurrency::PosixThreadFactory> _thread_factory;
    boost::shared_ptr<thrift::server::TServerEventHandler> _event_handler;

    boost::shared_ptr<thrift::server::TServer> _server;

};

 

template <class EventHandler, class ThriftHandler, class ServiceProcessor>

bool CThriftServerHelper<ThriftHandler, ServiceProcessor>::serve(

    const std::string& ip, uint16_t port, uint8_t num_threads)

{

    try

    {

        _handler.reset(new ThriftHandler);

        _processor.reset(new ServiceProcessor(_handler));

        _protocol_factory.reset(new thrift::protocol::TBinaryProtocolFactory());

        _thread_manager = thrift::server::ThreadManager::newSimpleThreadManager(num_threads);

        _thread_factory.reset(new thrift::concurrency::PosixThreadFactory());

 

        _thread_manager->threadFactory(_thread_factory);

        _thread_manager->start();

 

        _server.reset(new thrift::server::TNonblockingServer(

                _processor, _protocol_factory, port, _thread_manager));

 

        _event_handler.reset(new EventHandler());

        _handler->setServerEventHandler(_event_handler); // 加这一句,使用就非常方便了

        _server->setServerEventHandler(_event_handler);

        _server->serve(); // 根据前面的分析,这里其实也可调用run(),而且调用run()更为合理,因为万一run()有变化,则不能调用serve()

    }

    catch (thrift::TException& ex)

    {

        LOG(ERROR) << "Start thrift failed: " << ex.what();

        return false;

    }

 

    LOG(INFO) << "Start thrift successfully";

    return true;

}

附:问题

如何让Thrift只在指定的IP上监听,而不是监听0.0.0.0


阅读全文
0 0