protoBuf 实现客户端与服务端

Posted 香吧香

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了protoBuf 实现客户端与服务端相关的知识,希望对你有一定的参考价值。

转载请注明出处:

1.定义消息格式

   在 src/main/proto 目录下创建 person.proto 文件,并定义消息格式,例如:

syntax = "proto3";
package example;

message Person 
  string name = 1;
  int32 age = 2;
  repeated string interests = 3;

  这个文件定义了一个名为 Person 的消息类型,包括三个字段:name、age 和 interests

2.生成代码

  使用 protoc 工具来生成 Java 代码,需要安装相应的插件和工具,可以通过 Maven 或 Gradle 等构建工具自动下载和配置。这里演示手动下载和安装的方式。

  首先下载 protoc 工具及其插件,例如从官方网站下载对应版本的 protoc-3.x.x-linux-x86_64.zip,以及 protoc-gen-grpc-java 插件,例如从 Maven 中央仓库下载最新版的 protoc-gen-grpc-java-1.42.0-linux-x86_64.exe。

  然后解压 protoc 工具,将 protoc 命令所在的路径添加到环境变量 PATH 中,例如:

export PATH="/path/to/protoc/bin:$PATH"

  接下来安装 protobuf-java 和 grpc-java 两个依赖,例如通过 Maven 引入以下依赖:

<dependency>
  <groupId>com.google.protobuf</groupId>
  <artifactId>protobuf-java</artifactId>
  <version>3.17.3</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-netty-shaded</artifactId>
  <version>1.42.0</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-protobuf</artifactId>
  <version>1.42.0</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-stub</artifactId>
  <version>1.42.0</version>
</dependency>

<dependency>
  <groupId>io.grpc</groupId>
  <artifactId>grpc-testing</artifactId>
  <version>1.42.0</version>
  <scope>test</scope>
</dependency>

  接着使用 protoc 生成 Java 代码,例如:

protoc --java_out=src/main/java --grpc-java_out=src/main/java src/main/proto/person.proto

  这个命令会在 src/main/java/example 目录下生成 Person.java、PersonGrpc.java 和 PersonGrpc$PersonStub.java 等文件。

3.实现服务端

package example;

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

import java.io.IOException;

public class PersonServer 
    private final int port;
    private final Server server;

    public PersonServer(int port) throws IOException 
        this.port = port;
        this.server = ServerBuilder.forPort(port)
                .addService(new PersonServiceImpl())
                .build();
    

    public void start() throws IOException 
        server.start();
        System.out.println("Server started, listening on " + port);
        Runtime.getRuntime().addShutdownHook(new Thread(() -> 
            System.err.println("*** shutting down gRPC server since JVM is shutting down");
            PersonServer.this.stop();
            System.err.println("*** server shut down");
        ));
    

    public void stop() 
        if (server != null) 
            server.shutdown();
        
    

    private static class PersonServiceImpl extends PersonGrpc.PersonImplBase 
        @Override
        public void getMessage(PersonRequest request, StreamObserver<PersonResponse> responseObserver) 
            String name = request.getName();
            int age = request.getAge();
            String interests = String.join(", ", request.getInterestsList());
            
            // 将请求的内容响应回去
            PersonResponse response = PersonResponse.newBuilder()
                    .setMessage("Received person info: name=" + name + ", age=" + age + ", interests=[" + interests + "]")
                    .build();
            responseObserver.onNext(response);
            responseObserver.onCompleted();
        
    

    public static void main(String[] args) throws IOException, InterruptedException 
        PersonServer server = new PersonServer(8989);
        server.start();
        server.blockUntilShutdown();
    

  

4.实现客户端

package example;

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

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class PersonClient 
    private final ManagedChannel channel;
    private final PersonGrpc.PersonStub stub;

    public PersonClient(String host, int port) 
        this.channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext()
                .build();
        this.stub = PersonGrpc.newStub(channel);
    

    public void shutdown() throws InterruptedException 
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    

    public void sendMessage(String name, int age, String... interests) throws InterruptedException 
        final CountDownLatch latch = new CountDownLatch(1);

        StreamObserver<PersonResponse> responseObserver = new StreamObserver<PersonResponse>() 
            @Override
            public void onNext(PersonResponse response) 
                System.out.println("Received response: " + response.getMessage());
            

            @Override
            public void onError(Throwable t) 
                System.err.println("Received error: " + t.getMessage());
                latch.countDown();
            

            @Override
            public void onCompleted() 
                System.out.println("Request completed");
                latch.countDown();
            
        ;

        List<String> interestList = Arrays.asList(interests);
        PersonRequest request = PersonRequest.newBuilder()
                .setName(name)
                .setAge(age)
                .addAllInterests(interestList)
                .build();

        stub.getMessage(request, responseObserver);

        latch.await();
    

    public static void main(String[] args) throws InterruptedException 
        PersonClient client = new PersonClient("localhost", 8989);

        // 向服务器发送请求
        client.sendMessage("Alice", 20, "reading", "swimming");

        // 关闭连接
        client.shutdown();
    

  这个客户端会向服务器发送一个包含 name、age 和 interests 字段的 PersonRequest 消息,并等待接收服务器的响应信息。

 

 

Erik Bergstedt的工具库简化了在.NET中使用Protobuf的方式

Protobuf是由Google所创建的一种编码格式,它的正式名称叫做。这是一种紧凑的二进制格式,它牺牲了内容的可读性与可扩展性,换来了更小的空间占用与更好的性能。为了对某个Protobuf消息进行反序列化,客户端与服务端必须事先对消息中的特定字段达成一致。(与XML或JSON格式相反,客户端可以在运行时对后者的消息结构进行分析。)

.NET版本的Protobuf实现利用了属性的自定义attribute,为每个属性设置了一个“id”值。这些attribute将用于构建包含Protobuf消息的二进制数组。以下是Protobuf的一个应用示例:

[ProtoContract]
class Address
{
    [ProtoMember(1)]
    public string Line1 { get; set; }
    [ProtoMember(2)]
    public string Line2 { get; set; }
}

在C#中使用Protobuf的方式有些繁琐,因为开发者需要对流(stream)进行操作。而通过使用Erik Bergstedt所开发的,只需一行代码就可以完成序列化或反序列化工作。

var serialize = _simpleSerializer.ToByteArray(GetPerson());
Person deserialize = _simpleDeserializer.FromByteArray<Person>(serialize);

开发者可能会感到疑惑,为什么不将这些API直接加入Protobuf项目中呢?Erik对此写道:

因为这个库的API是写死的,它隐藏了某些功能,这对于某些场景来说可能并不合适。它为了换取简便性而牺牲了某些功能。

举例来说,在这个库里对于流的创建方式是写死的,并且对GZIP的支持也超出了原始的protobuf的功能范围。

我们需要小心地区分特性与特性膨胀的区别。如果将这个库的功能添加至原始的protobuf项目中,我会认为它不是一种特性,而是一种特性膨胀。

Protobuffer是一个基于MIT协议的开源软件。而Protobuf工具本身是由Google所开发的,它支持多种平台与编程语言,包括C++、Java、JavaScript、Python和Ruby等等。


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

以上是关于protoBuf 实现客户端与服务端的主要内容,如果未能解决你的问题,请参考以下文章

Erik Bergstedt的工具库简化了在.NET中使用Protobuf的方式

Unity3D客户端和Java服务端使用Protobuf

3.微服务--GRPC

Netty4.XUnity与Netty使用protoBuf

Netty+SpringBoot+protobuf3 搭建socket游戏网络层

现代的服务端技术栈:Golang/Protobuf/gRPC