• 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方法

    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中的注册

  • 相关阅读:
    Jedis scan及其count的值
    redis中KEYS、SMEMBERS、SCAN 、SSCAN 的区别
    Windows环境下RabbitMQ的启动和停止命令
    HTTP状态码->HTTP Status Code
    给所有的input trim去空格
    git clone 使用用户名和密码
    ABA问题
    FIFO、LRU、LFU的含义和原理
    【phpstorm】破解安装
    【windows7】解决IIS 80端口占用问题(亲测)
  • 原文地址:https://www.cnblogs.com/luckygxf/p/9398944.html
Copyright © 2020-2023  润新知