Thrift笔记(七)--回调源码分析

网上找了写代码,东拼西凑写了个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方法

扫描二维码关注公众号,回复: 2492214 查看本文章
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中的注册

猜你喜欢

转载自www.cnblogs.com/luckygxf/p/9398944.html
今日推荐