网上找了写代码,东拼西凑写了个demo。开始server用的是阻塞io,不行,换成非阻塞的io就可以。这里可能需要注意下

thrift文件

namespace java com.gxf.thrift

enum RequestType {
SAY_HELLO, //问好
QUERY_TIME, //询问时间
} struct Request {
1: required RequestType type; // 请求的类型,必选
2: required string name; // 发起请求的人的名字,必选
3: optional i32 age; // 发起请求的人的年龄,可选
} exception RequestException {
1: required i32 code;
2: optional string reason;
} // 服务名
service HelloWordService {
string doAction(1: Request request) throws (1:RequestException qe); // 可能抛出异常。
}

Server接口实现

import org.apache.commons.lang3.StringUtils;

import java.util.Date;

public class HelloWordServiceImpl implements HelloWordService.Iface {

    // 实现这个方法完成具体的逻辑。
public String doAction(Request request)
throws RequestException, org.apache.thrift.TException {
System.out.println("Get request: " + request);
if (StringUtils.isBlank(request.getName()) || request.getType() == null) {
throw new com.gxf.thrift.RequestException();
}
String result = "Hello, " + request.getName();
if (request.getType() == com.gxf.thrift.RequestType.SAY_HELLO) {
result += ", Welcome!";
} else {
result += ", Now is " + new Date().toLocaleString();
}
return result; }
}

Server启动代码

import org.apache.thrift.TProcessor;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TServerSocket; import java.net.ServerSocket; public class HelloWordServer { public static void main(String[] args) throws Exception {
asynServer();
} private static void asynServer() throws Exception{
int port = 7912;
TNonblockingServerSocket socket = new TNonblockingServerSocket(port);
final HelloWordService.Processor processor = new HelloWordService.Processor(new HelloWordServiceImpl());
THsHaServer.Args arg = new THsHaServer.Args(socket);
// 高效率的、密集的二进制编码格式进行数据传输
// 使用非阻塞方式,按块的大小进行传输,类似于 Java 中的 NIO
arg.protocolFactory(new TCompactProtocol.Factory());
arg.transportFactory(new TFramedTransport.Factory());
arg.processorFactory(new TProcessorFactory(processor));
TServer server = new THsHaServer(arg);
server.serve();
} private static void sampleServer () throws Exception{
ServerSocket socket = new ServerSocket(7912);
TServerSocket serverTransport = new TServerSocket(socket);
// com.gxf.thrift.HelloWordService.Processor processor = new com.gxf.thrift.HelloWordService.Processor(
// new HelloWordServiceImpl());
// TServer server = new TSimpleServer(processor, serverTransport);
// System.out.println("Running server...");
// server.serve();
TBinaryProtocol.Factory proFactory = new TBinaryProtocol.Factory(); /**
* 关联处理器与GreetingService服务实现
*/
TProcessor processor = new HelloWordService.Processor(new HelloWordServiceImpl()); TThreadPoolServer.Args serverArgs = new TThreadPoolServer.Args(serverTransport);
serverArgs.processor(processor);
serverArgs.protocolFactory(proFactory);
TServer server = new TThreadPoolServer(serverArgs);
System.out.println("Start server on port 7912..."); server.serve();
} }

Callback实现类

import org.apache.thrift.async.AsyncMethodCallback;

public class ThriftCallback implements AsyncMethodCallback<String> {

    @Override
public void onComplete(String response) {
System.out.println("onComplete");
System.out.println(response);
} @Override
public void onError(Exception exception) {
System.out.println("onError");
exception.printStackTrace();
}
}

客户端测试代码

import org.apache.thrift.async.TAsyncClientManager;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TCompactProtocol;
import org.apache.thrift.protocol.TProtocol;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.transport.TNonblockingSocket;
import org.apache.thrift.transport.TNonblockingTransport;
import org.apache.thrift.transport.TSocket;
import org.apache.thrift.transport.TTransport; public class HelloWordClient {
public static void main(String[] args) throws Exception {
asynCall();
} private static void sampleCall() throws Exception{
TTransport transport = new TSocket("127.0.0.1", 7912);
TProtocol protocol = new TBinaryProtocol(transport); // 创建client
com.gxf.thrift.HelloWordService.Client client = new com.gxf.thrift.HelloWordService.Client(protocol); transport.open(); // 建立连接 // 第一种请求类型
com.gxf.thrift.Request request = new com.gxf.thrift.Request()
.setType(com.gxf.thrift.RequestType.SAY_HELLO).setName("guanxianseng").setAge(28);
System.out.println(client.doAction(request)); // 第二种请求类型
request.setType(com.gxf.thrift.RequestType.QUERY_TIME).setName("guanxianseng");
System.out.println(client.doAction(request)); transport.close(); // 请求结束,断开连接
} private static void asynCall() throws Exception {
String address = "127.0.0.1";
int port = 7912;
int clientTimeout = 3000;
TAsyncClientManager clientManager = new TAsyncClientManager();
TNonblockingTransport transport = new TNonblockingSocket(address, port, clientTimeout);
TProtocolFactory protocol = new TCompactProtocol.Factory();
HelloWordService.AsyncClient asyncClient = new HelloWordService.AsyncClient(protocol, clientManager, transport); com.gxf.thrift.Request request = new com.gxf.thrift.Request()
.setType(com.gxf.thrift.RequestType.SAY_HELLO).setName("guanxianseng").setAge(28);
asyncClient.doAction(request, new ThriftCallback()); while(true){
Thread.sleep(1000);
}
} }

ok。上面是demo

跟进源码前,说下大概流程。client使用nio channel发送数据。将channel注册到selector中,监听对应的accept, read, write等事件。

主要分析客户端代码,跟进

TAsyncClientManager
public TAsyncClientManager() throws IOException {
this.selectThread = new SelectThread();
selectThread.start();
}

这里可以看出起了一个select线程,跟进这个线程实现类

private class SelectThread extends Thread {
private final Selector selector;
private volatile boolean running;
private final TreeSet<TAsyncMethodCall> timeoutWatchSet = new TreeSet<TAsyncMethodCall>(new TAsyncMethodCallTimeoutComparator()); public SelectThread() throws IOException {
this.selector = SelectorProvider.provider().openSelector();
this.running = true;
this.setName("TAsyncClientManager#SelectorThread " + this.getId()); // We don't want to hold up the JVM when shutting down
setDaemon(true);
}

可以看出这里有个Selector对象,后面的channel会注册进来。看下run方法

public void run() {
while (running) {
try {
try {
if (timeoutWatchSet.size() == 0) {
// No timeouts, so select indefinitely
selector.select();
} else {
// We have a timeout pending, so calculate the time until then and select appropriately
long nextTimeout = timeoutWatchSet.first().getTimeoutTimestamp();
long selectTime = nextTimeout - System.currentTimeMillis();
if (selectTime > 0) {
// Next timeout is in the future, select and wake up then
selector.select(selectTime);
} else {
// Next timeout is now or in past, select immediately so we can time out
selector.selectNow();
}
}
} catch (IOException e) {
LOGGER.error("Caught IOException in TAsyncClientManager!", e);
}
transitionMethods();
timeoutMethods();
startPendingMethods();
} catch (Exception exception) {
LOGGER.error("Ignoring uncaught exception in SelectThread", exception);
}
} try {
selector.close();
} catch (IOException ex) {
LOGGER.warn("Could not close selector. This may result in leaked resources!", ex);
}
}

这里可以看出,客户单select注册的channel,接收服务端返回的结果。注意这里会阻塞到select()方法,也没有注册。后面会有一个wakeup()方法会结束阻塞,完成channel注册和select轮询

private static void asynCall() throws Exception {
String address = "127.0.0.1";
int port = 7912;
int clientTimeout = 3000;
TAsyncClientManager clientManager = new TAsyncClientManager();
TNonblockingTransport transport = new TNonblockingSocket(address, port, clientTimeout);
TProtocolFactory protocol = new TCompactProtocol.Factory();
HelloWordService.AsyncClient asyncClient = new HelloWordService.AsyncClient(protocol, clientManager, transport); com.gxf.thrift.Request request = new com.gxf.thrift.Request()
.setType(com.gxf.thrift.RequestType.SAY_HELLO).setName("guanxianseng").setAge(28);
asyncClient.doAction(request, new ThriftCallback()); while(true){
Thread.sleep(1000);
}
}

中间部分代码,是一些注册,常用的观察者模式的初始化。主要看下doAction(),即调用服务端rpc

public void doAction(Request request, org.apache.thrift.async.AsyncMethodCallback<String> resultHandler) throws org.apache.thrift.TException {
checkReady();
doAction_call method_call = new doAction_call(request, resultHandler, this, ___protocolFactory, ___transport);
this.___currentMethod = method_call;
___manager.call(method_call);
}

跟进call方法

public void call(TAsyncMethodCall method) throws TException {
if (!isRunning()) {
throw new TException("SelectThread is not running");
}
method.prepareMethodCall();
pendingCalls.add(method);
selectThread.getSelector().wakeup();
}

这里有个wakeup()方法,会中断前面selector线程的select()阻塞。回到前面的阻塞代码,即run方法

public void run() {
while (running) {
try {
try {
if (timeoutWatchSet.size() == 0) {
// No timeouts, so select indefinitely
selector.select();
} else {
// We have a timeout pending, so calculate the time until then and select appropriately
long nextTimeout = timeoutWatchSet.first().getTimeoutTimestamp();
long selectTime = nextTimeout - System.currentTimeMillis();
if (selectTime > 0) {
// Next timeout is in the future, select and wake up then
selector.select(selectTime);
} else {
// Next timeout is now or in past, select immediately so we can time out
selector.selectNow();
}
}
} catch (IOException e) {
LOGGER.error("Caught IOException in TAsyncClientManager!", e);
}
transitionMethods();
timeoutMethods();
startPendingMethods();
} catch (Exception exception) {
LOGGER.error("Ignoring uncaught exception in SelectThread", exception);
}
}

跟进 transitionMethods()方法

// Transition methods for ready keys
private void transitionMethods() {
try {
Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
while (keys.hasNext()) {
SelectionKey key = keys.next();
keys.remove();
if (!key.isValid()) {
// this can happen if the method call experienced an error and the
// key was cancelled. can also happen if we timeout a method, which
// results in a channel close.
// just skip
continue;
}
TAsyncMethodCall methodCall = (TAsyncMethodCall)key.attachment();
methodCall.transition(key); // If done or error occurred, remove from timeout watch set
if (methodCall.isFinished() || methodCall.getClient().hasError()) {
timeoutWatchSet.remove(methodCall);
}
}
} catch (ClosedSelectorException e) {
LOGGER.error("Caught ClosedSelectorException in TAsyncClientManager!", e);
}
}

这里主要是处理注册channel返回的key.回到前面run,跟进下面方法

// Start any new calls
private void startPendingMethods() {
TAsyncMethodCall methodCall;
while ((methodCall = pendingCalls.poll()) != null) {
// Catch registration errors. method will catch transition errors and cleanup.
try {
methodCall.start(selector); // If timeout specified and first transition went smoothly, add to timeout watch set
TAsyncClient client = methodCall.getClient();
if (client.hasTimeout() && !client.hasError()) {
timeoutWatchSet.add(methodCall);
}
} catch (Exception exception) {
LOGGER.warn("Caught exception in TAsyncClientManager!", exception);
methodCall.onError(exception);
}
}
}
}

跟进start方法

/**
* Register with selector and start first state, which could be either connecting or writing.
* @throws IOException if register or starting fails
*/
void start(Selector sel) throws IOException {
SelectionKey key;
if (transport.isOpen()) {
state = State.WRITING_REQUEST_SIZE;
key = transport.registerSelector(sel, SelectionKey.OP_WRITE);
} else {
state = State.CONNECTING;
key = transport.registerSelector(sel, SelectionKey.OP_CONNECT); // non-blocking connect can complete immediately,
// in which case we should not expect the OP_CONNECT
if (transport.startConnect()) {
registerForFirstWrite(key);
}
} key.attach(this);
}

这里可以看到channel在selector中的注册

Thrift笔记(七)--回调源码分析的更多相关文章

  1. memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...

  2. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

  3. tornado 学习笔记6 Application 源码分析

    Application 是Tornado重要的模块之一,主要是配置访问路由表及其他应用参数的设置. 源代码位于虚拟运行环境文件夹下(我的是env),具体位置为env > lib>sit-p ...

  4. OA学习笔记-010-Struts部分源码分析、Intercepter、ModelDriver、OGNL、EL

    一.分析 二. 1.OGNL 在访问action前,要经过各种intercepter,其中ParameterFilterInterceptor会把各咱参数放到ValueStack里,从而使OGNL可以 ...

  5. 正式学习React (七) react-router 源码分析

    学习react已经有10来天了,对于react redux react-redux 的使用流程和原理,也已经有一定的了解,在我上一篇的实战项目里,我用到了react-route,其实对它还只是 停留在 ...

  6. Java并发编程笔记之FutureTask源码分析

    FutureTask可用于异步获取执行结果或取消执行任务的场景.通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Fu ...

  7. Java并发编程笔记之SimpleDateFormat源码分析

    SimpleDateFormat 是 Java 提供的一个格式化和解析日期的工具类,日常开发中应该经常会用到,但是由于它是线程不安全的,多线程公用一个 SimpleDateFormat 实例对日期进行 ...

  8. Java并发编程笔记之Timer源码分析

    timer在JDK里面,是很早的一个API了.具有延时的,并具有周期性的任务,在newScheduledThreadPool出来之前我们一般会用Timer和TimerTask来做,但是Timer存在一 ...

  9. Java并发编程笔记之CyclicBarrier源码分析

    JUC 中 回环屏障 CyclicBarrier 的使用与分析,它也可以实现像 CountDownLatch 一样让一组线程全部到达一个状态后再全部同时执行,但是 CyclicBarrier 可以被复 ...

随机推荐

  1. 洛谷P3613 睡觉困难综合征

    传送门 题解 人生第一道由乃…… 做这题之前应该先去把这一题给切掉->这里 我的题解->这里 然后先膜一波zsy大佬和flashhu大佬 大体思路就是先吧全0和全1的都跑答案,然后按位贪心 ...

  2. mycat 1.6.6.1 distinct报错问题

    以前在mysql5.7上执行如下sql语句没有问题 SELECT DISTINCT u.*,c.content userCategory FROM m_user u LEFT JOIN m_categ ...

  3. Jupyter Notebook远程服务器配置[转]

    首先要生成密码,打开python终端. In [1]: from IPython.lib import passwd In [2]: passwd() Enter password: Verify p ...

  4. ArchLinux 下文件描述符

    stderr -> /proc/self/fd/2 标准错误:2 stdin -> /proc/self/fd/0 标准输入:0 stdout -> /proc/self/fd/1 ...

  5. CentOS6.9 ARM虚拟机扩容系统磁盘

    由于扩容磁盘的操作非同小可,一旦哪一步出现问题,就会导致分区损坏,数据丢失等一系列严重的问题,因此建议:在进行虚拟机分区扩容之前,一定要备份重要数据文件,并且先在测试机上验证以下步骤,再应用于您的生产 ...

  6. P1979华容道(神仙题)

    题目描述 小 B 最近迷上了华容道,可是他总是要花很长的时间才能完成一次.于是,他想到用编程来完成华容道:给定一种局面, 华容道是否根本就无法完成,如果能完成, 最少需要多少时间. 小 B 玩的华容道 ...

  7. js 中常见的深拷贝的方法

    建议最简单的第一种 1.通过 JSON 对象实现深拷贝 this.data = JSON.parse(JSON.stringify(this.vm.$store.state.security.menu ...

  8. 文件IO(存取.txt文件)

    //存文件方法 public void Save_File_Info(string Save_Path) { //根据路径,创建文件和数据流 FileStream FS = new FileStrea ...

  9. fix the issue that disk space is not the size that aws ec2 have.

    在申请aws ec2时,按照向导,在选择存储的时候默认硬盘大小是 8 G,这时候可以根据自己的需要输入一个合适的数字,例如100.完成向导并启动ec2 instance 后登陆机器.使用命令: df ...

  10. centos 7编译安装apache

    1.安装工具和依赖包 yum install unzipyum -y install gcc gcc-c++ 2.创建软件安装目录mkdir /usr/local/{apr,apr-util,apr- ...