先来个传统的client同步调用,分析下具体的原理!
---经过简单的debug环境准备后,我们开始看源码
public String helloWorldString(String content) throws org.apache.thrift.TException {
send_helloWorldString(content);//发送请求
return recv_helloWorldString();
}
public void send_helloWorldString(String content) throws org.apache.thrift.TException {
helloWorldString_args args = new helloWorldString_args();//主要是做了一些静态对象初始化
args.setContent(content);
sendBase("helloWorldString", args);
}
接着往下看
public helloWorldString_args setContent(String content) {
this.content = content;//就是设置content值
return this;//返回自己
}
接下来,开始执行
sendBase("helloWorldString", args);
看看里面做了什么事情
protected void sendBase(String methodName, TBase<?,?> args) throws TException {
sendBase(methodName, args, TMessageType.CALL);
}
大概就是CALL,方法名,加上参数
看看具体做了什么
private void sendBase(String methodName, TBase<?, ?> args, byte type) throws TException {
// 看到这里了
oprot_.writeMessageBegin(new TMessage(methodName, type, ++seqid_));
args.write(oprot_);
oprot_.writeMessageEnd();
oprot_.getTransport().flush();
}
所以把这4行解决就可以看懂发送了,继续看
-------------------------------------------------------
先看第一行
@Override
public void writeMessageBegin(TMessage tMessage) throws TException {
// 看到这里了
if (tMessage.type == TMessageType.CALL || tMessage.type == TMessageType.ONEWAY) {
// 判断是不是发送模式
super.writeMessageBegin(//多路复用的话,需要重新构造一下
new TMessage(SERVICE_NAME + SEPARATOR + tMessage.name, tMessage.type, tMessage.seqid));
} else {
super.writeMessageBegin(tMessage);
}
}
比如name是这样的
Thread-5[1] print n
n = "HelloWorldService:helloWorldString"
public void writeMessageBegin(TMessage tMessage) throws TException {
// 判断是不是发送模式
//这里的concreteProtocol是class org.apache.thrift.protocol.TBinaryProtocol
concreteProtocol.writeMessageBegin(tMessage);
}
跟进去
public void writeMessageBegin(TMessage message) throws TException {
// 看到这里了
if (strictWrite_) {
int version = VERSION_1 | message.type;// 版本号
// 看到这里了
writeI32(version);
writeString(message.name);
writeI32(message.seqid);
} else {
writeString(message.name);
writeByte(message.type);
writeI32(message.seqid);
}
}
public void writeI32(int i32) throws TException {
//看到这里了
i32out[0] = (byte) (0xff & (i32 >> 24));
i32out[1] = (byte) (0xff & (i32 >> 16));
i32out[2] = (byte) (0xff & (i32 >> 8));
i32out[3] = (byte) (0xff & (i32));
trans_.write(i32out, 0, 4);
}
可以看到,write是写到了本地的缓冲区
public void write(byte[] buf, int off, int len) throws TTransportException {
//调用了一个本地的缓冲区
writeBuffer_.write(buf, off, len);
}
写字符串就是
public void writeString(String str) throws TException {
try {
byte[] dat = str.getBytes("UTF-8");
writeI32(dat.length);//写长度
trans_.write(dat, 0, dat.length);//写具体内容
} catch (UnsupportedEncodingException uex) {
throw new TException("JVM DOES NOT SUPPORT UTF-8");
}
}
然后开始写参数了
public void write(org.apache.thrift.protocol.TProtocol oprot, helloWorldString_args struct)
throws org.apache.thrift.TException {
struct.validate();
//看到这里了
oprot.writeStructBegin(STRUCT_DESC);
//看到这里了
if (struct.content != null) {
oprot.writeFieldBegin(CONTENT_FIELD_DESC);//写内容
oprot.writeString(struct.content);//写具体的值
oprot.writeFieldEnd();//什么都不做
}
//
oprot.writeFieldStop();//写停止位
oprot.writeStructEnd();
}
最后调用TFramedTransport.flush
@Override
public void flush() throws TTransportException {
byte[] buf = writeBuffer_.get();//获得内容
int len = writeBuffer_.len();//长度
writeBuffer_.reset();//重置本地的缓冲区
encodeFrameSize(len, i32buf);//总长度
transport_.write(i32buf, 0, 4);
transport_.write(buf, 0, len);
transport_.flush();
}
也就是写总长度+总内容,
重点就是write + flush
先看write函数,
/**
* Writes to the underlying output stream if not null.
*/
public void write(byte[] buf, int off, int len) throws TTransportException {
if (outputStream_ == null) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot write to null outputStream");
}
try {
outputStream_.write(buf, off, len);
} catch (IOException iox) {
throw new TTransportException(TTransportException.UNKNOWN, iox);
}
}
那么outputStream是什么呢?
这就得看socket初始化的时候了
/**
* Connects the socket, creating a new socket object if necessary.
*/
public void open() throws TTransportException {
if (isOpen()) {
throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected.");
}
if (host_.length() == 0) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");
}
if (port_ <= 0) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open without port.");
}
if (socket_ == null) {
initSocket();
}
try {
socket_.connect(new InetSocketAddress(host_, port_), connectTimeout_);
inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);
outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);
} catch (IOException iox) {
close();
throw new TTransportException(TTransportException.NOT_OPEN, iox);
}
}
那么,这里写内容的话,就是真的写入了
然后就剩下flush了
Step completed: "thread=Thread-5", org.apache.thrift.transport.TIOStreamTransport.flush(), line=159 bci=18
159 outputStream_.flush();//真实的flush了
Thread-5[1] print outputStream_
outputStream_ = "java.io.BufferedOutputStream@d69f29"
发送完毕,这里可以看到,确实是同步发送,接下来就是接收了
return recv_helloWorldString();
可以查看栈
Thread-5[1] where
[1] org.apache.thrift.transport.TFramedTransport.readFrame (TFramedTransport.java:129)
[2] org.apache.thrift.transport.TFramedTransport.read (TFramedTransport.java:101)
[3] org.apache.thrift.transport.TTransport.readAll (TTransport.java:86)
[4] org.apache.thrift.protocol.TBinaryProtocol.readAll (TBinaryProtocol.java:429)
[5] org.apache.thrift.protocol.TBinaryProtocol.readI32 (TBinaryProtocol.java:318)
[6] org.apache.thrift.protocol.TBinaryProtocol.readMessageBegin (TBinaryProtocol.java:219)
[7] org.apache.thrift.protocol.TProtocolDecorator.readMessageBegin (TProtocolDecorator.java:135)
[8] org.apache.thrift.TServiceClient.receiveBase (TServiceClient.java:77)
到最后,都是涉及到inputStream_
这个也是跟outputStream_一个意思,所以这里确实是同步处理的。
------更细的细节,这里就不说了,thrift能有比较好的效率,确实做得不错,果然是facebook.
另外注意到一点,每次请求,都会发送一个seqid过去,这个seqid必须在返回值里存在且一样
protected void receiveBase(TBase<?, ?> result, String methodName) throws TException {
TMessage msg = iprot_.readMessageBegin();
if (msg.type == TMessageType.EXCEPTION) {
TApplicationException x = TApplicationException.read(iprot_);
iprot_.readMessageEnd();
throw x;
}
if (msg.seqid != seqid_) {//序列号判断
throw new TApplicationException(TApplicationException.BAD_SEQUENCE_ID,
methodName + " failed: out of sequence response");
}
result.read(iprot_);
iprot_.readMessageEnd();
}
所以,再花一点时间,看看这个 seqid在服务端的处理。
这里就需要翻看我以前的博客了:https://my.oschina.net/qiangzigege/blog/517263
方便快速找到我想要的代码位置
其实就一句话,收到了seqid,然后回写,代码如下:
public final void process(int seqid, TProtocol iprot, TProtocol oprot, I iface) throws TException {
T args = getEmptyArgsInstance();
try {
args.read(iprot);
} catch (TProtocolException e) {
iprot.readMessageEnd();
TApplicationException x = new TApplicationException(TApplicationException.PROTOCOL_ERROR, e.getMessage());
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
x.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
return;
}
iprot.readMessageEnd();
TBase result = null;
try {
result = getResult(iface, args);
} catch(TException tex) {
LOGGER.error("Internal error processing " + getMethodName(), tex);
TApplicationException x = new TApplicationException(TApplicationException.INTERNAL_ERROR,
"Internal error processing " + getMethodName());
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.EXCEPTION, seqid));
x.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
return;
}
if(!isOneway()) {
logger.info("oprot---"+oprot);
logger.info("oprot.getTransport()---"+oprot.getTransport());
//这里回写seqid
oprot.writeMessageBegin(new TMessage(getMethodName(), TMessageType.REPLY, seqid));
result.write(oprot);
oprot.writeMessageEnd();
oprot.getTransport().flush();
}
}
注意一个细节:这里的oprot.getTransport()
oprot.getTransport()---org.apache.thrift.transport.TFramedTransport@7ef6f0aa - [org.apache.thrift.ProcessFunction.24]
flush invoked... - [org.apache.thrift.transport.TFramedTransport.24]
transport_ org.apache.thrift.transport.TIOStreamTransport@3bf46110 - [org.apache.thrift.transport.TFramedTransport.24]
outputStream_---class org.apache.thrift.TByteArrayOutputStream
所以,这里的flush实际上什么都不做,因为真正的事情,是交给第二层的IO线程来做。
Thrift真是不错啊,业内良心。