以 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 客户端的请求过程。