网上找了写代码,东拼西凑写了个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中的注册