使用Java实现简单gRPC开发

使用Java实现简单gRPC开发

1. gRPC简介

gRPC(全称为gRPC Remote Procedure Call)是一种高性能、开源和通用的远程过程调用(RPC)框架,由Google开发。gRPC基于HTTP/2协议设计,使用ProtoBuf(全称Protocol Buffers)作为接口描述语言。gRPC原生方面支持了C++, Java, Python, Go, Ruby等语言,不同语言的应用程序能够方便地进行通信。

ProtoBuf是一种由Google开发的轻量级、高效的二进制数据序列化格式,用于结构化数据的序列化和反序列化。 ProtoBuf采用二进制格式,相比于文本格式,序列化后的数据更紧凑,解析速度更快,占用的空间更小。这使得它特别适用于对性能要求较高的场景。使用ProtoBuf语言定义文件来描述数据结构和消息格式。这些文件可以用于生成不同编程语言的代码,使得在不同平台和语言之间进行数据交换更为方便。

在gRPC中,需要使用ProtoBuf定义好消息与服务。这是一种轻量级、高效的二进制序列化格式。ProtoBuf允许你定义数据结构和服务接口,而且提供了代码生成工具,可以生成Server端和Client端的代码。在后续开发中,Server端需要实现定义好的接口,而在Client端,只需要调用定义好的方法即可完成传参。

gRPC支持四种调用方式,包括简单调用、服务器流式调用、客户端流式调用和双向流式调用,满足不同应用场景的需求。

  • 简单调用,客户端发起一次请求,服务端响应一个数据。
    当client发起调用后,提交数据,并且等待 服务端响应。
    在开发过程中,主要采用简单调用的这种通信方式
rpc fun1(Request) returns (Response) {}
  • 服务器流式调用,一个请求对象,服务端可以回传多个结果对象。
rpc fun2(Request) returns (stream Response) {}
  • 客户端流式调用,客户端发送多个请求对象,服务端只返回一个结果。
rpc fun3(stream Request) returns (Response) {}
  • 双向流式调用,客户端可以发送多个请求消息,服务端响应多个响应消息。
    应用场景:聊天室
rpc fun4(stream Request) returns (stream Response) {}

2. 开发环境的搭建

整体项目结构如下
在这里插入图片描述

引入相关gRPC依赖

<dependencies>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-netty-shaded</artifactId>
            <version>1.60.0</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-protobuf</artifactId>
            <version>1.60.0</version>
        </dependency>
        <dependency>
            <groupId>io.grpc</groupId>
            <artifactId>grpc-stub</artifactId>
            <version>1.60.0</version>
        </dependency>
        <dependency> 
            <groupId>org.apache.tomcat</groupId>
            <artifactId>annotations-api</artifactId>
            <version>6.0.53</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

3. 实现

3.1 定义服务

在proto文件夹下创建Chat.proto文件,定义消息格式为sender、content,定义服务包括SendMessage、JoinChat。

syntax = "proto3";

option java_multiple_files = false;
option java_package = "com.jy";
option java_outer_classname = "ChatProto";



message ChatMessage {
  string sender = 1;
  string content = 2;
}

service ChatService {
  rpc SendMessage(ChatMessage) returns (ChatMessage);
  rpc JoinChat(ChatMessage) returns (ChatMessage);
}

3.2 生成Java代码

在pom.xml文件中加入以下配置,即可使用Maven中的compile与compile-custom生成代码。compile用于生成消息相关类,compile-custom生成接口。

 <build>
        <extensions>
            <extension>
                <groupId>kr.motd.maven</groupId>
                <artifactId>os-maven-plugin</artifactId>
                <version>1.7.1</version>
            </extension>
        </extensions>
        <plugins>
            <plugin>
                <groupId>org.xolstice.maven.plugins</groupId>
                <artifactId>protobuf-maven-plugin</artifactId>
                <version>0.6.1</version>
                <configuration>
                    <protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
                    <pluginId>grpc-java</pluginId>
                    <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.60.0:exe:${os.detected.classifier}</pluginArtifact>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>compile-custom</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

将生成的代码复制到/src/main/java/com.jy中
在这里插入图片描述

3.3 Server端

package com.jy;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

public class ChatServer {
    private final int port;
    private final Server server;

    // 构造函数,初始化端口和 gRPC 服务器
    public ChatServer(int port) {
        this.port = port;
        this.server = ServerBuilder.forPort(port)
                .addService(new ChatServiceImpl())
                .build();
    }

    // 启动服务器的方法
    public void start() throws Exception {
        server.start();
        System.out.println("服务器已启动,端口号:" + port);
        // 注册 JVM 关闭钩子,用于在 JVM 关闭时停止服务器
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("正在关闭服务器");
            ChatServer.this.stop();
        }));
    }

    // 停止服务器的方法
    private void stop() {
        if (server != null) {
            server.shutdown();
        }
    }

    // 内部类,实现 gRPC 服务接口
    private static class ChatServiceImpl extends ChatServiceGrpc.ChatServiceImplBase {
        // 处理客户端发送消息的方法
        @Override
        public void sendMessage(ChatProto.ChatMessage request, StreamObserver<ChatProto.ChatMessage> responseObserver) {
            System.out.println("收到来自 " + request.getSender() + " 的消息:" + request.getContent());

            // 构建服务器回复消息
            ChatProto.ChatMessage response = ChatProto.ChatMessage.newBuilder()
                    .setSender("服务器")
                    .setContent("已成功接收您的消息")
                    .build();

            // 发送回复消息给客户端
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }

        // 处理客户端加入聊天的方法
        @Override
        public void joinChat(ChatProto.ChatMessage request, StreamObserver<ChatProto.ChatMessage> responseObserver) {
            System.out.println(request.getSender() + " 加入了聊天");

            // 构建服务器欢迎消息
            ChatProto.ChatMessage response = ChatProto.ChatMessage.newBuilder()
                    .setSender("服务器")
                    .setContent("欢迎加入聊天," + request.getSender() + "!")
                    .build();

            // 发送欢迎消息给客户端
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        }
    }

    // 主函数,启动服务器并等待终止
    public static void main(String[] args) throws Exception {
        int port = 50051;
        ChatServer chatServer = new ChatServer(port);
        chatServer.start();
        chatServer.server.awaitTermination();
    }
}

3.4 Client端

package com.jy;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

import java.util.Scanner;

public class ChatClient {
    private final ManagedChannel channel;
    private final ChatServiceGrpc.ChatServiceStub chatStub;

    // 构造函数,初始化 gRPC 通道和服务存根
    public ChatClient(String host, int port) {
        this.channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();
        this.chatStub = ChatServiceGrpc.newStub(channel);
    }

    // 关闭客户端的方法
    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, java.util.concurrent.TimeUnit.SECONDS);
    }

    // 启动聊天的方法
    public void startChat(String username) {
        // 创建用于接收服务器消息的观察者
        StreamObserver<ChatProto.ChatMessage> responseObserver = new StreamObserver<>() {
            @Override
            public void onNext(ChatProto.ChatMessage message) {
                System.out.println(message.getSender() + " " + message.getContent());
            }

            @Override
            public void onError(Throwable throwable) {
                // 处理错误
            }

            @Override
            public void onCompleted() {
                // 处理完成事件
            }
        };

        // 创建加入聊天的请求消息
        ChatProto.ChatMessage joinRequest = ChatProto.ChatMessage.newBuilder()
                .setSender(username)
                .setContent("加入聊天")
                .build();

        // 发送加入聊天请求
        chatStub.joinChat(joinRequest, responseObserver);

        // 启动新线程用于发送用户输入的消息
        new Thread(() -> {
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String message = scanner.nextLine();
                // 创建聊天消息请求
                ChatProto.ChatMessage chatRequest = ChatProto.ChatMessage.newBuilder()
                        .setSender(username)
                        .setContent(message)
                        .build();
                // 发送聊天消息请求
                chatStub.sendMessage(chatRequest, responseObserver);
            }
        }).start();
    }

    // 主函数,用于启动客户端
    public static void main(String[] args) {
        String host = "localhost";
        int port = 50051;

        ChatClient chatClient = new ChatClient(host, port);

        try {
            Scanner scanner = new Scanner(System.in);
            System.out.print("请输入您的用户名:");
            String username = scanner.nextLine();

            // 启动聊天
            chatClient.startChat(username);

            // 主线程等待
            Thread.currentThread().join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭客户端
                chatClient.shutdown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4. 运行结果

在这里插入图片描述
在这里插入图片描述
Server端与Client端的代码均正常执行,Server端可以正常接受请求以及向客户端发送消息。

5. 课程总结

网络程序设计这门课程中,我学习了很多东西,不同于其他课程,我相信这门课可以对我求职起到很大的作用。
在这门课程中,首先学习了在浏览器端和服务器端使用JavaScript进行网络开发的方法,这包括使用Ajax进行异步通信、处理JSON数据、以及利用WebSocket实现实时通信等;学习了epoll并发编程,这是一种在Linux系统上高效处理大量并发连接的方法,通过epoll,我学到了如何在非阻塞模式下管理多个套接字,提高了程序的性能和可伸缩性;学习gRPC让我接触到了远程过程调用(RPC)框架,通过定义服务和消息类型,能够轻松地生成跨语言的客户端和服务器端代码,实现这些方法;学习Linux内核网络协议栈,我了解了网络协议的实现原理,包括TCP/IP协议栈、套接字接口等。通过深入研究内核层面的网络机制,我对操作系统如何管理和处理网络通信有了更全面的认识。
孟宁老师每次上课都会和同学们积极互动,调动大家的积极性。这是一门十分有趣的课程!

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
gRPC是由Google开发的一种高性能、开源和通用的RPC框架,它支持多种语言(如Java、Python、Go等)和平台。gRPC使用Protocol Buffers作为接口描述语言(IDL),可以轻松定义服务方法和消息类型,并且生成相应的服务端和客户端代码。 gRPC基于HTTP/2协议进行通信,提供了诸如双向流、流控制、头压缩、多路复用等功能,可以在客户端和服务端之间快速地进行高效通信。以下是gRPC的原理和使用方法: 1. 原理 gRPC的核心原理是通过IDL文件定义服务接口和消息类型,根据IDL文件生成相应的代码,然后在客户端和服务端之间通过HTTP/2协议进行通信。客户端和服务端可以使用不同的编程语言实现,但是必须使用相同的IDL文件定义服务接口和消息类型。 gRPC支持四种类型的RPC方法:简单RPC、服务端流式RPC、客户端流式RPC和双向流式RPC。其中,简单RPC是最常用的一种,它类似于传统的函数调用,客户端发送请求并等待服务端返回结果。服务端流式RPC和客户端流式RPC则是一种流式数据传输方式,双向流式RPC则是同时支持流式数据传输和异步处理的方式。 2. 使用方法 gRPC使用方法可以分为以下几个步骤: (1) 定义IDL文件 使用Protocol Buffers定义IDL文件,其中包含服务接口和消息类型的定义。 (2) 生成代码 使用gRPC提供的代码生成工具,根据IDL文件生成相应的客户端和服务端代码。 (3) 实现服务端 使用生成的服务端代码,实现服务端逻辑,并将服务端绑定到指定的端口上。 (4) 实现客户端 使用生成的客户端代码,调用服务端提供的方法,并处理返回结果。 (5) 编译和构建 编译客户端和服务端代码,并将生成的可执行文件部署到相应的服务器上。 (6) 运行测试 启动服务端和客户端,进行测试,并进行必要的优化和调试。 总之,gRPC是一个高效、灵活和易用的RPC框架,可以帮助开发者快速实现分布式系统中的服务通信。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值