gRpc_go_dart-1.编写第一个服务

news2025/1/16 12:35:25

通俗的讲下grpc

简化掉所有复杂的实现,它要求服务端和客户端之间按照protobuf的规范进行数据交换,所以服务端和客户端都不用关心彼此的代码实现,只关心按照protobuf的形式提供数据

为什么是go和dart

技术栈,已经是google的形状了

同时,go客户端和Flutter间本身通过http协议不好交接数据,用grpc更快更方便

Hello,World

这个章节将教会你实现用go编写服务端提供grpc接口,而在flutter中定义一个按钮和输入框输入姓名来请求go服务端并获取信息

核心的规范文件-protobuf-及基于go的grpc服务端实现

  1. protobuf是grpc中最重要的文件,它规定了服务端与客户端之间通信的规范,同时我们也要求客户端和服务端中的protobuf文件必须完全一致,所以让我们首先来编写这个protobuf文件,让我们思考一下这个pro文件要实现什么功能

  2. 它应当有一个服务获取请求的字符后返回Hello world + name 字符串

  3. 我们首先编写人类可读的proto文件,这个文件是人类可以阅读的:

  4. syntax = "proto3";
    //指定了 Go 语言代码生成后应该放置在名为 "github.com/rn-consider/grpcservice/helloworld" 的包中,
    //会影响生成的 .pb.go 文件的 package 声明
    option go_package = "github.com/rn-consider/grpcservice/helloworld";
    option java_multiple_files = true;
    option java_package = "io.helloworld.examples.helloworld";
    option java_outer_classname = "HelloWorldProto";
    
    package helloworld;
    
    // 此处定义服务,为协议缓冲区中的服务定义
    service Greeter {
      /*
      提供SayHello函数,接受HelloRequest类型的消息,
      返回HelloReply类型的消息在grpc中,函数必须始终具有输入消息并返回输出消息
      */
      // Sends a greeting
      rpc SayHello (HelloRequest) returns (HelloReply) {}
    
      // Sends another greeting
      rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
    
    }
    
    // 要求传入参数必须要name
    message HelloRequest {
      /*
      字段的设计十分重要,应谨慎分配字段编号,切勿更改,且在设计时考虑未来的修订
      消息中的字段定义必须指定三件事:类型,名称,编号
      字段的类型可以是当前支持的整数类型(int32,int64等),float,double,bool,字符串,字节(用于任何数据)
      要注意的是字段名称必须全部小写,并使用_分隔多个单词.
      如first_name,字段编号表示字段在消息中的位置,如name = 1表示name在返回信息中占第一位
      字段编号可以从1到2^29
      推荐在字段编号内留下间距,例如将第一个字段编号为1,然后将10用于下一个字段
      这意味着可在稍后添加任何其他字段而不需要对字段进行编号
      */
      string name = 1;
    }
    
    // 要求返回参数必须要是message
    message HelloReply {
      string message = 1;
    }
    
  5. 然后我们使用proto工具将这个人类proto转化为用于客户端和服务端定义的接口文件.我们永远不会手动编辑这些文件:

  6. protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       helloworld/helloworld.proto
    
  7. windows下执行:

  8. protoc --go_out=. --go_opt=paths=source_relative "--go-grpc_out=." "--go-grpc_opt=paths=source_relative" helloworld\helloworld.proto
    
  9. 获得如下的文件,现在项目结构应该如:

  10. Image1

  11. 接下来我们在main函数中导入,并实现在pb中定义的函数

  12. // Package main implements a server for Greeter service.
    package main
    
    import (
        "context"
        "log"
        "net"
    
        pb "github.com/rn-consider/grpcservice/helloworld"
        "google.golang.org/grpc"
    )
    
    // server 用来实现pb中的helloworld.GreeterServer.
    type server struct {
        pb.UnimplementedGreeterServer
    }
    
    // SayHello 实现helloworld.GreeterServer
    func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        log.Printf("Received: %v", in.GetName())
        return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
    }
    
    func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
    }
    
    func main() {
        // grpc服务默认应该运行在50051端口
        lis, err := net.Listen("tcp", ":50051")
        if err != nil {
            log.Fatalf("报错: %v", err)
        }
        s := grpc.NewServer()
    
        // 注册服务
        pb.RegisterGreeterServer(s, &server{})
        log.Printf("server listening at %v", lis.Addr())
        if err := s.Serve(lis); err != nil {
            log.Fatalf("failed to serve: %v", err)
        }
    }
    

当看到输出时,说明gRpc的服务端已经运行成功

基于go服务端中的protobuf文件实现dart客户端

注意:环境要求安装dart-sdk或者flutter

当我们想要使用dart快速创建一个项目,因为我们要创建供flutter使用的客户端,使用

dart项目命名规范要求全部小写,用_来分隔单词,建议直接全小写了,dart是很严格的语言

dart create -t package helloworld

删掉不必要的文件并增加存放protos和其编译后文件的项目结构应该如

  • 编辑我们把go服务端写好的proto文件复制到protos目录下,往

  • pubspec.yaml里添加依赖,:后不加版本号默认获取最新:

  • dependencies:
      # path: ^1.8.0
      async: ^2.2.0
      grpc: ^3.2.4
      protobuf: ^3.0.0
    
    dev_dependencies:
      lints: ^2.0.0
      test: ^1.21.0
    
  • 然后运行:

  • protoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto

  • 接下来我们在bin中增加client.dart文件,我们将在其中实现grpc客户端,删除不必要的文件后项目结构应该如:

  • 接下来我们在dart.client里添加代码

  • import 'package:grpc/grpc.dart';
    import 'package:helloworld/src/generated/helloworld.pbgrpc.dart';
    
    class GreeterService {
      late ClientChannel _channel;
      late GreeterClient _stub;
    
      GreeterService(String host, int port) {
        _channel = ClientChannel(
          host,
          port: port,
          options: ChannelOptions(
            credentials: ChannelCredentials.insecure(),
          ),
        );
        _stub = GreeterClient(_channel);
      }
    
      Future<String> callSayHello(String name) async {
        try {
          final response = await _stub.sayHello(
            HelloRequest()..name = name,
          );
          return response.message;
        } catch (e) {
          print('Caught error: $e');
          return 'Error occurred';
        } finally {
          await _channel.shutdown();
        }
      }
    }
    

运行了go service后我们可以新建测试目录测试dart clilent

并在test/client_test.dart中加入以下代码:

import 'package:test/test.dart';
import 'package:helloworld/bin/client.dart'; // 根据实际路径进行调整

void main() {
  group('GreeterService', () {
    late GreeterService greeterService;

    setUp(() {
      greeterService = GreeterService('localhost',50051);
    });

    test('callSayHello returns correct message', () async {
      final name = 'John';
      final result = await greeterService.callSayHello(name);
      expect(result, 'Hello $name');
    });

    test('callSayHello handles error', () async {
      final name = 'Error';
      final result = await greeterService.callSayHello(name);
      expect(result, 'Hello Error');
    });
  });
}

可看见测试通过:

将上一步的dart客户端整合为Packages

因为我们一开始就是基于Package创建的项目所以现在只需要在lib目录下创建与项目同名的dart文件,在这里我们导出bin/client中的GreeterService:

Image2

然后新建flutter项目并且按照顺序

更改pubspec.yaml中的依赖项我们让它能索引到本地包

在lib/main.dart中添加以下代码

flutter调用dart客户端

    • 首先新建一flutter项目,初始的flutter项目结构如:

    • 我们的源代码应该放在lib目录中:

    • 让我们在main.dart中调用它:

    • import 'package:flutter/material.dart';
      import 'package:helloworld/helloworld.dart'; // 根据实际路径进行调整
      
      void main() {
        runApp(MyApp());
      }
      
      class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
          final greeterService = GreeterService('localhost',50051);
      
          return MaterialApp(
            title: 'Flutter gRPC Example',
            home: MyHomePage(greeterService: greeterService),
          );
        }
      }
      
      class MyHomePage extends StatefulWidget {
        final GreeterService greeterService;
      
        MyHomePage({required this.greeterService});
      
        @override
        _MyHomePageState createState() => _MyHomePageState();
      }
      
      class _MyHomePageState extends State<MyHomePage> {
        final TextEditingController _nameController = TextEditingController();
        String _message = '';
      
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: Text('Flutter gRPC Example'),
            ),
            body: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.all(16.0),
                    child: TextField(
                      controller: _nameController,
                      decoration: InputDecoration(labelText: 'Enter your name'),
                    ),
                  ),
                  ElevatedButton(
                    onPressed: () async {
                      final name = _nameController.text;
                      final response = await widget.greeterService.callSayHello(name);
                      setState(() {
                        _message = response;
                      });
                    },
                    child: Text('Call gRPC Service'),
                  ),
                  SizedBox(height: 20.0),
                  Text(
                    'Response: $_message',
                    style: TextStyle(fontSize: 18.0),
                  ),
                ],
              ),
            ),
          );
        }
      
        @override
        void dispose() {
          _nameController.dispose();
          super.dispose();
        }
      }
      

      编译后你便能看到运行的结果,我们的windows桌面应用已经可以获取grpc响应,假如说我们想在移动端使用呢?这需要一台有公网ip的服务器,并在实例化GreeterService时传入服务器的主机名:

在安卓中使用

  1. 先编译go service为linux可执行文件,我使用的linux架构为amd64,

  2. 注意修改main.go中的监听地址,还有注意在服务器运营商的安全组开放50051端口,还有注意关闭服务器的防火墙

  3. 结果如

  4. 上传服务器并运行,用nohup使其后台运行

  5. 更改flutter的实例化

  6. Image3

成功

额外讲点:生成的pb文件的作用

  1. helloworld.pb.go

    • 这个文件包含了与消息定义相关的代码。每个在 helloworld.proto 中定义的消息类型(如 HelloRequestHelloResponse)都会对应生成 Go 结构体,并且这些结构体实现了 Protocol Buffers 的序列化和反序列化方法,以便在网络上传输或进行持久化时使用。
    • 此文件还包含了一些辅助方法,用于创建、解析和操作消息对象。这些方法让你能够更轻松地使用消息类型。
  2. helloworld_grpc.pb.go

    • 这个文件包含了与 gRPC 服务定义相关的代码。在 helloworld.proto 中定义的 gRPC 服务(如 Greeter)会被转化为 Go 接口,并且为每个 gRPC 方法生成相应的客户端和服务器端函数。
    • 客户端函数用于向远程 gRPC 服务发起请求,而服务器端函数用于处理客户端请求并返回响应。

额外讲点:dart create命令详解

Dart 官方提供了多种不同类型的模板,以满足不同类型的项目需求。一些常见的 Dart 模板包括:

  • console:用于创建命令行应用程序的模板
  • web:用于创建 Web 应用程序的模板。
  • package:用于创建 Dart 软件包库的模板,供其他 Dart 项目使用。
  • server:用于创建 Dart 服务器应用程序的模板,用于构建服务器端应用程序。

额外讲点:dart proto生成pb文件命令详解

protoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto

  • protoc 是 Protocol Buffers 编译器。
  • --dart_out=grpc:lib/src 指定生成的 Dart 代码应放置在 lib/src/generated 目录中,并包括 gRPC 支持。
  • -Iprotos 指定搜索 Proto 文件的路径(protos 目录)。
  • protos/helloworld.proto 是您要生成 Dart 代码的 Proto 文件的路径。
    创建 Dart 软件包库的模板,供其他 Dart 项目使用。
  • server:用于创建 Dart 服务器应用程序的模板,用于构建服务器端应用程序。

额外讲点:dart proto生成pb文件命令详解

protoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto

  • protoc 是 Protocol Buffers 编译器。
  • --dart_out=grpc:lib/src 指定生成的 Dart 代码应放置在 lib/src/generated 目录中,并包括 gRPC 支持。
  • -Iprotos 指定搜索 Proto 文件的路径(protos 目录)。
  • protos/helloworld.proto 是您要生成 Dart 代码的 Proto 文件的路径。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1016338.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05-Flask-Flask查询路由方式

Flask查询路由方式 前言命令行方式代码实现返回所有路由 前言 本篇来学习下Flask中查询路由的方式 命令行方式 # window 用set linux 用 export set FLASK_APPtest_6_flask运行发方式# 打印所有路由 flask routes代码实现返回所有路由 # -*- coding: utf-8 -*- # Time …

优质医疗机构网站模板源码(含手机端)基于pbootcms开源项目

模板名称&#xff1a; 基于pbootcms的医疗机构网站模板&#xff08;手机端优化&#xff09; 模板介绍&#xff1a; 这是一个使用PbootCMS内核开发的医疗机构网站模板&#xff0c;专为医疗机构和医疗保健类企业设计。该模板具有简洁、简单和易于管理的页面风格&#xff0c;并附…

征战开发板从无到有(三)

接上一篇&#xff0c;翘首已盼的PCB板子做好了&#xff0c;管脚约束信息都在PCB板上体现出来了&#xff0c;很满意&#xff0c;会不会成为爆款呢&#xff0c;嘿嘿&#xff0c;来&#xff0c;先看看PCB裸板美图 由于征战开发板电路功能兼容小梅哥ACX720&#xff0c;大家可以直…

数据结构---链表(java)

目录 1. 链表 2. 创建Node 3. 增加 4. 获取元素 5. 删除 6. 遍历链表 7. 查找元素是否存在 8. 链栈的实现 9. 链队的实现 1. 链表 数据存放在"Node"结点中 优点&#xff1a;不用考虑扩容和缩容的问题&#xff0c;实现了动态存储数据 缺点&#xff1a;没有…

SQLAlchemy Oracle Database 23c Free 集成之旅

SQLAlchemy & Oracle Database 23c Free 集成之旅 1. SQLAlchemy 是什么2. Oracle Database 23c Free 是什么3. 运行 Oracle Database 23c Free4. 学习 SQLAlchemy 统一教程4-1. 安装依赖库4-2. 建立连接 - 引擎4-3. 使用事务和 DBAPI4-3-1. 获取连接4-3-2. 提交更改4-3-3.…

git git fetch 和 git fetch origin master 的区别

git fetch 第1步 先读取 .git/config 配置 [remote origin]&#xff0c;若 fetch 并没有指定其中一个或多个远程仓库&#xff0c;就会处理所有的远程仓库 [remote “origin”]url gitgithub.com:kaku/testGit.gitfetch refs/heads/:refs/remotes/origin/第2步 git fetch 会…

Python 字符串的常用方法

视频版教程 Python3零基础7天入门实战视频教程 下标索引操作和前面的列表&#xff0c;元组用法一样。 虽然字符串不能被修改&#xff0c;但是返回一个操作过的新字符串&#xff0c;所以方法还不少。 我先学习下常用方法&#xff1b; 1&#xff0c;index(元素)方法&#xff0…

第三篇------Virtual I/O Device (VIRTIO) Version 1.1

上一篇文章链接https://blog.csdn.net/Phoenix_zxk/article/details/132921821 接下来续上 5.9.4 支持的加密服务 以下加密服务已定义&#xff1a; /* CIPHER 服务&#xff1a;用于加密解密操作 */ #define VIRTIO_CRYPTO_SERVICE_CIPHER 0 /* HASH 服务&#xff1a;用于哈…

【微信小程序】网络请求

环境&#xff1a;微信小程序开发工具 测试api&#xff08;随机获取猫咪靓照&#xff09;:https://api.thecatapi.com/v1/images/search?limit2 示例&#xff1a; 完整代码 request.wxml <button bind:tap"requestBtn" type"primary">网络请求&l…

基于SSM+Vue的校园活动管理平台设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

【python之经验模态分解EMD实现】PyEMD库的安装和导入EMD, Visualisation问题解决方法[完整可运行]

现有的导入问题 目前网上的办法&#xff0c;直接导入&#xff1a;from PyEMD import EMD, Visualisation 是有问题的 可能会出现 在 ‘init.py | init.py’ 中找不到引用 ‘Visualisation’ 的报错。 原因似乎是现在导入的命令改了&#xff0c;这是一个坑&#xff0c;解决的…

数组相关面试题

1、原地移除数组中所有的元素val&#xff0c;要求时间复杂度为O(N),空间复杂度为O(1)。 OJ链接&#xff1a;27. 移除元素 - 力扣&#xff08;LeetCode&#xff09; 分析&#xff1a; 法1&#xff1a;挪到数据&#xff0c;思路如顺序表的头删&#xff0c;将后面的数据向前挪动将…

What does rail-to-rail mean (Rail-to-Rail Op amp) ?

What does rail-to-rail mean (Rail-to-Rail Op amp) ?

网络安全进阶学习第十九课——CTF之密码学

文章目录 一、密码学简介二、密码设计的根本目标三、古典密码1、摩斯密码CTF-题目展示 2、换位密码1&#xff09;栅栏密码2&#xff09;凯撒密码3&#xff09;曲路密码4&#xff09;列移位密码 3、替换密码1&#xff09;移位密码2&#xff09;简单替换密码3&#xff09;埃特巴什…

10 Ubuntu下配置STMCubeMX与CLion IDE联合环境搭建(不包含下载CLion的教程)

序言 果然作为一名测控系的学生&#xff0c;纯搞视觉多少还是有点与专业脱节&#xff0c;决定入坑嵌入式。选择STM32进行入门&#xff0c;并且使用CubeMX加CLion作为我的第一个真正意义上的嵌入式开发环境&#xff08;大一的时候玩过一段时间&#xff0c;但是没什么技术&#…

java创建excel文件和解析excel文件

创建excel文件 package com.bjpowernode.crm.poi;import org.apache.poi.hssf.usermodel.*; import org.apache.poi.ss.usermodel.HorizontalAlignment;import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.…

用于3D模具和模型制作:数控机床进行加工软件DeskProto【常用CAM(计算机辅助制造)软件】

DeskProto是一款用于计算机数控&#xff08;CNC&#xff09;机床的三维模型加工的软件&#xff0c;它的技术原理和操作方法涵盖了以下方面&#xff1a; 技术原理&#xff1a; DeskProto的技术原理基于计算机辅助设计&#xff08;CAD&#xff09;和计算机数控&#xff08;CNC&…

Day43:VUEX

1. 基本介绍 这里介绍的VueX是匹配Vue3的V4版本&#xff0c;它绝大多数功能使用都和之前基本保持一致。 1.1 官方定义 先一起看一下官网对于VueX的定义&#xff1a; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&…

Mybatis入门与数据库连接池以及lombok插件

我们做为后端程序开发人员&#xff0c;通常会使用Java程序来完成对数据库的操作。Java程序操作数据库&#xff0c;现在主流的方式是&#xff1a;Mybatis。 什么是MyBatis? MyBatis是一款优秀的 持久层 框架&#xff0c;用于简化JDBC的开发。 MyBatis本是 Apache的一个开源项…

typescrip接口 interface详解,以及ts实现多态

ts 接口 当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的 示例如下 当一个对象类型被多次使用时,可以看到,很明显代码有大量的冗余 let personTom: { name: string, age?: number, sayHi(name: string): void } {name: Tom,sayHi(n…