thrift 框架源码:客户端

2019/7/23 9:44:37 人评论 次浏览 分类:学习教程

以 thrift 框架简单使用 这篇文章为例,用gdb 跟踪一下client 的执行流程,以此学习下thrift 的源码。在此先贴出客户端代码:

#include "gen-cpp/Hello.h"
#include <transport/TSocket.h>
#include <transport/TBufferTransports.h>
#include <protocol/TBinaryProtocol.h>
 
 
using namespace apache::thrift;
using namespace apache::thrift::protocol;
using namespace apache::thrift::transport;
 
 
using boost::shared_ptr;
 
 
int main(int argc, char **argv)
{
    boost::shared_ptr<TSocket> socket(new TSocket("localhost", 9090));
    boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
    boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
 
    transport->open();
    //调用server服务
    demo::helloIn sIn;
    demo::helloOut sOut;
    sIn.age   = 25;
    sIn.name = "ccy";
 
    demo::HelloClient client(protocol);
    printf("age:%d name:%s\n", sIn.age, sIn.name.c_str());
 
 
    client.helloString(sOut, sIn);
    printf("res:%d, msg:%s\n", sOut.resCode, sOut.msg.c_str());
 
    transport->close();
    return 0;
}

后台运行server 程序后,在终端执行:

gdb --args ./client

以跟踪代码

 

transport->open()

这一步进入到了 ./src/thrift/transport/TBufferTransports.h

void open() { transport_->open(); }

然后到了 src/thrift/transport/TSocket.cpp 

void TSocket::open() {
  if (isOpen()) {
    return;
  }
  if (!path_.empty()) {
    unix_open();
  } else {
    local_open();
  }
}

然后到了 src/thrift/transport/TSocket.cpp 

void TSocket::local_open() {

  if (isOpen()) {
    return;
  }

  // 校验端口
  if (port_ < 0 || port_ > 0xFFFF) {
    throw TTransportException(TTransportException::BAD_ARGS, "Specified port is invalid");
  }

  struct addrinfo hints, *res, *res0;
  res = nullptr;
  res0 = nullptr;
  int error;
  char port[sizeof("65535")];
  std::memset(&hints, 0, sizeof(hints));
  hints.ai_family = PF_UNSPEC; // 可以是任何协议族
  hints.ai_socktype = SOCK_STREAM;  // 只处理TCP连接 不处理UDP
  hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; // 可以再一次bind调用中使用返回的hints
  sprintf(port, "%d", port_);

  error = getaddrinfo(host_.c_str(), port, &hints, &res0); // res0 获得一个addrinfo类型的链表的地址

  if (error) {
    string errStr = "TSocket::open() getaddrinfo() " + getSocketInfo()
                    + string(THRIFT_GAI_STRERROR(error));
    GlobalOutput(errStr.c_str());
    close();
    throw TTransportException(TTransportException::NOT_OPEN,
                              "Could not resolve host for client socket.");
  }

  // 遍历返回的地址链表,知道打开一个连接或抛出异常.
  for (res = res0; res; res = res->ai_next) {
    try {
      openConnection(res);
      break;
    } catch (TTransportException&) {
      if (res->ai_next) {
        close();
      } else {
        close();
        freeaddrinfo(res0); // cleanup on failure
        throw;
      }
    }
  }

  // Free address structure memory
  freeaddrinfo(res0);
}

openConnection

然后到了  src/thrift/transport/TSocket.cpp 的 openConnection 函数 ,向服务器发起连接

// 这里省略了一些不必要的代码,可以自己到源码中看
void TSocket::openConnection(struct addrinfo* res) {

  if (isOpen()) { // 已经连接直接返回
    return;
  }

  socket_ = socket(res->ai_family, res->ai_socktype, res->ai_protocol); // 系统支持的 socket 函数

  // Send timeout
  if (sendTimeout_ > 0) {
    setSendTimeout(sendTimeout_);
  }

  // Recv timeout
  if (recvTimeout_ > 0) {
    setRecvTimeout(recvTimeout_);
  }

  if (keepAlive_) { // socket 连接保活 定期检测对方是否在线
    setKeepAlive(keepAlive_);
  }

  // Linger
  setLinger(lingerOn_, lingerVal_);

  // No delay
  setNoDelay(noDelay_); // 设置 TCP_NODELAY,禁用nagle算法,详见 tcp/ip 详解

  // 将 socket 设置为非阻塞,若connTimeout_ > 0
  int flags = THRIFT_FCNTL(socket_, THRIFT_F_GETFL, 0);
  if (connTimeout_ > 0) {
    if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags | THRIFT_O_NONBLOCK)) {
      int errno_copy = THRIFT_GET_SOCKET_ERROR;
      GlobalOutput.perror("TSocket::open() THRIFT_FCNTL() " + getSocketInfo(), errno_copy);
      throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy);
    }
  } else {
    if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags & ~THRIFT_O_NONBLOCK)) {
      int errno_copy = THRIFT_GET_SOCKET_ERROR;
      GlobalOutput.perror("TSocket::open() THRIFT_FCNTL " + getSocketInfo(), errno_copy);
      throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy);
    }
  }

  // Connect the socket
  int ret;
  ret = connect(socket_, res->ai_addr, static_cast<int>(res->ai_addrlen)); 发起连接

  // success case
  if (ret == 0) {
    goto done;
  }

done:
  // 将socket 恢复为一般阻塞状态
  if (-1 == THRIFT_FCNTL(socket_, THRIFT_F_SETFL, flags)) {
    int errno_copy = THRIFT_GET_SOCKET_ERROR;
    GlobalOutput.perror("TSocket::open() THRIFT_FCNTL " + getSocketInfo(), errno_copy);
    throw TTransportException(TTransportException::NOT_OPEN, "THRIFT_FCNTL() failed", errno_copy);
  }

  if (path_.empty()) {
    setCachedAddress(res->ai_addr, static_cast<socklen_t>(res->ai_addrlen));
  }
}

至此建立了和server的连接

gdb 跟踪到这里时,用netstat  命令可以查看到当前连接状态:

$ netstat -nltpa|grep 9090

(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
tcp        0      0 0.0.0.0:9090            0.0.0.0:*               LISTEN      25857/./server      
tcp        0      0 127.0.0.1:53138         127.0.0.1:9090          ESTABLISHED 26194/client        
tcp        0      0 127.0.0.1:9090          127.0.0.1:53138         ESTABLISHED 25857/./server 

rpc 调用

gdb 进 helloString 函数,到 gen-cpp/Hello.cpp 

void HelloClient::helloString(helloOut& _return, const helloIn& sIn)
{                                           
  send_helloString(sIn);                                            
  recv_helloString(_return);                                        
}                                               

这里的代码是thrift 的IDL 自动生成的,可以看到是一个发包和收包的函数。

发包 send_helloString :

void HelloClient::send_helloString(const helloIn& sIn)                                 
{
  int32_t cseqid = 0;
// oprot_ 是 TBinaryProtocolT 对象 这里将消息头写入 详见 thrift 协议层与序列化
  oprot_->writeMessageBegin("helloString", ::apache::thrift::protocol::T_CALL, cseqid);

  Hello_helloString_pargs args;
  args.sIn = &sIn;
  args.write(oprot_); // 将入参序列化

  oprot_->writeMessageEnd(); // 写消息尾
  oprot_->getTransport()->writeEnd(); // 详见thrift 传输层
  oprot_->getTransport()->flush();
}

收包 recv_helloString

void HelloClient::recv_helloString(helloOut& _return)                                                                                           
{

  int32_t rseqid = 0;
  std::string fname;
  ::apache::thrift::protocol::TMessageType mtype;

  iprot_->readMessageBegin(fname, mtype, rseqid);
  if (mtype == ::apache::thrift::protocol::T_EXCEPTION) {
    ::apache::thrift::TApplicationException x;
    x.read(iprot_);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
    throw x;
  }
  if (mtype != ::apache::thrift::protocol::T_REPLY) {
    iprot_->skip(::apache::thrift::protocol::T_STRUCT);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
  }
  if (fname.compare("helloString") != 0) {
    iprot_->skip(::apache::thrift::protocol::T_STRUCT);
    iprot_->readMessageEnd();
    iprot_->getTransport()->readEnd();
  }
  Hello_helloString_presult result;
  result.success = &_return;
  result.read(iprot_);
  iprot_->readMessageEnd();
  iprot_->getTransport()->readEnd();

  if (result.__isset.success) {
    // _return pointer has now been filled
    return;
  }
  throw ::apache::thrift::TApplicationException(::apache::thrift::TApplicationException::MISSING_RESULT, "helloString failed: unknown result");
}

同样也是涉及协议层和传输层的操作

 

transport->close()

./src/thrift/transport/TBufferTransports.h 

  void close() override {
    flush();
    transport_->close();
  }
void TBufferedTransport::flush() {
  // Write out any data waiting in the write buffer.
  auto have_bytes = static_cast<uint32_t>(wBase_ - wBuf_.get());
  if (have_bytes > 0) {
    wBase_ = wBuf_.get();
    transport_->write(wBuf_.get(), have_bytes);
  }

  // Flush the underlying transport.
  transport_->flush();
}

src/thrift/transport/TSocket.cpp

void TSocket::close() {
  if (socket_ != THRIFT_INVALID_SOCKET) {
    shutdown(socket_, THRIFT_SHUT_RDWR); // 同时关闭socket的读和写
    ::THRIFT_CLOSESOCKET(socket_);
  }
  socket_ = THRIFT_INVALID_SOCKET;
}

以上就是thrift 客户端的请求过程。

 

 

 

 

相关资讯

    暂无相关的资讯...

共有访客发表了评论 网友评论

验证码: 看不清楚?
    -->