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(); + } + } +}