grpc服务是基于 protobuf 的,安装教程可以参考本站另一篇文章:https://xhyz.fun/art/81.html

一、gRPC是什么?

gRPCRPC 框架的一种,是一个高性能、开源和通用的 RPC 框架,面向服务端和移动端,基于 HTTP/2 设计。
官网:https://grpc.io/
github:https://github.com/grpc/grpc

RPC框架是什么?

RPC 框架说白了就是让你 可以像调用本地方法一样调用远程服务提供的方法 ,而不需要关心底层的通信细节。简单地说就让远程服务调用更加简单、透明。
RPC包含了 客户端 (Client)和 服务端 (Server)

常见的RPC框架有
  • gRPC:谷歌出品
  • Thrift:Apache出品
  • Dubbo:阿里出品,也是一个微服务框架

    gRPC的特性

    看官方文档的介绍,有以下4点特性:

  • 使用Protocal Buffers这个强大的结构数据序列化工具
  • grpc可以 跨语言 使用
  • 安装简单, 扩展方便 (用该框架每秒可达到百万个RPC)
  • 基于HTTP2协议

    gRPC使用流程

    gprc的使用流程一般是这样的:

  • 定义标准的proto文件
  • 生成标准代码
  • 服务端使用生成的代码提供服务
  • 客户端使用生成的代码调用服务

    二、Protocol Buffers是什么?

    谷歌开源的一种结构数据序列化的工具,比方说JSON、XML也是结构数据序列化的工具,不同的是,

Protocol Buffers序列化后的数据是不可读的,因为是二进制流
使用Protocol Buffer需要事先定义数据的格式(.proto 协议文件),还原一个序列化之后的数据需要使用到这个数据格式
Protocol Buffer 比 XML、JSON快很多,因为是基于二进制流,比字符串更省带宽,传输速度快
Protocol Buffer语法:查看官方文档
下面演示根据需求开发项目,建议自己运行一下,加深印象

三、各语言安装grpc要用的包及生成grpc客户端与服务端

.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.common_service";// 生成类的包名,注意:会在指定路径下按照该包名的定义来生成文件夹
option java_outer_classname = "CommonServiceProto";// 生成类的类名,注意:下划线的命名会在编译的时候被自动改为驼峰命名
option objc_class_prefix = "COMSEV";

package common_service;// 包名,其他 proto 在引用此 proto 的时候,就可以使用 test.protobuf.PersonTest 来使用,
option go_package = "/common_service";// 生成go文件的包名


service CommonService {
  // 根据传入字符串获取分词标签或关键词
  rpc GetTags (GetTagsRequest) returns (GetTagsResponse) {}

  // 同义词替换-伪原创
  rpc SynonymReplace (SynonymReplaceRequest) returns (SynonymReplaceResponse) {}

  // 获取同义词
  rpc GetSynonym (GetSynonymRequest) returns (GetSynonymResponse) {}

  // 获取摘要
  rpc GetSentences (GetSentencesRequest) returns (GetSentencesResponse) {}

  // 过滤HTML
  rpc FilterHTMLTag (FilterHTMLTagRequest) returns (FilterHTMLTagResponse) {}

  // 谷歌翻译
  rpc GoogleTranslate(GoogleTranslateRequest) returns (GoogleTranslateResponse) {}

  // 下载图片到指定目录
  rpc DownloadWebImg(DownloadWebImgRequest) returns (DownloadWebImgResponse){}

  // 添加图片水印
  rpc AddImgWatemark(AddImgWatemarkRequest) returns (AddImgWatemarkResponse){}

  // 获取网页HTML
  rpc GetWebHtml(GetWebHtmlRequest) returns (GetWebHtmlResponse){}

  // 传入文字,获取拼音
  rpc GetWordPinyin(GetWordPinyinRequest) returns (GetWordPinyinResponse){}

  // 格式化 **前 时间为 Y-m-d H:i:s格式
  rpc FormatTimeago(FormatTimeagoRequest) returns (FormatTimeagoResponse){}

  // 从字符串里提取时间
  rpc ExtractDatetimeByStr(ExtractDatetimeByStrRequest) returns(ExtractDatetimeByStrResponse){}

  // 发送邮件
  rpc SendEmail(SendEmailRequest) returns (SendEmailResponse){}
}


message GetTagsRequest{
  string str = 1;
  int32 tag_type = 2;// 1=jieba分词 2=提取关键字
}
message GetTagsResponse {
  int32 Code = 1;
  string Msg = 2;
  repeated string Data = 3;
}

message SynonymReplaceRequest{
  string str = 1;
}
message SynonymReplaceResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message GetSynonymRequest{
  string str = 1;
}
message GetSynonymResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message GetSentencesRequest{
  string str = 1;
}
message GetSentencesResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message GoogleTranslateRequest{
  string str = 1;
  string now_lang = 2;
  string to_lang = 3;
}
message GoogleTranslateResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message FilterHTMLTagRequest{
  string str = 1;
}
message FilterHTMLTagResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message DownloadWebImgRequest{
  string url = 1;
}
message DownloadWebImgResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message AddImgWatemarkRequest{
  string imgpath = 1;
  string watemark_name = 2;  // 水印名称 ITM资源\npic.itmresources.com
  optional int64 watemark_lineheight= 3;  // 水印文字上下间距 -20
  optional string watemark_text_align = 4;  // 水印文字对齐 left center right
  optional string watemark_font_color = 5;  // 水印文字填充颜色 (255, 255, 255, 190)
  optional string watemark_font= 6;  // 水印文字位置 ./MFShangHei_Noncommercial-Regular.otf
  optional int64 watemark_font_size = 7;  // 水印字体大小 45
  optional int64 watemark_min_width = 8;  // 添加水印图片的最小宽度,小于此值不添加水印 300
  optional int64 watemark_font_weight = 9;  // 水印描边大小 4
  optional string watemark_font_weight_color = 10;  // 水印描边颜色 (0, 0, 0, 255)
}
message AddImgWatemarkResponse {
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message GetWebHtmlRequest{
  string url = 1;
  optional int32 get_type = 2;// 获取类型 0=普通方式获取 1=selenium获取
  optional string userAgent = 3;
  optional string referer = 4;
  optional int64 timeout = 5;
  optional int64 proxy_ip_http = 6;
  optional int64 proxy_ip_https = 7;
}
message GetWebHtmlResponse{
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message GetWordPinyinRequest{
  string str = 1;
  optional int32 mode = 2;
  optional string separator = 3;
}
message GetWordPinyinResponse{
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message FormatTimeagoRequest{
  string str = 1;
}
message FormatTimeagoResponse{
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message ExtractDatetimeByStrRequest{
  string str = 1;
}
message ExtractDatetimeByStrResponse{
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

message SendEmailRequest{
  map <int32,string> toMails = 1;
  string subject = 2;
  string body = 3;
}
message SendEmailResponse{
  int32 Code = 1;
  string Msg = 2;
  string Data = 3;
}

PHP

cmake版本问题

cmake版本需要3.5以上,不然编译报错,如果已经是3.5以上可以跳过
cmake官网:https://cmake.org/download/
报错截图:

升级cmake:

# 查看版本
cmake --version
# 查看yum包信息
yum info cmake
# 卸载yum安装的cmake
yum remove cmake -y
# 开始升级
wget https://github.com/Kitware/CMake/releases/download/v3.23.1/cmake-3.23.1.tar.gz
tar -zxvf cmake-3.23.1.tar.gz
cd cmake-3.23.1/
./bootstrap --prefix=/usr/local
make
make install

# 添加环境变量
vim ~/.bash_profile
# 追加以下内容
export PATH=$PATH:/usr/local/bin

# 让环境变量生效
source ~/.bash_profile
# 查看安装结果
cmake --version


gcc版本问题

php的grpc扩展安装,需要 gcc 4.9 + 以上的版本,否则安装php grpc拓展的时候会报make编译错误。

升级:

#1、安装centos-release-scl
sudo yum install centos-release-scl

#2、安装devtoolset
# 注意,如果想安装8.*版本的,就改成devtoolset-8-gcc*,以此类推,我的是centos7,所以下面都是7
sudo yum install devtoolset-7-gcc*

#3、激活对应的devtoolset,所以你可以一次安装多个版本的devtoolset,需要的时候用下面这条命令切换到对应的版本
scl enable devtoolset-7 bash

#大功告成,查看一下gcc版本
gcc -v

# 每个版本的目录下面都有个 enable 文件,如果需要启用某个版本,只需要执行
source /opt/rh/devtoolset-7/enable

升级成功:

安装生成代码的插件grpc_php_plugin

该插件用来生成PHP的gRPC代码

git clone https://github.com/grpc/grpc
cd grpc
# 这一步很耗时 可以在.gitmodules文件中看到依赖的仓库比较多
git submodule update --init
mkdir -p cmake/build
cd cmake/build
cmake ../..
# 编译出插件
make protoc grpc_php_plugin

# 把grpc_php_plugin添加软链接,如果你没装protoc,也可以建立protoc的软链接
ln -s /root/protoc/tmp/grpc/cmake/build/grpc_php_plugin /usr/bin/grpc_php_plugin

上面的命令将在文件中生成protoc和grpc_php_plugin可执行文件。

cmake/build/third_party/protobuf/protoccmake/build/grpc_php_plugin
安装protobuf拓展和grpc拓展

使用php的pecl安装,如果没有安装pecl,可以参考:https://xhyz.fun/art/89.html

pecl install protobuf
pecl install grpc

我的目录结构

定义composer.json
composer install
{
  "name": "common_service/common_service",
  "description": "php的grpc实例",
  "require": {
    "php": ">=7.4",
    "grpc/grpc": "^v1.3.0",
    "google/protobuf": "^v3.3.0"
  },
  "autoload": {
    "psr-4": {
      "GPBMetadata\\": "protos/common_service/GPBMetadata/",
      "Common_service\\": "protos/common_service/Common_service/"
    }
  }
}
生成php grpc客户端
init_proto.shsh init_proto.sh./protos/common_service
#!/bin/bash

# 生成grpc文件
protoDir="./protos" # proto目录
outDir="./protos/common_service" # 代码输出目录
pluginPath="/usr/bin/grpc_php_plugin" # grpc_php_plugin位置

protoc --proto_path=${protoDir} --php_out=${outDir} --grpc_out=${outDir} --plugin=protoc-gen-grpc=${pluginPath} ${protoDir}/*.proto
客户端
client.php
<?php
/**
 * 客户端使用示例
 * @site https://xhyz.fun
 * @author 小黑宇宙
 * @doc https://github.com/grpc/grpc/tree/master/src/php
 */

use Common_service\CommonServiceClient;
use Common_service\GetTagsRequest;
use Grpc\ChannelCredentials;

require dirname(__FILE__) . '/vendor/autoload.php';

//初始化
$client = new CommonServiceClient('localhost:50051', [
    'credentials' => ChannelCredentials::createInsecure(),
]);
// 请求
$request = new GetTagsRequest();
// 根据服务端定义的参数设置参数
$request->setStr("小黑宇宙测试php grpc调用python grpc服务端的中文分词服务。");
$request->setTagType(1);

// 调用并获取响应
list($response, $status) = $client->GetTags($request)->wait();

var_dump("code:".$response->getCode());
var_dump("msg:".$response->getMsg());
// 服务端返回的是多个,循环获取
foreach ($response->getData() as $item){
    var_dump("data:".$item);
}

首先我启动了python的grpc服务端

启动

php client.php

python

_pb2
安装grpc、protobuf包
pip install grpcio
pip install protobuf
# 安装grpc的protobuf编译工具
pip install grpcio-tools
生成代码
#!/bin/bash

# 生成grpc文件
protoDir="./protos" # proto目录
outDir="." # 代码输出目录
python3 -m grpc_tools.protoc -I=${protoDir} --python_out=${outDir} --grpc_python_out=${outDir} ${protoDir}/*.proto
实现代码
protorpc

golang

官方示例:
https://grpc.io/docs/languages/go/quickstart/
https://github.com/grpc/grpc-go

安装protoc-gen-go文件
GOPATHbin
go get google.golang.org/protobuf/cmd/protoc-gen-go
go get google.golang.org/grpc/cmd/protoc-gen-go-grpc
安装go grpc要用到的包

这是要代码里需要使用的,go get直接安装不了,手动克隆

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
cd $GOPATH/src/
go install google.golang.org/grpc
生成代码
# 生成go grpc文件
protoDir="./protos" # proto目录
outDir="./grpc" # 代码输出目录
protoc --go_out=${outDir}  --go-grpc_out=${outDir} ${protoDir}/*.proto
protoc-gen-go/data/go_prjgo env
vim ~/.bash_profile
# 添加路径
export PATH=$PATH:/data/go_prj/bin
# 生效
source ~/.bash_profile

我的目录结构:

服务端
package main

import (
    "context"
    "github.com/yanyiwu/gojieba"
    "log"
    "net"
    "spider_prj/libs"

    "google.golang.org/grpc"
    pb "spider_prj/grpc/common_service"
)

var SEG *gojieba.Jieba // 中文分词

// server结构体用于实现方法
type server struct {
    pb.CommonServiceServer
}

// 根据传入字符串获取分词标签或关键词
func (s *server) GetTags(ctx context.Context, in *pb.GetTagsRequest) (*pb.GetTagsResponse, error) {
    SEG = gojieba.NewJieba()
    // 拿到参数
    str := in.GetStr()
    tag_type := in.GetTagType()
    var tags []string
    if tag_type == 1 {
        var words []string
        words = SEG.CutForSearch(str, true)
        for i := range words {
            if len(words[i]) > 3 {
                tags = append(tags, words[i])
            }
        }
        // 切片去重
        tags = libs.RemoveDuplicateElement(tags)
    }
    // 响应结果
    return &pb.GetTagsResponse{
        Code: 0,
        Msg:  "成功",
        Data: tags,
    }, nil
}

func main() {
    lis, err := net.Listen("tcp", "127.0.0.1:50051")
    if err != nil {
        log.Fatalf("监听失败: %v", err)
    }
    s := grpc.NewServer()
    // 注册服务
    pb.RegisterCommonServiceServer(s, &server{})
    log.Printf("服务监听地址: %v", lis.Addr())

    if err := s.Serve(lis); err != nil {
        log.Fatalf("服务启动失败: %v", err)
    }
}
客户端
package main

import (
    "context"
    "flag"
    "log"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "spider_prj/grpc/common_service"
)

func main() {
    flag.Parse()
    // 建立到服务器的连接
    conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithTransportCredentials(insecure.NewCredentials()))
    if err != nil {
        log.Fatalf("没有连接成功: %v", err)
    }
    defer conn.Close()
    c := pb.NewCommonServiceClient(conn)

    // 连接服务器并打印响应
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()

    r, err := c.GetTags(ctx, &pb.GetTagsRequest{Str: "小黑宇宙测试go grpc调用grpc服务端的中文分词服务。", TagType: 1})
    if err != nil {
        log.Fatalf("调用失败: %v", err)
    }
    log.Printf("Code: %s", r.GetCode())
    log.Printf("Msg: %s", r.GetMsg())
    log.Printf("Data: %s", r.GetData())
}

启动服务端

启动客户端