diff --git a/examples/README.md b/examples/README.md
index f57ad16989..42964d519b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -117,6 +117,8 @@ before trying out the examples.
+- [Keep Alive](src/main/java/io/grpc/examples/keepalive)
+
### To build the examples
1. **[Install gRPC Java library SNAPSHOT locally, including code generation plugin](../COMPILING.md) (Only need this step for non-released versions, e.g. master HEAD).**
diff --git a/examples/build.gradle b/examples/build.gradle
index c45e8e9da5..2758892aad 100644
--- a/examples/build.gradle
+++ b/examples/build.gradle
@@ -167,6 +167,20 @@ task nameResolveClient(type: CreateStartScripts) {
classpath = startScripts.classpath
}
+task keepAliveServer(type: CreateStartScripts) {
+ mainClass = 'io.grpc.examples.keepalive.KeepAliveServer'
+ applicationName = 'keep-alive-server'
+ outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
+ classpath = startScripts.classpath
+}
+
+task keepAliveClient(type: CreateStartScripts) {
+ mainClass = 'io.grpc.examples.keepalive.KeepAliveClient'
+ applicationName = 'keep-alive-client'
+ outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
+ classpath = startScripts.classpath
+}
+
applicationDistribution.into('bin') {
from(routeGuideServer)
from(routeGuideClient)
@@ -183,5 +197,7 @@ applicationDistribution.into('bin') {
from(loadBalanceClient)
from(nameResolveServer)
from(nameResolveClient)
+ from(keepAliveServer)
+ from(keepAliveClient)
fileMode = 0755
}
diff --git a/examples/logging.properties b/examples/logging.properties
new file mode 100644
index 0000000000..b807613adc
--- /dev/null
+++ b/examples/logging.properties
@@ -0,0 +1,8 @@
+# Create a file called logging.properties with the following contents.
+handlers=java.util.logging.ConsoleHandler
+io.grpc.level=FINE
+java.util.logging.ConsoleHandler.level=ALL
+java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
+
+# Pass the location of the file to JVM via this command-line flag
+JAVA_OPTS=-Djava.util.logging.config.file=logging.properties
diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java
new file mode 100644
index 0000000000..a7c59c3952
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveClient.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2023 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.examples.keepalive;
+
+import io.grpc.Channel;
+import io.grpc.Grpc;
+import io.grpc.InsecureChannelCredentials;
+import io.grpc.ManagedChannel;
+import io.grpc.StatusRuntimeException;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * A simple client that requests a greeting from the {@link KeepAliveServer}.
+ */
+public class KeepAliveClient {
+ private static final Logger logger = Logger.getLogger(KeepAliveClient.class.getName());
+
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+ /** Construct client for accessing HelloWorld server using the existing channel. */
+ public KeepAliveClient(Channel channel) {
+ // 'channel' here is a Channel, not a ManagedChannel, so it is not this code's responsibility to
+ // shut it down.
+
+ // Passing Channels to code makes code easier to test and makes it easier to reuse Channels.
+ blockingStub = GreeterGrpc.newBlockingStub(channel);
+ }
+
+ /** Say hello to server. */
+ public void greet(String name) {
+ logger.info("Will try to greet " + name + " ...");
+ HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+ HelloReply response;
+ try {
+ response = blockingStub.sayHello(request);
+ } catch (StatusRuntimeException e) {
+ logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+ return;
+ }
+ logger.info("Greeting: " + response.getMessage());
+ }
+
+ /**
+ * Greet server.
+ */
+ public static void main(String[] args) throws Exception {
+ // Access a service running on the local machine on port 50051
+ String target = "localhost:50051";
+
+ // Create a channel with the following keep alive configurations (demo only, you should set
+ // more appropriate values based on your environment):
+ // keepAliveTime: Send pings every 10 seconds if there is no activity. Set to an appropriate
+ // value in reality, e.g. (5, TimeUnit.MINUTES).
+ // keepAliveTimeout: Wait 1 second for ping ack before considering the connection dead. Set to a
+ // larger value in reality, e.g. (10, TimeUnit.SECONDS). You should only set such a small value,
+ // e.g. (1, TimeUnit.SECONDS) in certain low latency environments.
+ // keepAliveWithoutCalls: Send pings even without active streams. Normally disable it.
+ // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see the keep alive ping
+ // frames.
+ // More details see: https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md
+ ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
+ .keepAliveTime(5, TimeUnit.MINUTES)
+ .keepAliveTime(10, TimeUnit.SECONDS) // Change to a larger value, e.g. 5min.
+ .keepAliveTimeout(1, TimeUnit.SECONDS) // Change to a larger value, e.g. 10s.
+ .keepAliveWithoutCalls(true)// You should normally avoid enabling this.
+ .build();
+
+ try {
+ KeepAliveClient client = new KeepAliveClient(channel);
+ client.greet("Keep-alive Demo");
+ Thread.sleep(30000);
+ } finally {
+ channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java
new file mode 100644
index 0000000000..884bbfea53
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/keepalive/KeepAliveServer.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2023 The gRPC Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.grpc.examples.keepalive;
+
+import io.grpc.Grpc;
+import io.grpc.InsecureServerCredentials;
+import io.grpc.Server;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+/**
+ * Server that manages startup/shutdown of a keep alive server.
+ */
+public class KeepAliveServer {
+ private static final Logger logger = Logger.getLogger(KeepAliveServer.class.getName());
+
+ private Server server;
+
+ private void start() throws IOException {
+ /* The port on which the server should run */
+ int port = 50051;
+
+ // Start a server with the following configurations (demo only, you should set more appropriate
+ // values based on your real environment):
+ // keepAliveTime: Ping the client if it is idle for 5 seconds to ensure the connection is
+ // still active. Set to an appropriate value in reality, e.g. in minutes.
+ // keepAliveTimeout: Wait 1 second for the ping ack before assuming the connection is dead.
+ // Set to an appropriate value in reality, e.g. (10, TimeUnit.SECONDS).
+ // permitKeepAliveTime: If a client pings more than once every 5 seconds, terminate the
+ // connection.
+ // permitKeepAliveWithoutCalls: Allow pings even when there are no active streams.
+ // maxConnectionIdle: If a client is idle for 15 seconds, send a GOAWAY.
+ // maxConnectionAge: If any connection is alive for more than 30 seconds, send a GOAWAY.
+ // maxConnectionAgeGrace: Allow 5 seconds for pending RPCs to complete before forcibly closing
+ // connections.
+ // Use JAVA_OPTS=-Djava.util.logging.config.file=logging.properties to see keep alive ping
+ // frames.
+ // More details see: https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md
+ server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
+ .addService(new GreeterImpl())
+ .keepAliveTime(5, TimeUnit.SECONDS)
+ .keepAliveTimeout(1, TimeUnit.SECONDS)
+ .permitKeepAliveTime(5, TimeUnit.SECONDS)
+ .permitKeepAliveWithoutCalls(true)
+ .maxConnectionIdle(15, TimeUnit.SECONDS)
+ .maxConnectionAge(30, TimeUnit.SECONDS)
+ .maxConnectionAgeGrace(5, TimeUnit.SECONDS)
+ .build()
+ .start();
+ logger.info("Server started, listening on " + port);
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+ System.err.println("*** shutting down gRPC server since JVM is shutting down");
+ try {
+ KeepAliveServer.this.stop();
+ } catch (InterruptedException e) {
+ e.printStackTrace(System.err);
+ }
+ System.err.println("*** server shut down");
+ }
+ });
+ }
+
+ private void stop() throws InterruptedException {
+ if (server != null) {
+ server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Await termination on the main thread since the grpc library uses daemon threads.
+ */
+ private void blockUntilShutdown() throws InterruptedException {
+ if (server != null) {
+ server.awaitTermination();
+ }
+ }
+
+ /**
+ * Main launches the server from the command line.
+ */
+ public static void main(String[] args) throws IOException, InterruptedException {
+ final KeepAliveServer server = new KeepAliveServer();
+ server.start();
+ server.blockUntilShutdown();
+ }
+
+ static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
+
+ @Override
+ public void sayHello(HelloRequest req, StreamObserver responseObserver) {
+ HelloReply reply = HelloReply.newBuilder().setMessage("Hello " + req.getName()).build();
+ responseObserver.onNext(reply);
+ responseObserver.onCompleted();
+ }
+ }
+}