“J.U.C”:CountDownlatch (r)

本文深入剖析了CountDownLatch的工作原理,包括其内部实现机制、构造函数、await及countDown方法的源码解析,并通过一个员工开会的实际例子展示了如何使用CountDownLatch。

上篇博文(【Java并发编程实战】—–“J.U.C”:CyclicBarrier)LZ介绍了CyclicBarrier。CyclicBarrier所描述的是“允许一组线程互相等待,直到到达某个公共屏障点,才会进行后续任务”。而CountDownlatch和它也有一点点相似之处:CountDownlatch所描述的是“在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待”。在JDK API中是这样阐述的:

用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。(await()方法是相当于获取锁,条件不满足阻塞,等待释放锁后进行通知,countDown()方法相当于释放锁,然后进行通知等待线程的操作)

CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。

虽然,CountDownlatch与CyclicBarrier有那么点相似,但是他们还是存在一些区别的:

1、CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待。

2、 CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

CountDownLatch分析

CountDownLatch结构如下:


从上图中可以看出CountDownLatch依赖Sync,其实CountDownLatch内部采用的是共享锁来实现的(内部Sync的实现可以看出)。它的构造函数如下:

CountDownLatch(int count):构造一个用给定计数初始化的 CountDownLatch。

[java]  view plain  copy
 print ?
  1. public CountDownLatch(int count) {  
  2.         if (count < 0throw new IllegalArgumentException("count < 0");  
  3.         this.sync = new Sync(count);  
  4.     }  

以下源代码可以证明,CountDownLatch内部是采用共享锁来实现的:

[java]  view plain  copy
 print ?
  1. private static final class Sync extends AbstractQueuedSynchronizer {  
  2.         private static final long serialVersionUID = 4982264981922014374L;  
  3.   
  4.         protected int tryAcquireShared(int acquires) {  //具体的获取锁的逻辑就是重写这个方法
  5.             /** 省略源代码 **/  
  6.         }  
  7.   
  8.         protected boolean tryReleaseShared(int releases) {   //具体的释放锁的逻辑就是重写这个方法
  9.            /** 省略源代码 **/  
  10.         }  
  11.     }  

CountDownLatch提供了await方法来实现:

await():使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断

await(long timeout, TimeUnit unit): 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间

[java]  view plain  copy
 print ?
  1. public void await() throws InterruptedException {  
  2.         sync.acquireSharedInterruptibly(1);  
  3.     }  

await内部调用sync的acquireSharedInterruptibly方法:

[java]  view plain  copy
 print ?
  1. public final void acquireSharedInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.         //线程中断,抛出InterruptedException异常  
  4.         if (Thread.interrupted())  
  5.             throw new InterruptedException();  
  6.         if (tryAcquireShared(arg) < 0)  
  7.             doAcquireSharedInterruptibly(arg);  
  8.     }  

acquireSharedInterruptibly()的作用是获取共享锁。如果在获取共享锁过程中线程中断则抛出InterruptedException异常。否则通过tryAcquireShared方法来尝试获取共享锁。如果成功直接返回,否则调用doAcquireSharedInterruptibly方法。

tryAcquireShared源码:

[java]  view plain  copy
 print ?
  1. protected int tryAcquireShared(int acquires) {  
  2.             return (getState() == 0) ? 1 : -1;  
  3.         }  

tryAcquireShared方法被CountDownLatch重写,他的主要作用是尝试着获取锁。getState == 0 表示锁处于可获取状态返回1否则返回-1;当tryAcquireShared返回-1获取锁失败,调用doAcquireSharedInterruptibly获取锁:

[java]  view plain  copy
 print ?
  1. private void doAcquireSharedInterruptibly(int arg)  
  2.             throws InterruptedException {  
  3.             //创建当前线程(共享锁)Node节点  
  4.             final Node node = addWaiter(Node.SHARED);  
  5.             boolean failed = true;  
  6.             try {  
  7.                 for (;;) {  
  8.                     //获取当前节点的前继节点  
  9.                     final Node p = node.predecessor();  
  10.                     //如果当前节点为CLH列头,则尝试获取锁  
  11.                     if (p == head) {  
  12.                         //获取锁  
  13.                         int r = tryAcquireShared(arg);  
  14.                         if (r >= 0) {  
  15.                             setHeadAndPropagate(node, r);  
  16.                             p.next = null// help GC  
  17.                             failed = false;  
  18.                             return;  
  19.                         }  
  20.                     }  
  21.                     //如果当前节点不是CLH列头,当前线程一直等待,直到获取锁为止  
  22.                     if (shouldParkAfterFailedAcquire(p, node) &&  
  23.                         parkAndCheckInterrupt())  
  24.                         throw new InterruptedException();  
  25.                 }  
  26.             } finally {  
  27.                 if (failed)  
  28.                     cancelAcquire(node);  
  29.             }  
  30.         }  

该方法当中的方法,前面博客都讲述过,请参考:【Java并发编程实战】—–“J.U.C”:ReentrantLock之二lock方法分析、【Java并发编程实战】—–“J.U.C”:Semaphore。

CountDownLatch,除了提供await方法外,还提供了countDown(),countDown所描述的是“递减锁存器的计数,如果计数到达零,则释放所有等待的线程。”,源码如下:

[java]  view plain  copy
 print ?
  1. public void countDown() {  
  2.         sync.releaseShared(1);  
  3.     }  

countDown内部调用releaseShared方法来释放线程:

[java]  view plain  copy
 print ?
  1. public final boolean releaseShared(int arg) {  
  2.         //尝试释放线程,如果释放释放则调用doReleaseShared()  
  3.         if (tryReleaseShared(arg)) {  
  4.             doReleaseShared();  
  5.             return true;  
  6.         }  
  7.         return false;  
  8.     }  

tryReleaseShared,同时被CountDownLatch重写了:

[java]  view plain  copy
 print ?
  1. protected boolean tryReleaseShared(int releases) {  
  2.         for (;;) {  
  3.             //获取锁状态  
  4.             int c = getState();  
  5.             //c == 0 直接返回,释放锁成功  
  6.             if (c == 0)  
  7.                 return false;  
  8.             //计算新“锁计数器”  
  9.             int nextc = c-1;  
  10.             //更新锁状态(计数器)  
  11.             if (compareAndSetState(c, nextc))  
  12.                 return nextc == 0;  
  13.         }  
  14.     }  

总结:

CountDownLatch内部通过“共享锁”实现。在创建CountDownLatch时,需要传递一个int类型的count参数,该count参数为“锁状态”的初始值,该值表示着该“共享锁”可以同时被多少线程获取。当某个线程调用await方法时,首先判断锁的状态是否处于可获取状态(其条件就是count==0?),如果共享锁可获取则获取共享锁,否则一直处于等待直到获取为止。当线程调用countDown方法时,计数器count – 1。当在创建CountDownLatch时初始化的count参数,必须要有count线程调用countDown方法才会使计数器count等于0,锁才会释放,前面等待的线程才会继续运行。

实例

员工开会只有当所有人到期之后才会开户。我们初始化与会人员为3个,那么CountDownLatch的count应为3:

[java]  view plain  copy
 print ?
  1. public class Conference implements Runnable{  
  2.     private final CountDownLatch countDown;  
  3.       
  4.     public Conference(int count){  
  5.         countDown = new CountDownLatch(count);  
  6.     }  
  7.       
  8.     /** 
  9.      * 与会人员到达,调用arrive方法,到达一个CountDownLatch调用countDown方法,锁计数器-1 
  10.      * @author:chenssy 
  11.      * @data:2015年9月6日 
  12.      * 
  13.      * @param name 
  14.      */  
  15.     public void arrive(String name){  
  16.         System.out.println(name + "到达.....");  
  17.         //调用countDown()锁计数器 - 1  
  18.         countDown.countDown();  
  19.         System.out.println("还有 " + countDown.getCount() + "没有到达...");  
  20.     }  
  21.       
  22.     @Override  
  23.     public void run() {  
  24.         System.out.println("准备开会,参加会议人员总数为:" + countDown.getCount());  
  25.         //调用await()等待所有的与会人员到达  
  26.         try {  
  27.             countDown.await();  
  28.         } catch (InterruptedException e) {  
  29.         }  
  30.         System.out.println("所有人员已经到达,会议开始.....");  
  31.     }  
  32. }  

参加与会人员Participater:

[java]  view plain  copy
 print ?
  1. public class Participater implements Runnable{  
  2.     private String name;  
  3.     private Conference conference;  
  4.       
  5.     public Participater(String name,Conference conference){  
  6.         this.name = name;  
  7.         this.conference = conference;  
  8.     }  
  9.   
  10.     @Override  
  11.     public void run() {  
  12.         conference.arrive(name);  
  13.     }  
  14. }  

Test:

[java]  view plain  copy
 print ?
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         //启动会议室线程,等待与会人员参加会议  
  4.         Conference conference = new Conference(3);  
  5.         new Thread(conference).start();  
  6.           
  7.         for(int i = 0 ; i < 3 ; i++){  
  8.             Participater participater = new Participater("chenssy-0" + i , conference);  
  9.             Thread thread = new Thread(participater);  
  10.             thread.start();  
  11.         }  
  12.     }  
  13. }  

运行结果:

[java]  view plain  copy
 print ?
  1. 准备开会,参加会议人员总数为:3  
  2. chenssy-01到达.....  
  3. 还有 2没有到达...  
  4. chenssy-00到达.....  
  5. 还有 1没有到达...  
  6. chenssy-02到达.....  
  7. 还有 0没有到达...  
  8. 所有人员已经到达,会议开始.....  
"C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\bin\java.exe" "-javaagent:D:\IntelliJ IDEA Community Edition 2025.1.4.1\lib\idea_rt.jar=57570" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\charsets.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\dnsns.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\jaccess.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\localedata.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\nashorn.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunec.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\zipfs.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jce.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jfr.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jfxswt.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jsse.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\management-agent.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\resources.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\rt.jar;D:\r\basic-spring-boot\target\classes;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-validation\2.1.14.RELEASE\spring-boot-starter-validation-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter\2.1.14.RELEASE\spring-boot-starter-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot\2.1.14.RELEASE\spring-boot-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.1.14.RELEASE\spring-boot-autoconfigure-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.1.14.RELEASE\spring-boot-starter-logging-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\admin\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\admin\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;C:\Users\admin\.m2\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;C:\Users\admin\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\admin\.m2\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;C:\Users\admin\.m2\repository\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;C:\Users\admin\.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.34\tomcat-embed-el-9.0.34.jar;C:\Users\admin\.m2\repository\org\hibernate\validator\hibernate-validator\6.0.19.Final\hibernate-validator-6.0.19.Final.jar;C:\Users\admin\.m2\repository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;C:\Users\admin\.m2\repository\org\jboss\logging\jboss-logging\3.3.3.Final\jboss-logging-3.3.3.Final.jar;C:\Users\admin\.m2\repository\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-data-jpa\2.1.14.RELEASE\spring-boot-starter-data-jpa-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-jdbc\2.1.14.RELEASE\spring-boot-starter-jdbc-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;C:\Users\admin\.m2\repository\org\springframework\spring-jdbc\5.1.15.RELEASE\spring-jdbc-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\javax\transaction\javax.transaction-api\1.3\javax.transaction-api-1.3.jar;C:\Users\admin\.m2\repository\javax\xml\bind\jaxb-api\2.3.1\jaxb-api-2.3.1.jar;C:\Users\admin\.m2\repository\javax\activation\javax.activation-api\1.2.0\javax.activation-api-1.2.0.jar;C:\Users\admin\.m2\repository\org\hibernate\hibernate-core\5.3.17.Final\hibernate-core-5.3.17.Final.jar;C:\Users\admin\.m2\repository\javax\persistence\javax.persistence-api\2.2\javax.persistence-api-2.2.jar;C:\Users\admin\.m2\repository\org\javassist\javassist\3.23.2-GA\javassist-3.23.2-GA.jar;C:\Users\admin\.m2\repository\net\bytebuddy\byte-buddy\1.9.16\byte-buddy-1.9.16.jar;C:\Users\admin\.m2\repository\antlr\antlr\2.7.7\antlr-2.7.7.jar;C:\Users\admin\.m2\repository\org\jboss\jandex\2.0.5.Final\jandex-2.0.5.Final.jar;C:\Users\admin\.m2\repository\org\dom4j\dom4j\2.1.3\dom4j-2.1.3.jar;C:\Users\admin\.m2\repository\org\hibernate\common\hibernate-commons-annotations\5.0.4.Final\hibernate-commons-annotations-5.0.4.Final.jar;C:\Users\admin\.m2\repository\org\glassfish\jaxb\jaxb-runtime\2.3.1\jaxb-runtime-2.3.1.jar;C:\Users\admin\.m2\repository\org\glassfish\jaxb\txw2\2.3.1\txw2-2.3.1.jar;C:\Users\admin\.m2\repository\com\sun\istack\istack-commons-runtime\3.0.7\istack-commons-runtime-3.0.7.jar;C:\Users\admin\.m2\repository\org\jvnet\staxex\stax-ex\1.8\stax-ex-1.8.jar;C:\Users\admin\.m2\repository\com\sun\xml\fastinfoset\FastInfoset\1.2.15\FastInfoset-1.2.15.jar;C:\Users\admin\.m2\repository\org\springframework\data\spring-data-jpa\2.1.17.RELEASE\spring-data-jpa-2.1.17.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\data\spring-data-commons\2.1.17.RELEASE\spring-data-commons-2.1.17.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-orm\5.1.15.RELEASE\spring-orm-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-context\5.1.15.RELEASE\spring-context-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-tx\5.1.15.RELEASE\spring-tx-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-beans\5.1.15.RELEASE\spring-beans-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\admin\.m2\repository\org\springframework\spring-aspects\5.1.15.RELEASE\spring-aspects-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\com\mysql\mysql-connector-j\8.2.0\mysql-connector-j-8.2.0.jar;C:\Users\admin\.m2\repository\com\google\protobuf\protobuf-java\3.21.9\protobuf-java-3.21.9.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-web\2.1.14.RELEASE\spring-boot-starter-web-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-json\2.1.14.RELEASE\spring-boot-starter-json-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.9.10.4\jackson-databind-2.9.10.4.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.9.10\jackson-annotations-2.9.10.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.9.10\jackson-core-2.9.10.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.10\jackson-datatype-jdk8-2.9.10.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.10\jackson-datatype-jsr310-2.9.10.jar;C:\Users\admin\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.10\jackson-module-parameter-names-2.9.10.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\2.1.14.RELEASE\spring-boot-starter-tomcat-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.34\tomcat-embed-core-9.0.34.jar;C:\Users\admin\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.34\tomcat-embed-websocket-9.0.34.jar;C:\Users\admin\.m2\repository\org\springframework\spring-web\5.1.15.RELEASE\spring-web-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-webmvc\5.1.15.RELEASE\spring-webmvc-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-expression\5.1.15.RELEASE\spring-expression-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-aop\2.1.14.RELEASE\spring-boot-starter-aop-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-aop\5.1.15.RELEASE\spring-aop-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-starter-actuator\2.1.14.RELEASE\spring-boot-starter-actuator-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-actuator-autoconfigure\2.1.14.RELEASE\spring-boot-actuator-autoconfigure-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\boot\spring-boot-actuator\2.1.14.RELEASE\spring-boot-actuator-2.1.14.RELEASE.jar;C:\Users\admin\.m2\repository\org\projectlombok\lombok\1.18.12\lombok-1.18.12.jar;C:\Users\admin\.m2\repository\org\springframework\spring-core\5.1.15.RELEASE\spring-core-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\org\springframework\spring-jcl\5.1.15.RELEASE\spring-jcl-5.1.15.RELEASE.jar;C:\Users\admin\.m2\repository\com\alibaba\fastjson2\fastjson2\2.0.23\fastjson2-2.0.23.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-netty-shaded\1.56.0\grpc-netty-shaded-1.56.0.jar;C:\Users\admin\.m2\repository\com\google\guava\guava\31.1-android\guava-31.1-android.jar;C:\Users\admin\.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\admin\.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\admin\.m2\repository\org\checkerframework\checker-qual\3.12.0\checker-qual-3.12.0.jar;C:\Users\admin\.m2\repository\com\google\j2objc\j2objc-annotations\1.3\j2objc-annotations-1.3.jar;C:\Users\admin\.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\admin\.m2\repository\io\perfmark\perfmark-api\0.26.0\perfmark-api-0.26.0.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-protobuf\1.56.0\grpc-protobuf-1.56.0.jar;C:\Users\admin\.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\admin\.m2\repository\com\google\api\grpc\proto-google-common-protos\2.17.0\proto-google-common-protos-2.17.0.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-protobuf-lite\1.56.0\grpc-protobuf-lite-1.56.0.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-stub\1.56.0\grpc-stub-1.56.0.jar;C:\Users\admin\.m2\repository\net\devh\grpc-spring-boot-starter\2.14.0.RELEASE\grpc-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\net\devh\grpc-server-spring-boot-starter\2.14.0.RELEASE\grpc-server-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\net\devh\grpc-server-spring-boot-autoconfigure\2.14.0.RELEASE\grpc-server-spring-boot-autoconfigure-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\net\devh\grpc-common-spring-boot\2.14.0.RELEASE\grpc-common-spring-boot-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-core\1.51.0\grpc-core-1.51.0.jar;C:\Users\admin\.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar;C:\Users\admin\.m2\repository\com\google\android\annotations\4.1.1.4\annotations-4.1.1.4.jar;C:\Users\admin\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.21\animal-sniffer-annotations-1.21.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-services\1.51.0\grpc-services-1.51.0.jar;C:\Users\admin\.m2\repository\com\google\protobuf\protobuf-java-util\3.21.7\protobuf-java-util-3.21.7.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-api\1.51.0\grpc-api-1.51.0.jar;C:\Users\admin\.m2\repository\io\grpc\grpc-context\1.51.0\grpc-context-1.51.0.jar;C:\Users\admin\.m2\repository\net\devh\grpc-client-spring-boot-starter\2.14.0.RELEASE\grpc-client-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\net\devh\grpc-client-spring-boot-autoconfigure\2.14.0.RELEASE\grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar;C:\Users\admin\.m2\repository\io\micrometer\micrometer-core\1.11.4\micrometer-core-1.11.4.jar;C:\Users\admin\.m2\repository\io\micrometer\micrometer-commons\1.11.4\micrometer-commons-1.11.4.jar;C:\Users\admin\.m2\repository\io\micrometer\micrometer-observation\1.11.4\micrometer-observation-1.11.4.jar;C:\Users\admin\.m2\repository\org\hdrhistogram\HdrHistogram\2.1.12\HdrHistogram-2.1.12.jar;C:\Users\admin\.m2\repository\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar" com.tplink.nbu.demo.basicspringboot.GrpcClient 11:38:31.760 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework 11:38:31.764 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false 11:38:31.764 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - Java version: 8 11:38:31.764 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - direct buffer constructor: available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable prior to Java9 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.<init>(long, int): available 11:38:31.765 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available 11:38:31.766 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: C:\Users\admin\AppData\Local\Temp (java.io.tmpdir) 11:38:31.766 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model) 11:38:31.766 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - Platform: Windows 11:38:31.767 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: 7570718720 bytes 11:38:31.767 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1 11:38:31.767 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.CleanerJava6 - java.nio.ByteBuffer.cleaner(): available 11:38:31.767 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false 11:38:31.905 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.MultithreadEventLoopGroup - -Dio.netty.eventLoopThreads: 48 11:38:31.912 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024 11:38:31.912 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096 11:38:31.915 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop - -Dio.netty.noKeySetOptimization: false 11:38:31.915 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop - -Dio.netty.selectorAutoRebuildThreshold: 512 11:38:31.918 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - org.jctools-core.MpscChunkedArrayQueue: available 11:38:32.087 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector - -Dio.grpc.netty.shaded.io.netty.leakDetection.level: simple 11:38:32.087 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector - -Dio.grpc.netty.shaded.io.netty.leakDetection.targetRecords: 4 11:38:32.090 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.AbstractByteBuf - -Dio.grpc.netty.shaded.io.netty.buffer.checkAccessible: true 11:38:32.090 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.AbstractByteBuf - -Dio.grpc.netty.shaded.io.netty.buffer.checkBounds: true 11:38:32.090 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector@7f0df18 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 48 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 48 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 9 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 4194304 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimIntervalMillis: 0 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.useCacheForAllThreads: false 11:38:32.117 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023 11:38:32.135 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.channel.DefaultChannelId - -Dio.netty.processId: 8460 (auto-detected) 11:38:32.136 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false 11:38:32.136 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - -Djava.net.preferIPv6Addresses: false 11:38:32.176 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtilInitializations - Loopback interface: lo (Software Loopback Interface 1, 127.0.0.1) 11:38:32.176 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - Failed to get SOMAXCONN from sysctl and file \proc\sys\net\core\somaxconn. Default: 200 11:38:32.179 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.channel.DefaultChannelId - -Dio.netty.machineId: a0:ad:9f:ff:fe:1d:8c:93 (auto-detected) 11:38:32.189 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled 11:38:32.189 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0 11:38:32.189 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384 11:38:32.196 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096 11:38:32.196 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8 11:38:32.196 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.chunkSize: 32 11:38:32.196 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.blocking: false 11:38:32.207 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND SETTINGS: ack=false settings={ENABLE_PUSH=0, MAX_CONCURRENT_STREAMS=0, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192} 11:38:32.211 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041 11:38:32.256 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] INBOUND SETTINGS: ack=false settings={MAX_CONCURRENT_STREAMS=2147483647, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192} 11:38:32.257 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND SETTINGS: ack=true 11:38:32.265 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] INBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041 11:38:32.266 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] INBOUND SETTINGS: ack=true 11:38:32.271 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND HEADERS: streamId=3 headers=GrpcHttp2OutboundHeaders[:authority: localhost:9090, :path: /UserGrpcService/Register, :method: POST, :scheme: http, content-type: application/grpc, te: trailers, user-agent: grpc-java-netty/1.51.0, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false 11:38:32.277 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND DATA: streamId=3 padding=0 endStream=true length=49 bytes=000000002c0a08746573745573657212063132333435361a1074657374406578616d706c652e636f6d2206e58c97e4baac 11:38:32.302 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] INBOUND PING: ack=false bytes=1234 11:38:32.302 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] OUTBOUND PING: ack=true bytes=1234 11:38:32.308 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xf85c2e3d, L:/127.0.0.1:57623 - R:localhost/127.0.0.1:9090] INBOUND HEADERS: streamId=3 headers=GrpcHttp2ResponseHeaders[:status: 200, content-type: application/grpc, grpc-status: 12, grpc-message: Method not found: UserGrpcService/Register] padding=0 endStream=true 【注册失败】原因:Method not found: UserGrpcService/Register,请分析并修改,源代码如下public class GrpcClient { private final ManagedChannel channel; private final UserGrpcServiceGrpc.UserGrpcServiceFutureStub futureStub; private final CountDownLatch latch = new CountDownLatch(3); public GrpcClient(String host, int port) { this.channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build(); this.futureStub = UserGrpcServiceGrpc.newFutureStub(channel); } // 异步注册(触发后续登录) public void asyncRegister(String username, String password, String email, String address) { UserRegisterRequest request = UserRegisterRequest.newBuilder() .setUsername(username) .setPassword(password) .setEmail(email) .setAddress(address) .build(); ListenableFuture<UserRegisterResponse> responseFuture = futureStub.register(request); responseFuture.addListener(() -> { try { UserRegisterResponse response = responseFuture.get(); System.out.println("【注册结果】成功:" + response.getSuccess() + ",消息:" + response.getMessage()); if (response.getSuccess()) { // 注册成功后触发登录(使用相同邮箱和密码) asyncLogin(email, password); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【注册中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【注册失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 注册操作完成 } }, MoreExecutors.directExecutor()); } // 异步登录(触发后续登出) private void asyncLogin(String email, String password) { UserLoginRequest request = UserLoginRequest.newBuilder() .setEmail(email) .setPassword(password) .build(); ListenableFuture<UserLoginResponse> responseFuture = futureStub.login(request); responseFuture.addListener(() -> { try { UserLoginResponse response = responseFuture.get(); System.out.println("【登录结果】token:" + response.getToken() + ",用户ID:" + response.getUserId()); if (!response.getToken().isEmpty()) { // 登录成功后触发登出 asyncLogout(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【登录中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【登录失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 登录操作完成 } }, MoreExecutors.directExecutor()); } // 异步登出 private void asyncLogout() { UserLogoutRequest request = UserLogoutRequest.newBuilder().build(); ListenableFuture<UserLogoutResponse> responseFuture = futureStub.logout(request); responseFuture.addListener(() -> { try { UserLogoutResponse response = responseFuture.get(); System.out.println("【登出结果】成功:" + response.getSuccess() + ",消息:" + response.getMessage()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【登出中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【登出失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 登出操作完成 } }, MoreExecutors.directExecutor()); } // 等待所有操作完成后关闭通道 public void shutdown() throws InterruptedException { latch.await(); // 阻塞直到所有操作完成 channel.shutdown(); } public static void main(String[] args) throws InterruptedException { GrpcClient client = new GrpcClient("localhost", 9090); // 启动注册流程(触发后续登录、登出) client.asyncRegister("testUser", "123456", "test@example.com", "北京"); // 等待所有操作完成后关闭客户端 client.shutdown(); } }
08-26
public class GrpcClient { private final ManagedChannel channel; private final UserGrpcServiceGrpc.UserGrpcServiceFutureStub futureStub; private final CountDownLatch latch = new CountDownLatch(3); public GrpcClient(String host, int port) { this.channel = ManagedChannelBuilder.forAddress(host, port) .usePlaintext() .build(); this.futureStub = UserGrpcServiceGrpc.newFutureStub(channel); } // 异步注册(触发后续登录) public void asyncRegister(String username, String password, String email, String address) { UserRegisterRequest request = UserRegisterRequest.newBuilder() .setUsername(username) .setPassword(password) .setEmail(email) .setAddress(address) .build(); ListenableFuture<UserRegisterResponse> responseFuture = futureStub.register(request); responseFuture.addListener(() -> { try { UserRegisterResponse response = responseFuture.get(); System.out.println("【注册结果】成功:" + response.getSuccess() + ",消息:" + response.getMessage()); if (response.getSuccess()) { // 注册成功后触发登录(使用相同邮箱和密码) asyncLogin(email, password); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【注册中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【注册失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 注册操作完成 } }, MoreExecutors.directExecutor()); } // 异步登录(触发后续登出) private void asyncLogin(String email, String password) { UserLoginRequest request = UserLoginRequest.newBuilder() .setEmail(email) .setPassword(password) .build(); ListenableFuture<UserLoginResponse> responseFuture = futureStub.login(request); responseFuture.addListener(() -> { try { UserLoginResponse response = responseFuture.get(); System.out.println("【登录结果】token:" + response.getToken() + ",用户ID:" + response.getUserId()); if (!response.getToken().isEmpty()) { // 登录成功后触发登出 asyncLogout(); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【登录中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【登录失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 登录操作完成 } }, MoreExecutors.directExecutor()); } // 异步登出 private void asyncLogout() { UserLogoutRequest request = UserLogoutRequest.newBuilder().build(); ListenableFuture<UserLogoutResponse> responseFuture = futureStub.logout(request); responseFuture.addListener(() -> { try { UserLogoutResponse response = responseFuture.get(); System.out.println("【登出结果】成功:" + response.getSuccess() + ",消息:" + response.getMessage()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); System.err.println("【登出中断】原因:" + e.getMessage()); } catch (ExecutionException e) { Status status = Status.fromThrowable(e.getCause()); System.err.println("【登出失败】原因:" + status.getDescription()); } finally { latch.countDown(); // 登出操作完成 } }, MoreExecutors.directExecutor()); } // 等待所有操作完成后关闭通道 public void shutdown() throws InterruptedException { latch.await(); // 阻塞直到所有操作完成 channel.shutdown(); } public static void main(String[] args) throws InterruptedException { GrpcClient client = new GrpcClient("localhost", 9090); // 启动注册流程(触发后续登录、登出) client.asyncRegister("testUser", "123456", "test@example.com", "北京"); // 等待所有操作完成后关闭客户端 client.shutdown(); } }@Service public class UserGrpcServiceImpl extends UserGrpcServiceGrpc.UserGrpcServiceImplBase { private final UserService userService; private final Counter emailDuplicateCounter; @Autowired public UserGrpcServiceImpl(UserService userService, MeterRegistry meterRegistry) { this.userService = userService; this.emailDuplicateCounter = meterRegistry.counter("register.email.duplicate"); } // 注册接口实现(异步非阻塞由gRPC自动处理) @Override public void register(UserRegisterRequest request, StreamObserver<UserRegisterResponse> responseObserver) { UserInfo user = UserInfo.builder() .username(request.getUsername()) .password(request.getPassword()) .email(request.getEmail()) .address(request.getAddress()) .build(); try { userService.register(user); // 复用原有注册逻辑 UserRegisterResponse response = UserRegisterResponse.newBuilder() .setSuccess(true) .setMessage("注册成功") .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } catch (RuntimeException e) { // 转换异常为gRPC状态码 Status status = e.getMessage().contains("邮箱已注册") ? Status.ALREADY_EXISTS.withDescription(e.getMessage()) : Status.RESOURCE_EXHAUSTED.withDescription(e.getMessage()); responseObserver.onError(status.asRuntimeException()); } } // 登录接口实现 @Override public void login(UserLoginRequest request, StreamObserver<UserLoginResponse> responseObserver) { try { UserLoginSuccessDTO dto = userService.login(request.getEmail(), request.getPassword()); UserLoginResponse response = UserLoginResponse.newBuilder() .setToken(dto.getToken()) .setUserId(dto.getUserId()) .setUsername(dto.getUsername()) .setEmail(dto.getEmail()) .setAddress(dto.getAddress()) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } catch (UnauthorizedException e) { responseObserver.onError(Status.UNAUTHENTICATED.withDescription(e.getMessage()).asRuntimeException()); //Unauthenticated } } // 退出接口实现(简化处理) @Override public void logout(UserLogoutRequest request, StreamObserver responseObserver) { UserLogoutResponse response = UserLogoutResponse.newBuilder() .setSuccess(true) .setMessage(“退出成功”) .build(); responseObserver.onNext(response); responseObserver.onCompleted(); } }syntax = “proto3”; option java_multiple_files = true; option java_package = “com.tplink.nbu.demo.basicspringboot.grpc”; option java_outer_classname = “UserGrpcProto”; // 定义用户服务 service UserGrpcService { // 注册接口(异步非阻塞) rpc Register (UserRegisterRequest) returns (UserRegisterResponse); // 登录接口(异步非阻塞) rpc Login (UserLoginRequest) returns (UserLoginResponse); // 退出接口(异步非阻塞) rpc Logout (UserLogoutRequest) returns (UserLogoutResponse); } // 注册请求消息 message UserRegisterRequest { string username = 1; string password = 2; string email = 3; string address = 4; } // 注册响应消息 message UserRegisterResponse { bool success = 1; string message = 2; } // 登录请求消息 message UserLoginRequest { string email = 1; string password = 2; } // 登录响应消息(含token和用户信息) message UserLoginResponse { string token = 1; int64 user_id = 2; string username = 3; string email = 4; string address = 5; } // 退出请求消息(无参数) message UserLogoutRequest {} // 退出响应消息 message UserLogoutResponse { bool success = 1; string message = 2; }客户端运行结果如下"C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\bin\java.exe" “-javaagent:D:\IntelliJ IDEA Community Edition 2025.1.4.1\lib\idea_rt.jar=57846” -Dfile.encoding=UTF-8 -classpath “C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\charsets.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\dnsns.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\jaccess.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\localedata.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\nashorn.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunec.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\ext\zipfs.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jce.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jfr.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jfxswt.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\jsse.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\management-agent.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\resources.jar;C:\Program Files\Java\openlogic-openjdk-8u462-b08-windows-x64\jre\lib\rt.jar;D:\r\basic-spring-boot\target\classes;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-validation\2.1.14.RELEASE\spring-boot-starter-validation-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter\2.1.14.RELEASE\spring-boot-starter-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot\2.1.14.RELEASE\spring-boot-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.1.14.RELEASE\spring-boot-autoconfigure-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.1.14.RELEASE\spring-boot-starter-logging-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\admin.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\admin.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;C:\Users\admin.m2\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;C:\Users\admin.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\admin.m2\repository\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;C:\Users\admin.m2\repository\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;C:\Users\admin.m2\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.34\tomcat-embed-el-9.0.34.jar;C:\Users\admin.m2\repository\org\hibernate\validator\hibernate-validator\6.0.19.Final\hibernate-validator-6.0.19.Final.jar;C:\Users\admin.m2\repository\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;C:\Users\admin.m2\repository\org\jboss\logging\jboss-logging\3.3.3.Final\jboss-logging-3.3.3.Final.jar;C:\Users\admin.m2\repository\com\fasterxml\classmate\1.4.0\classmate-1.4.0.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-data-jpa\2.1.14.RELEASE\spring-boot-starter-data-jpa-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-jdbc\2.1.14.RELEASE\spring-boot-starter-jdbc-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\com\zaxxer\HikariCP\3.2.0\HikariCP-3.2.0.jar;C:\Users\admin.m2\repository\org\springframework\spring-jdbc\5.1.15.RELEASE\spring-jdbc-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\javax\transaction\javax.transaction-api\1.3\javax.transaction-api-1.3.jar;C:\Users\admin.m2\repository\javax\xml\bind\jaxb-api\2.3.1\jaxb-api-2.3.1.jar;C:\Users\admin.m2\repository\javax\activation\javax.activation-api\1.2.0\javax.activation-api-1.2.0.jar;C:\Users\admin.m2\repository\org\hibernate\hibernate-core\5.3.17.Final\hibernate-core-5.3.17.Final.jar;C:\Users\admin.m2\repository\javax\persistence\javax.persistence-api\2.2\javax.persistence-api-2.2.jar;C:\Users\admin.m2\repository\org\javassist\javassist\3.23.2-GA\javassist-3.23.2-GA.jar;C:\Users\admin.m2\repository\net\bytebuddy\byte-buddy\1.9.16\byte-buddy-1.9.16.jar;C:\Users\admin.m2\repository\antlr\antlr\2.7.7\antlr-2.7.7.jar;C:\Users\admin.m2\repository\org\jboss\jandex\2.0.5.Final\jandex-2.0.5.Final.jar;C:\Users\admin.m2\repository\org\dom4j\dom4j\2.1.3\dom4j-2.1.3.jar;C:\Users\admin.m2\repository\org\hibernate\common\hibernate-commons-annotations\5.0.4.Final\hibernate-commons-annotations-5.0.4.Final.jar;C:\Users\admin.m2\repository\org\glassfish\jaxb\jaxb-runtime\2.3.1\jaxb-runtime-2.3.1.jar;C:\Users\admin.m2\repository\org\glassfish\jaxb\txw2\2.3.1\txw2-2.3.1.jar;C:\Users\admin.m2\repository\com\sun\istack\istack-commons-runtime\3.0.7\istack-commons-runtime-3.0.7.jar;C:\Users\admin.m2\repository\org\jvnet\staxex\stax-ex\1.8\stax-ex-1.8.jar;C:\Users\admin.m2\repository\com\sun\xml\fastinfoset\FastInfoset\1.2.15\FastInfoset-1.2.15.jar;C:\Users\admin.m2\repository\org\springframework\data\spring-data-jpa\2.1.17.RELEASE\spring-data-jpa-2.1.17.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\data\spring-data-commons\2.1.17.RELEASE\spring-data-commons-2.1.17.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-orm\5.1.15.RELEASE\spring-orm-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-context\5.1.15.RELEASE\spring-context-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-tx\5.1.15.RELEASE\spring-tx-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-beans\5.1.15.RELEASE\spring-beans-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\admin.m2\repository\org\springframework\spring-aspects\5.1.15.RELEASE\spring-aspects-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\com\mysql\mysql-connector-j\8.2.0\mysql-connector-j-8.2.0.jar;C:\Users\admin.m2\repository\com\google\protobuf\protobuf-java\3.21.9\protobuf-java-3.21.9.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-web\2.1.14.RELEASE\spring-boot-starter-web-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-json\2.1.14.RELEASE\spring-boot-starter-json-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.9.10.4\jackson-databind-2.9.10.4.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.9.10\jackson-annotations-2.9.10.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\core\jackson-core\2.9.10\jackson-core-2.9.10.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.10\jackson-datatype-jdk8-2.9.10.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.10\jackson-datatype-jsr310-2.9.10.jar;C:\Users\admin.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.10\jackson-module-parameter-names-2.9.10.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\2.1.14.RELEASE\spring-boot-starter-tomcat-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.34\tomcat-embed-core-9.0.34.jar;C:\Users\admin.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.34\tomcat-embed-websocket-9.0.34.jar;C:\Users\admin.m2\repository\org\springframework\spring-web\5.1.15.RELEASE\spring-web-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-webmvc\5.1.15.RELEASE\spring-webmvc-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-expression\5.1.15.RELEASE\spring-expression-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-aop\2.1.14.RELEASE\spring-boot-starter-aop-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-aop\5.1.15.RELEASE\spring-aop-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\aspectj\aspectjweaver\1.9.5\aspectjweaver-1.9.5.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-starter-actuator\2.1.14.RELEASE\spring-boot-starter-actuator-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-actuator-autoconfigure\2.1.14.RELEASE\spring-boot-actuator-autoconfigure-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\boot\spring-boot-actuator\2.1.14.RELEASE\spring-boot-actuator-2.1.14.RELEASE.jar;C:\Users\admin.m2\repository\org\projectlombok\lombok\1.18.12\lombok-1.18.12.jar;C:\Users\admin.m2\repository\org\springframework\spring-core\5.1.15.RELEASE\spring-core-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\org\springframework\spring-jcl\5.1.15.RELEASE\spring-jcl-5.1.15.RELEASE.jar;C:\Users\admin.m2\repository\com\alibaba\fastjson2\fastjson2\2.0.23\fastjson2-2.0.23.jar;C:\Users\admin.m2\repository\io\grpc\grpc-netty-shaded\1.56.0\grpc-netty-shaded-1.56.0.jar;C:\Users\admin.m2\repository\com\google\guava\guava\31.1-android\guava-31.1-android.jar;C:\Users\admin.m2\repository\com\google\guava\failureaccess\1.0.1\failureaccess-1.0.1.jar;C:\Users\admin.m2\repository\com\google\guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;C:\Users\admin.m2\repository\org\checkerframework\checker-qual\3.12.0\checker-qual-3.12.0.jar;C:\Users\admin.m2\repository\com\google\j2objc\j2objc-annotations\1.3\j2objc-annotations-1.3.jar;C:\Users\admin.m2\repository\com\google\errorprone\error_prone_annotations\2.18.0\error_prone_annotations-2.18.0.jar;C:\Users\admin.m2\repository\io\perfmark\perfmark-api\0.26.0\perfmark-api-0.26.0.jar;C:\Users\admin.m2\repository\io\grpc\grpc-protobuf\1.56.0\grpc-protobuf-1.56.0.jar;C:\Users\admin.m2\repository\com\google\code\findbugs\jsr305\3.0.2\jsr305-3.0.2.jar;C:\Users\admin.m2\repository\com\google\api\grpc\proto-google-common-protos\2.17.0\proto-google-common-protos-2.17.0.jar;C:\Users\admin.m2\repository\io\grpc\grpc-protobuf-lite\1.56.0\grpc-protobuf-lite-1.56.0.jar;C:\Users\admin.m2\repository\io\grpc\grpc-stub\1.56.0\grpc-stub-1.56.0.jar;C:\Users\admin.m2\repository\net\devh\grpc-spring-boot-starter\2.14.0.RELEASE\grpc-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\net\devh\grpc-server-spring-boot-starter\2.14.0.RELEASE\grpc-server-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\net\devh\grpc-server-spring-boot-autoconfigure\2.14.0.RELEASE\grpc-server-spring-boot-autoconfigure-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\net\devh\grpc-common-spring-boot\2.14.0.RELEASE\grpc-common-spring-boot-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\io\grpc\grpc-core\1.51.0\grpc-core-1.51.0.jar;C:\Users\admin.m2\repository\com\google\code\gson\gson\2.8.6\gson-2.8.6.jar;C:\Users\admin.m2\repository\com\google\android\annotations\4.1.1.4\annotations-4.1.1.4.jar;C:\Users\admin.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.21\animal-sniffer-annotations-1.21.jar;C:\Users\admin.m2\repository\io\grpc\grpc-services\1.51.0\grpc-services-1.51.0.jar;C:\Users\admin.m2\repository\com\google\protobuf\protobuf-java-util\3.21.7\protobuf-java-util-3.21.7.jar;C:\Users\admin.m2\repository\io\grpc\grpc-api\1.51.0\grpc-api-1.51.0.jar;C:\Users\admin.m2\repository\io\grpc\grpc-context\1.51.0\grpc-context-1.51.0.jar;C:\Users\admin.m2\repository\net\devh\grpc-client-spring-boot-starter\2.14.0.RELEASE\grpc-client-spring-boot-starter-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\net\devh\grpc-client-spring-boot-autoconfigure\2.14.0.RELEASE\grpc-client-spring-boot-autoconfigure-2.14.0.RELEASE.jar;C:\Users\admin.m2\repository\io\micrometer\micrometer-core\1.11.4\micrometer-core-1.11.4.jar;C:\Users\admin.m2\repository\io\micrometer\micrometer-commons\1.11.4\micrometer-commons-1.11.4.jar;C:\Users\admin.m2\repository\io\micrometer\micrometer-observation\1.11.4\micrometer-observation-1.11.4.jar;C:\Users\admin.m2\repository\org\hdrhistogram\HdrHistogram\2.1.12\HdrHistogram-2.1.12.jar;C:\Users\admin.m2\repository\org\latencyutils\LatencyUtils\2.0.3\LatencyUtils-2.0.3.jar” com.tplink.nbu.demo.basicspringboot.GrpcClient 11:44:37.435 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework 11:44:37.438 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - -Dio.netty.noUnsafe: false 11:44:37.438 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - Java version: 8 11:44:37.439 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.theUnsafe: available 11:44:37.439 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.copyMemory: available 11:44:37.439 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - sun.misc.Unsafe.storeFence: available 11:44:37.439 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.Buffer.address: available 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - direct buffer constructor: available 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.Bits.unaligned: available, true 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable prior to Java9 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent0 - java.nio.DirectByteBuffer.(long, int): available 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - sun.misc.Unsafe: available 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.tmpdir: C:\Users\admin\AppData\Local\Temp (java.io.tmpdir) 11:44:37.440 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.bitMode: 64 (sun.arch.data.model) 11:44:37.441 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - Platform: Windows 11:44:37.441 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.maxDirectMemory: 7570718720 bytes 11:44:37.441 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.uninitializedArrayAllocationThreshold: -1 11:44:37.442 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.CleanerJava6 - java.nio.ByteBuffer.cleaner(): available 11:44:37.442 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - -Dio.netty.noPreferDirect: false 11:44:37.583 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.MultithreadEventLoopGroup - -Dio.netty.eventLoopThreads: 48 11:44:37.590 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024 11:44:37.591 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096 11:44:37.593 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop - -Dio.netty.noKeySetOptimization: false 11:44:37.593 [main] DEBUG io.grpc.netty.shaded.io.netty.channel.nio.NioEventLoop - -Dio.netty.selectorAutoRebuildThreshold: 512 11:44:37.597 [main] DEBUG io.grpc.netty.shaded.io.netty.util.internal.PlatformDependent - org.jctools-core.MpscChunkedArrayQueue: available 11:44:37.768 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector - -Dio.grpc.netty.shaded.io.netty.leakDetection.level: simple 11:44:37.768 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector - -Dio.grpc.netty.shaded.io.netty.leakDetection.targetRecords: 4 11:44:37.770 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.AbstractByteBuf - -Dio.grpc.netty.shaded.io.netty.buffer.checkAccessible: true 11:44:37.772 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.AbstractByteBuf - -Dio.grpc.netty.shaded.io.netty.buffer.checkBounds: true 11:44:37.772 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.grpc.netty.shaded.io.netty.util.ResourceLeakDetector@5023e79d 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numHeapArenas: 48 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.numDirectArenas: 48 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.pageSize: 8192 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxOrder: 9 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.chunkSize: 4194304 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.smallCacheSize: 256 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.normalCacheSize: 64 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedBufferCapacity: 32768 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimInterval: 8192 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.cacheTrimIntervalMillis: 0 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.useCacheForAllThreads: false 11:44:37.800 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.PooledByteBufAllocator - -Dio.netty.allocator.maxCachedByteBuffersPerChunk: 1023 11:44:37.818 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.channel.DefaultChannelId - -Dio.netty.processId: 27564 (auto-detected) 11:44:37.819 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - -Djava.net.preferIPv4Stack: false 11:44:37.819 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - -Djava.net.preferIPv6Addresses: false 11:44:37.858 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtilInitializations - Loopback interface: lo (Software Loopback Interface 1, 127.0.0.1) 11:44:37.859 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.NetUtil - Failed to get SOMAXCONN from sysctl and file \proc\sys\net\core\somaxconn. Default: 200 11:44:37.862 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.channel.DefaultChannelId - -Dio.netty.machineId: a0:ad:9f:ff:fe:1d:8c:93 (auto-detected) 11:44:37.872 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.allocator.type: pooled 11:44:37.872 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.threadLocalDirectBufferSize: 0 11:44:37.872 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.buffer.ByteBufUtil - -Dio.netty.maxThreadLocalCharBufferSize: 16384 11:44:37.878 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 4096 11:44:37.878 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8 11:44:37.878 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.chunkSize: 32 11:44:37.878 [grpc-default-executor-0] DEBUG io.grpc.netty.shaded.io.netty.util.Recycler - -Dio.netty.recycler.blocking: false 11:44:37.890 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND SETTINGS: ack=false settings={ENABLE_PUSH=0, MAX_CONCURRENT_STREAMS=0, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192} 11:44:37.894 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041 11:44:37.939 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] INBOUND SETTINGS: ack=false settings={MAX_CONCURRENT_STREAMS=2147483647, INITIAL_WINDOW_SIZE=1048576, MAX_HEADER_LIST_SIZE=8192} 11:44:37.940 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND SETTINGS: ack=true 11:44:37.948 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] INBOUND WINDOW_UPDATE: streamId=0 windowSizeIncrement=983041 11:44:37.949 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] INBOUND SETTINGS: ack=true 11:44:37.954 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND HEADERS: streamId=3 headers=GrpcHttp2OutboundHeaders[:authority: localhost:9090, :path: /UserGrpcService/Register, :method: POST, :scheme: http, content-type: application/grpc, te: trailers, user-agent: grpc-java-netty/1.51.0, grpc-accept-encoding: gzip] streamDependency=0 weight=16 exclusive=false padding=0 endStream=false 11:44:37.960 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND DATA: streamId=3 padding=0 endStream=true length=49 bytes=000000002c0a08746573745573657212063132333435361a1074657374406578616d706c652e636f6d2206e58c97e4baac 11:44:37.984 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] INBOUND PING: ack=false bytes=1234 11:44:37.984 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] OUTBOUND PING: ack=true bytes=1234 11:44:37.989 [grpc-nio-worker-ELG-1-2] DEBUG io.grpc.netty.shaded.io.grpc.netty.NettyClientHandler - [id: 0xe18f6953, L:/127.0.0.1:57899 - R:localhost/127.0.0.1:9090] INBOUND HEADERS: streamId=3 headers=GrpcHttp2ResponseHeaders[:status: 200, content-type: application/grpc, grpc-status: 12, grpc-message: Method not found: UserGrpcService/Register] padding=0 endStream=true 【注册失败】原因:Method not found: UserGrpcService/Register请分析并解决
08-26
flowchart TD A[用户在Activity点击按钮] --> B[主线程启动子线程(避免UI阻塞,防止ANR)] B --> C[主线程显示ProgressDialog:“软件正在生成中 请稍等...”] C --> D[子线程调用FileUtils.copyAssetsToFile: - 输入:当前Activity、Assets内模板文件“dc.zip” - 输出:复制到ROOT_DIR(外部存储根目录)/dc.zip - 校验:判断目标文件是否已存在,存在则覆盖] D -->|抛出IOException(如Assets文件不存在/存储权限被拒)| Z[子线程捕获异常→主线程显示错误对话框:“生成失败:{IO异常信息}”] D --> E[子线程显示ProgressDialog:“验证传入图片合法性...”] E --> F[子线程初始化支付码检测: 1. 前置校验:BaseLockActivity已通过CheckPayQrCode.init(Context)初始化上下文 2. 实例化CheckPayQrCode:传入背景图片路径(bjlj.getText().toString()) 3. 调用checkPay2code()开始检测] F --> F1[checkPay2code()内部流程1:删除历史debug目录 - 调用deleteDebugDir()→获取sAppContext的ExternalFilesDir/debug - 目录存在则递归删除(deleteDirRecursively()),不存在则打印日志] F1 --> F2[checkPay2code()内部流程2:按顺序检测支付码(任一命中即抛异常) - ① 检测微信支付码:调用hasWeChatPay()→WeChatPay.containsWeChatPay() - 读取图片→缩放至1024px→ZXing解析→判断是否含“wechatpay/wxp:///weixin://” - 检测耗时记录+日志打印,命中则抛UnsupportedPayQrCodeException(WECHAT_PAY类型) - ② 未命中则检测支付宝码:调用hasAliPay()→AlipayCode.containsAlipay() - 同样ZXing解析,命中则抛异常(Alipay类型) - ③ 未命中则检测QQ钱包码:调用hasQQWallet()→QQWalletCode.containsQQWallet() - 命中则抛异常(QQ_Wallet类型) - ④ 未命中则检测微信小程序/赞赏码:调用hasWeChatMiniAppCode() - 启动子线程(CountDownLatch同步)→MiniAppQrcode.processImage() - 先ZXing识别,失败则检测定位点(需≥3个)+核心区/头像/Logo,命中则抛异常(WECHAT_MINI_APP类型)] F2 -->|检测到任一支付码(抛UnsupportedPayQrCodeException)| Z F2 -->|未检测到支付码,打印总耗时日志| G[子线程显示ProgressDialog:“复制资源文件成功 正在解密资源包...”] G --> H[子线程调用FileUtils.decryptAndUnpack: - 输入:assets/dc.zip、解密密钥、目标目录WORK_DIR(ROOT_DIR/闪电博士生成器) - 操作:1. 对称解密dc.zip文件 2. 解压解密后的文件到WORK_DIR - 校验:解压后检查WORK_DIR是否存在res、resources.arsc等核心目录/文件] H -->|解密失败(密钥错误)/解压失败(压缩包损坏)| Z H --> I[子线程显示ProgressDialog:“解密资源包成功 替换软件图标及背景并删除原签名...”] I --> J[子线程调用FileUtils.copyImageFiles替换软件图标: - 输入:用户选择的图标路径(tblj.getText().toString())、目标路径WORK_DIR/res/drawable/ic_launcher.png - 校验:1. 源文件是否为图片格式(png/jpg) 2. 目标目录是否存在,不存在则创建 3. 读写权限判断] J -->|图片格式错误/路径不存在| Z J --> K[子线程调用FileUtils.copyImageFiles替换软件背景: - 输入:用户选择的背景路径(bjlj.getText().toString())、目标路径WORK_DIR/res/drawable/sd.png - 校验:同图标替换逻辑] K -->|图片格式错误/路径不存在| Z K --> L[子线程显示ProgressDialog:“写入密码及内容...”] L --> M[子线程调用writeEncryptedPasswords()写入加密数据到WORK_DIR/res/raw: 1. 处理密码(passwordEdits[0]:用户输入的密码框): - 调用sf.computeHash()计算密码哈希→转16进制字符串→写入mm.txt 2. 处理内容(passwordEdits[1]:用户输入的内容框): - 调用TextUtil.encode(),以“闪”为密钥加密内容字节→写入nr.txt 3. 处理追溯数据(pz文件): - 拼接内容:“内容:{nr}+密码:{mm}+设备ID:{Settings.Secure.ANDROID_ID}” - 以空字符为密钥加密→写入pz文件 - 校验:确保res/raw目录存在,文件写入成功后刷新目录] M -->|文件写入失败(如目录不存在)| Z M --> N[子线程判断音频编辑框(audioEdit)是否非空?] N -->|是| O[子线程调用FileUtils.copyImageFiles处理音频: - 输入:用户选择的音频路径(audioEdit.getText().toString())、目标路径WORK_DIR/res/raw/a.mp3 - 校验:1. 源文件是否为音频格式(mp3等) 2. 文件大小是否超限(避免打包失败) 3. 目标目录存在性] O -->|音频格式错误/路径不存在| Z O --> P[跳过音频处理] N -->|否| P P --> Q[子线程判断应用名编辑框(rjm)是否非空?] Q -->|是| R[子线程显示ProgressDialog:“正在更改应用名称...”] R --> S[子线程调用ArscFile.ArscAppName()修改应用名: - 输入:WORK_DIR/resources.arsc(资源表文件)、用户输入的应用名(rjm.getText().toString()) - 内部流程:1. 读取arsc文件→解析ResTable_header/ResStringPool 2. 定位“app_name”资源条目 3. 替换资源值→生成新的字符串池→保存修改后的arsc文件] S -->|arsc文件损坏/未找到app_name资源| Z S --> T[跳过应用名修改] Q -->|否| T T --> U[子线程删除原APK签名目录: - 调用FileUtils.deleteDirectory()→删除WORK_DIR/META-INF - 校验:目录存在则递归删除所有文件/子目录,不存在则打印“无需删除”日志] U -->|删除失败(如文件被占用)| Z U --> V[子线程显示ProgressDialog:“删除签名成功 正在打包...”] V --> W[子线程初始化ApkZipModeBuilder并调用build()打包: - 输入:源目录WORK_DIR、输出APK路径ROOT_DIR/闪电博士.apk - 打包逻辑: 1. 初始化ZipFile(读写+创建+截断模式) 2. 遍历res目录:按资源类型选择压缩模式(raw/dex/jar不压缩,drawable/layout/xml等Deflate压缩) 3. 处理根目录文件:AndroidManifest.xml压缩,resources.arsc/dex/so不压缩 4. 生成LocalFileHeader+CentralDirEntry,写入PK头(0x04034b50) 5. 刷新中央目录,校验EOCD签名(0x06054b50)] W -->|打包失败(如文件权限不足)| Z W --> X[子线程显示ProgressDialog:“签名中...”] X --> Y[子线程调用apksigner.Main.sign()进行APK签名: - 输入:当前Activity、待签名APK(闪电博士.apk)、输出APK(OUTPUT_APK=ROOT_DIR/锁机生成(闪电博士生成器).apk) - 签名配置:使用内置签名证书 - 校验:签名后检查输出APK是否存在,文件大小是否正常] Y -->|签名失败(如证书无效)| Z Y --> AA[子线程显示ProgressDialog:“删除残留...”] AA --> AB[子线程调用FileUtils.deleteDirectory()删除WORK_DIR: - 递归删除WORK_DIR下所有文件/子目录(包括res、resources.arsc等) - 校验:确保目录删除干净,避免残留占用存储] AB -->|删除残留失败| AC[打印“残留删除警告”日志,不中断流程] AB --> AC AC --> AD[子线程显示ProgressDialog:“成功生成...”] AD --> AE[子线程通知主线程显示成功对话框: - 标题:“成功” - 内容:“路径: {OUTPUT_APK}” - 按钮:“确定”→点击后关闭对话框] AE --> AF[主线程隐藏ProgressDialog] Z --> AF[主线程隐藏ProgressDialog→流程结束] AF --> AG[生成流程结束] 生成思维导图
最新发布
10-17
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值