Thrift是一个rpc框架,也是一个不错的序列化框架,支持多种语言,此处我使用的是Java。
1、当然开始之前需要下载thrift到本地,我这里下载的是最新的0.12.0版本,并设置好环境变量
2、编写IDL
##命令空间
namespace java com.dxy.learn_netty.thrift
##为了书写习惯,将thrift的类型映射到java中的类型
typedef i16 short
typedef i32 int
typedef i64 long
typedef bool boolean
##编写一个结构体,用于构造数据,进行传输
struct Person{
1:optional string name,
2:optional int age,
3:optional boolean married
}
##定义服务接口,两个方法
service PersonService{
Person getPersonByName (1:required string name),
void savePerson(1:required Person person)
}
3、用thrift编译上面编写的IDL文件,命令非常简单,language是语言,这里当然是java了,Thrift filename是IDL文件名xxx.thrift
thrift --gen <language> <Thrift filename>
4、上面的IDL文件经过编译会生成两个java文件,分别是Person.java和PersonService.java,很好理解,一个是对象,一个是服务接口,然后将两个文件拷到项目对应的目录下即可。生成的代码出了一点问题,两个java文件中@Override标识的地方全部报错,提示父类中没有对应方法,不需要添加该注解,在网上也没找到出问题的具体原因,便将所有的@Override都去掉就好了。
5、在项目的pom.xml中引入相应的thrift依赖包
<dependency>
<groupId>org.apache.thrift</groupId>
<artifactId>libthrift</artifactId>
<version>0.12.0</version>
</dependency>
6、实现thrift自动生成出来的接口服务PersonService
package com.dxy.learn_netty.thrift;
import org.apache.thrift.TException;
public class PersonServiceImpl implements PersonService.Iface{
public Person getPersonByName(String name) throws TException {
Person person = new Person();
person.setName(name);
person.setAge(20);
person.setMarried(false);
return person;
}
public void savePerson(Person person) throws TException {
System.out.println(person.getName());
System.out.println(person.getAge());
System.out.println(person.isMarried());
}
}
7、编写服务端代码
package com.dxy.learn_netty.thrift;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
public class ThriftTestServer {
public static void main(String[] args) throws Exception{
//实例化一个非阻塞的ServerSocket,绑定端口号
TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899);
//实例化一个半同步半异步服务下的参数对象,设置最小工作线程数为3最大线程数5
THsHaServer.Args arg = new THsHaServer.Args(serverSocket).minWorkerThreads(3).maxWorkerThreads(5);
//构造自定义的服务的Processor
PersonService.Processor<PersonServiceImpl> processor
= new PersonService.Processor<PersonServiceImpl>(new PersonServiceImpl());
//在上面构造的参数中设置传输协议是压缩协议
arg.protocolFactory(new TCompactProtocol.Factory());
//设置传输方式为TFramedTransport
arg.transportFactory(new TFramedTransport.Factory());
//设置服务的Processor
arg.processorFactory(new TProcessorFactory(processor));
//构造一个半同步半异步
TServer server = new THsHaServer(arg);
System.out.println("Thrift Server Started......");
//启动服务,开启监听
server.serve();
}
}
8、编写客户端代码
package com.dxy.learn_netty.thrift;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport;
public class ThriftTestClient {
public static void main(String[] args) throws Exception{
//跟服务端保持一致,使用TFramedTransport传输方式
TTransport transport = new TFramedTransport(new TSocket("localhost", 8899),600);
//跟服务端保持一致,使用压缩协议
TProtocol protocol = new TCompactProtocol(transport);
//构造一个客户端实例
PersonService.Client client = new PersonService.Client(protocol);
try {
//开服务,进行远程连接
//这个方法会上面定义的TFramedTransport中去,然后再调用到参数TSocket中,并new一个Socket出来
transport.open();
Person person = client.getPersonByName("孙悟空");
System.out.println(person.name);
System.out.println(person.age);
System.out.println(person.married);
System.out.println("---------------");
Person person2 = new Person();
person2.setName("猪八戒");
person2.setAge(40);
person2.setMarried(true);
client.savePerson(person2);
}catch(Exception e) {
e.printStackTrace();
}finally {
//关闭客户端连接
transport.close();
}
}
}
客户端代码中的transport.open();看了一下源码,最后调用到了第一行代码中new出来的TSocket中去了,源码如下
/**
* 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_ == null || host_.length() == 0) {
throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");
}
if (port_ <= 0 || port_ > 65535) {
throw new TTransportException(TTransportException.NOT_OPEN, "Invalid port " + port_);
}
if (socket_ == null) {
initSocket();
}
try {
socket_.connect(new InetSocketAddress(host_, port_), connectTimeout_);
inputStream_ = new BufferedInputStream(socket_.getInputStream());
outputStream_ = new BufferedOutputStream(socket_.getOutputStream());
} catch (IOException iox) {
close();
throw new TTransportException(TTransportException.NOT_OPEN, iox);
}
}
/**
* Initializes the socket object
*/
private void initSocket() {
socket_ = new Socket();
try {
socket_.setSoLinger(false, 0);
socket_.setTcpNoDelay(true);
socket_.setKeepAlive(true);
socket_.setSoTimeout(socketTimeout_);
} catch (SocketException sx) {
LOGGER.error("Could not configure socket.", sx);
}
}
非常明显,这里实例化了一个Socket对象设置了一些参数,然后进行了connect()连接之后获取了BufferedInputStream和BufferedOutputStream对象,接下来应该就是用流对象进行数据传输了吧。
9、分别启动服务端和客户端代码
服务端输出:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Thrift Server Started......
猪八戒
40
true
客户端输出:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
孙悟空
20
false
---------------
到此,第一个Thrift程序就算完成了,初次接触Thrift,给我的感觉是非常像HttpClient的使用,很轻量级,挺好用,还需要花时间继续深入学习,如有任何问题还请各位看官指正,谢谢!
本文代码参考了张龙老师的课程,在此表示感谢。