grpc服务是基于 protobuf 的,安装教程可以参考本站另一篇文章:https://xhyz.fun/art/81.html
一、gRPC是什么?
gRPC 是 RPC 框架的一种,是一个高性能、开源和通用的 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())
}
启动服务端
启动客户端