下载直接安装
下载解压
tar -xvf go1.15.3.linux-amd64.tar.gz
配置环境变量
vim ~/.bashrc
export GOROOT=/root/go
export GOPATH=/root/projects/go
export PATH=$PATH:$GOROOT/bin:$GPPATH/bin
编辑保存并退出vim后,记得把这些环境载⼊:source ~/.bashrc
设置代理加速
go env -w GOPROXY=https://goproxy.io,direct
go env -w GO111MODULE=on
goland 的安装
官⽅地址下载安装即可
goland 的配置 goimports 和 go fmt
go fmt 和 goimports 保存后⾃动格式化
settings -> tools -> file watchers
设置 keymap 为 eclipse 模式
goland 中通过 file->settings->keymap 选择 eclipse(如果没有 eclipse 选型则通过 plugins 安eclipse)
命名是代码规范中很重要的⼀部分,统⼀的命名规则有利于提⾼的代码的可读性,好的命名仅仅通过命名就可以获取到⾜够多的信息。
a. 当命名(包括常量、变量、类型、函数名、结构字段等等)以⼀个⼤写字⺟开头,如:Group1,那么使⽤这种形式的标识符的对象就可以被外部包的代码所使⽤(客户端程序需要先导⼊这个包),这被称为导出(像⾯向对象语⾔中的 public);
b. 命名如果以⼩写字⺟开头,则对包外是不可⻅的,但是他们在整个包的内部是可⻅并且可⽤的(像⾯向对象语⾔中的 private )
保持 package 的名字和⽬录保持⼀致,尽量采取有意义的包名,简短,有意义,尽量和标准库不要冲突。包名应该为⼩写单词,不要使⽤下划线或者混合⼤⼩写。
package model
package main
尽量采取有意义的⽂件名,简短,有意义,应该为⼩写单词,使⽤下划线分隔各个单词。
type User struct{
Username string
Email string
}
u := User{
Username: "bobby",
Email: "[email protected]",
}
type Reader interface {
Read(p []byte) (n int, err error)
}
和结构体类似,变量名称⼀般遵循驼峰法,⾸字⺟根据访问控制原则⼤写或者⼩写,但遇到特有名词时,需要遵循以下规则:
错误示例:UrlArray,应该写成 urlArray 或者 URLArray
var isExist bool
var hasConflict bool
var canManage bool
var allowGitHook bool
常量均需使⽤全部⼤写字⺟组成,并使⽤下划线分词
如果是枚举类型的常量,需要先创建相应类型:
type Scheme string
const (
HTTP Scheme = "http"
HTTPS Scheme = "https"
)
Go 提供 C ⻛格的 /* */ 块注释和 C ++ ⻛格的 // ⾏注释。⾏注释是常态;块注释主要显示为包注释,但在表达式中很有⽤或禁⽤⼤量代码。
go 语⾔⾃带的 godoc ⼯具可以根据注释⽣成⽂档,⽣成可以⾃动⽣成对应的⽹站(golang.org 就是使⽤ godoc⼯具直接⽣成的),注释的质量决定了⽣成的⽂档的质量。每个包都应该有⼀个包注释,在 package ⼦句之前有⼀个块注释。对于多⽂件包,包注释只需要存在于⼀个⽂件中,任何⼀个都可以。包评论应该介绍包,并提供与整个包相关的信息。它将⾸先出现在 godoc ⻚⾯上,并应设置下⾯的详细⽂档。
每个包都应该有⼀个包注释,⼀个位于 package ⼦句之前的块注释或⾏注释。包如果有多个 go ⽂件,只需要出现在⼀个 go ⽂件中(⼀般是和包同名的⽂件)即可。 包注释应该包含下⾯基本信息 (请严格按照这个顺序,简介,创建⼈,创建时间):
每个⾃定义的结构体或者接⼝都应该有注释说明,该注释对结构进⾏简要介绍,放在结构体定义的前⼀⾏,格式为: 结构体名, 结构体说明。同时结构体内的每个成员变量都要有说明,该说明放在成员变量的后⾯(注意对⻬),实例如下:
type User struct{
Username string
Email string
}
每个函数,或者⽅法(结构体或者接⼝下的函数称为⽅法)都应该有注释说明,函数的注释应该包括三个⽅⾯(严格按照此顺序撰写):
func NewAttrModel(ctx *common.Context) *AttrModel {
}
对于⼀些关键位置的代码逻辑,或者局部较为复杂的逻辑,需要有相应的逻辑说明,⽅便其他开发者阅读该段代码,实例如下:
xxxxx
xxxxxxx
xxxxxxx
统⼀使⽤中⽂注释,对于中英⽂字符之间严格使⽤空格分隔, 这个不仅仅是中⽂和英⽂之间,英⽂和中⽂标点之间也都要使⽤空格分隔,例如:
上⾯ Redis 、 id 、 DB 和其他中⽂字符之间都是⽤了空格分隔。建议全部使⽤单⾏注释
和代码的规范⼀样,单⾏注释不要过⻓,禁⽌超过 120 字符。
import 在多⾏的情况下,goimports 会⾃动帮你格式化,但是我们这⾥还是规范⼀下 import 的⼀些规范,如果你在⼀个⽂件⾥⾯引⼊了⼀个 package,还是建议采⽤如下格式:
如果你的包引⼊了三种类型的包,标准库包,程序内部包,第三⽅包,建议采⽤如下⽅式进⾏组织你的包:
import (
"encoding/json"
"strings"
"myproject/models"
"myproject/controller"
"myproject/utils"
"github.com/astaxie/beego"
"github.com/go-sql-driver/mysql"
)
有顺序的引⼊包,不同的类型采⽤空格分离,第⼀种实标准库,第⼆是项⽬包,第三是第三⽅包。在项⽬中不要使⽤相对路径引⼊包:
import “../net”
import “github.com/repo/proj/src/net”
但是如果是引⼊本项⽬中的其他包,最好使⽤相对路径。
if err != nil {
} else {
}
if err != nil {
return
}
在远程调⽤时,我们需要执⾏的函数体是在远程的机器上的,也就是说,add 是在另⼀个进程中执⾏的。这就带来了⼏个新问题:
解决了上⾯三个机制,就能实现 RPC 了,具体过程如下:
要实现⼀个 RPC 框架,其实只需要按以上流程实现就基本完成了。
其中:
实际上真正的开发过程中,除了上⾯的基本功能以外还需要更多的细节:⽹络错误、流量控制、超时和重试等。
REST,是 Representational State Transfer 的简写,中⽂描述表述性状态传递(是指某个瞬间状态的资源数据的快照,包括资源数据的内容、表述格式 (XML、JSON) 等信息。)
REST 是⼀种软件架构⻛格。这种⻛格的典型应⽤,就是 HTTP。其因为简单、扩展性强的特点⽽⼴受开发者的⻘睐。
⽽ RPC 呢,是 Remote Procedure Call Protocol 的简写,中⽂描述是远程过程调⽤,它可以实现客户端像调⽤本地服务 (⽅法) ⼀样调⽤服务器的服务(⽅法)。
⽽ RPC 可以基于 TCP/UDP,也可以基于 HTTP 协议进⾏传输的,按理说它和 REST 不是⼀个层⾯意义上的东⻄,不应该放在⼀起讨论,但是谁让 REST 这么流⾏呢,它是⽬前最流⾏的⼀套互联⽹应⽤程序的 API 设计标准,某种意义下,我们说 REST 可以其实就是指代 HTTP 协议。
从使⽤上来看,HTTP 接⼝只关注服务提供⽅,对于客户端怎么调⽤并不关⼼。接⼝只要保证有客户端调⽤时,返回对应的数据就⾏了。⽽ RPC 则要求客户端接⼝保持和服务端的⼀致。
REST 是服务端把⽅法写好,客户端并不知道具体⽅法。客户端只想获取资源,所以发起 HTTP 请求,⽽服务端接收到请求后根据 URI 经过⼀系列的路由才定位到⽅法上⾯去 RPC 是服务端提供好⽅法给客户端调⽤,客户端需要知道服务端的具体类,具体⽅法,然后像调⽤本地⽅法⼀样直接调⽤它。
从设计上来看,RPC,所谓的远程过程调⽤ ,是⾯向⽅法的 ,REST:所谓的 Representational state transfer ,是⾯向资源的,除此之外,还有⼀种叫做 SOA,所谓的⾯向服务的架构,它是⾯向消息的。
接⼝调⽤通常包含两个部分,序列化和通信协议。
那到底为何要使⽤ RPC,单纯的依靠 RESTful API 不可以吗?为什么要搞这么多复杂的协议
RPC 和 REST 两者的定位不同,REST ⾯向资源,更注重接⼝的规范,因为要保证通⽤性更强,所以对外最好通过REST。⽽ RPC ⾯向⽅法,主要⽤于函数⽅法的调⽤,可以适合更复杂通信需求的场景。RESTful API 客户端与服务端之间采⽤的是同步机制,当发送 HTTP 请求时,客户端需要等待服务端的响应。当然对于这⼀点是可以通过⼀些技术来实现异步的机制的。采⽤ RESTful API,客户端与服务端之间虽然可以独⽴开发,但还是存在耦合。⽐如,客户端在发送请求的时,必须知道服务器的地址,且必须保证服务器正常⼯作。⽽ rpc + ralbbimq 中间件可以实现低耦合的分布式集群架构。
说了这么多,我们该如何选择这两者呢?我总结了如下两点,供你参考:
REST 接⼝更加规范,通⽤适配性要求⾼,建议对外的接⼝都统⼀成 REST。⽽组件内部的各个模块,可以选择RPC,⼀个是不⽤耗费太多精⼒去开发和维护多套的 HTTP 接⼝,⼀个 RPC 的调⽤性能更⾼(⻅下条)从性能⻆度看,由于 HTTP 本身提供了丰富的状态功能与扩展功能,但也正由于 HTTP 提供的功能过多,导致在⽹络传输时,需要携带的信息更多,从性能⻆度上讲,较为低效。⽽ RPC 服务⽹络传输上仅传输与业务内容相关的数据,传输数据更⼩,性能更⾼。
如果我们只是开发 web ⽹站或者⼀些服务的使⽤者, 那么我们⽤ restful 看起来已经⾜够了,但是 rpc 的这种模式在⼤量的服务中都有,⽐如 redis 协议, rabbitmq 的 AMQP 协议, 聊天软件的协议,也就是说我们想要开发⼀个 redis 的客户端,我们只需要⽤我们喜欢的语⾔实现 redis 定义的协议就⾏了,这对于开发服务来说⾮常有⽤,⼀般这种协议的价值在于我们⾃⼰开发的服务之间需要。通信的时候 - 那你会问了,⾃⼰开发的组件之间协作,
直接调⽤函数不就⾏了吗? - 对了,有些⼈已经反映过来了 – 分布式系统,分布式系统中⾮常常⽤, ⽐如openstack 中。 还有就是微服务!所以掌握 rpc 开发,对于进阶和分布式开发就变得⾮常重要。
http 协议 1.x ⼀般情况下⼀个来回就关闭连接,虽然提供了 keep-alive 可以保持⻓连接,但是依然不⽅便,所以就出现了 http2.0, http2.0 基本上可以当做 tcp 协议使⽤了。所以后⾯讲解到的 grpc 就会使⽤ http2.0 开发。
RPC 技术在架构设计上有四部分组成,分别是:客户端、客户端存根、服务端、服务端存根。
客户端 (Client): 服务调⽤发起⽅,也称为服务消费者。
客户端存根 (Client Stub): 该程序运⾏在客户端所在的计算机机器上,主要⽤来存储要调⽤的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过⽹络发送给服务端Stub 程序;其次,还要接收服务端 Stub 程序发送的调⽤结果数据包,并解析返回给客户端。
服务端 (Server): 远端的计算机机器上运⾏的程序,其中有客户端要调⽤的⽅法。
服务端存根 (Server Stub): 接收客户 Stub 程序通过⽹络发送的请求消息数据包,并调⽤服务端中真正的程序功能⽅法,完成功能调⽤;其次,将服务端执⾏调⽤的结果进⾏数据处理打包发送给客户端 Stub 程序。
了解完了 RPC 技术的组成结构我们来看⼀下具体是如何实现客户端到服务端的调⽤的。实际上,如果我们想要在⽹络中的任意两台计算机上实现远程调⽤过程,要解决很多问题,⽐如:
在上述图中,通过 1-10 的步骤图解的形式,说明了 RPC 每⼀步的调⽤过程。具体描述为:
1、客户端想要发起⼀个远程过程调⽤,⾸先通过调⽤本地客户端 Stub 程序的⽅式调⽤想要使⽤的功能⽅法名;
2、客户端 Stub 程序接收到了客户端的功能调⽤请求,将客户端请求调⽤的⽅法名,携带的参数等信息做序列化操作,并打包成数据包。
3、客户端 Stub 查找到远程服务器程序的 IP 地址,调⽤ Socket 通信协议,通过⽹络发送给服务端。
4、服务端 Stub 程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进⾏反序列化,得到请求的⽅法名和请求参数等信息。
5、服务端 Stub 程序准备相关数据,调⽤本地 Server 对应的功能⽅法进⾏,并传⼊相应的参数,进⾏业务处理。
6、服务端程序根据已有业务逻辑执⾏调⽤过程,待业务执⾏结束,将执⾏结果返回给服务端 Stub 程序。
7、服务端 Stub 程序 ** 将程序调⽤结果按照约定的协议进⾏序列化,** 并通过⽹络发送回客户端 Stub 程序。
8、客户端 Stub 程序接收到服务端 Stub 发送的返回数据,** 对数据进⾏反序列化操作,** 并将调⽤返回的数据传递给客户端请求发起者。
9、客户端请求发起者得到调⽤结果,整个 RPC 调⽤过程结束。
通过上⽂⼀系列的⽂字描述和讲解,我们已经了解了 RPC 的由来和 RPC 整个调⽤过程。我们可以看到 RPC 是⼀系列操作的集合,其中涉及到很多对数据的操作,以及⽹络通信。因此,我们对 RPC 中涉及到的技术做⼀个总结和分析:
1、动态代理技术: 上⽂中我们提到的 Client Stub 和 Sever Stub 程序,在具体的编码和开发实践过程中,都是使⽤动态代理技术⾃动⽣成的⼀段程序。
2、序列化和反序列化: 在 RPC 调⽤的过程中,我们可以看到数据需要在⼀台机器上传输到另外⼀台机器上。在互联⽹上,所有的数据都是以字节的形式进⾏传输的。⽽我们在编程的过程中,往往都是使⽤数据对象,因此想要在⽹络上将数据对象和相关变量进⾏传输,就需要对数据对象做序列化和反序列化的操作。
序列化: 把对象转换为字节序列的过程称为对象的序列化,也就是编码的过程。
反序列化: 把字节序列恢复为对象的过程称为对象的反序列化,也就是解码的过程。
我们常⻅的 Json,XML 等相关框架都可以对数据做序列化和反序列化编解码操作。后⾯我们要学习的 Protobuf 协议,这也是⼀种数据编解码的协议,在 RPC 框架中使⽤的更⼴泛。
Go 语⾔的 RPC 包的路径为 net/rpc,也就是放在了 net 包⽬录下⾯。因此我们可以猜测该 RPC 包是建⽴在 net包基础之上的。在第⼀章 “Hello, World” ⾰命⼀节最后,我们基于 http 实现了⼀个打印例⼦。下⾯我们尝试基于rpc 实现⼀个类似的例⼦。
package main
import (
"net"
"net/rpc"
)
type HelloService struct {}
func (s *HelloService) Hello(request string, reply *string) error {
*reply = "hello "+ request
return nil
}
func main(){
_ = rpc.RegisterName("HelloService", &HelloService{})
listener, err := net.Listen("tcp", ":1234")
if err != nil {
panic("监听端⼝失败")
}
conn, err := listener.Accept()
if err != nil {
panic("建⽴链接失败")
}
rpc.ServeConn(conn)
}
其中 Hello ⽅法 必须满⾜Go语⾔的RPC规则 :⽅法 只能有两个可序列化的参数,其中第⼆个参数是指针类型,并且返回⼀个error类型,同时必须是公开的⽅法 。
然后就可以将 HelloService 类型的对象注册为⼀个 RPC 服务:(TCP RPC 服务)。
其中 rpc.Register 函数调⽤会将对象类型中所有满⾜ RPC 规则的对象⽅法注册为 RPC 函数,所有注册的⽅法会放在 “HelloService” 服务空间之下。然后我们建⽴⼀个唯⼀的 TCP 链接,并且通过 rpc.ServeConn 函数在该 TCP链接上为对⽅提供 RPC 服务。
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
log.Fatal("dialing:", err)
}
var reply string
err = client.Call("HelloService.Hello", "hello", &reply)
if err != nil {
log.Fatal(err)
}
fmt.Println(reply)
}
⾸先是通过 rpc.Dial 拨号 RPC 服务,然后通过 client.Call 调⽤具体的 RPC ⽅法。在调⽤ client.Call时, 第⼀个参数是⽤点号链接的RPC服务名字和⽅法名字,第⼆和第三个参数分别我们定义RPC⽅法的两个参数 。
gRPC 是⼀个⾼性能、开源和通⽤的 RPC 框架,⾯向移动和 HTTP/2 设计。⽬前提供 C、Java 和 Go 语⾔版本,分别是:grpc, grpc-java, grpc-go. 其中 C 版本⽀持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# ⽀持.
grpc地址
java 中的 dubbo dubbo/rmi/hessian messagepack 如果你懂了协议完全有能⼒⾃⼰去实现⼀个协议
go get github.com/golang/protobuf/protoc-gen-go
如果是新版本 protoc 则需要安装下⾯两个
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
注意:安装过程中会提示说 go get 会慢慢被弃⽤,不是错误只是提示,go 的新版本依赖安装会慢慢弃⽤ go get⽅式安装,以后⼀律采⽤ go install ⽅式安装第三⽅依赖
syntax = "proto3";
option go_package = ".;proto"; #新版本的protoc和protobuf这⾥应该写成 option go_package =
"./;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
protoc -I . goods.proto --go_out=plugins=grpc:.
#如果⼤家使⽤最新的protoc此处可能会报错说不⽀持这种⽤法,所以可以使⽤下⾯的语句⽣成:
protoc --go_out=. --go-grpc_out=. goods.proto
开发
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc_demo/hello"
"net"
)
type Server struct {
}
func (s *Server) SayHello(ctx context.Context,request *hello.HelloRequest)
(*hello.HelloReply,error){
return &hello.HelloReply{Message:"Hello "+request.Name},nil
}
func main() {
g := grpc.NewServer()
s := Server{}
hello.RegisterGreeterServer(g,&s)
lis, err := net.Listen("tcp", fmt.Sprintf(":8080"))
if err != nil {
panic("failed to listen: "+err.Error())
}
g.Serve(lis)
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"grpc_demo/proto"
)
func main() {
conn,err := grpc.Dial("127.0.0.1:8080",grpc.WithInsecure())
if err!=nil{
panic(err)
}
defer conn.Close()
c := hello.NewGreeterClient(conn)
r,err := c.SayHello(context.Background(),&hello.HelloRequest{Name:"bobby"})
if err!=nil{
panic(err)
}
fmt.Println(r.Message)
}
先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了:
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
在上面的例子中,所有字段都是标量类型:两个整型(page_number和result_per_page),一个string类型(query)。当然,你也可以为字段指定其他的合成类型,包括枚举(enumerations)或其他消息类型。
正如你所见,在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改变。注:[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记:要为将来有可能添加的、频繁出现的标识号预留一些标识号。
最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999]( (从FieldDescriptor::kFirstReservedNumber 到FieldDescriptor::kLastReservedNumber) ) 的 标 识 号 ,Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。同样你也不能使用早期保留的标识号。
所指定的消息字段修饰符必须是如下之一:
在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候,这一点特别有用——例如,如果想定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
向.proto文件添加注释,可以使用C/C++/java风格的双斜杠(//) 语法格式,如:
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
MD 类型实际上是map,key是string,value是string类型的slice。
type MD map[string][]string
创建的时候可以像创建普通的map类型一样使用new关键字进行创建:
/第⼀种⽅式
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
//第⼆种⽅式 key不区分⼤⼩写,会被统⼀转成⼩写。
md := metadata.Pairs(
"key1", "val1",
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2",
)
md := metadata.Pairs("key", "val")
// 新建⼀个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeRespon
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
syntax = "proto3";
option go_package=".;proto";
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
package main
import (
"OldPackageTest/grpc_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func main(){
//stream
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
//md := metadata.Pairs("timestamp", time.Now().Format(timestampFormat))
md := metadata.New(map[string]string{
"name":"bobby",
"pasword":"imooc",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
r, err := c.SayHello(ctx, &proto.HelloRequest{Name:"bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc/metadata"
"net"
"google.golang.org/grpc"
"OldPackageTest/grpc_test/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*pro
error) {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
fmt.Println("get metadata error")
}
if nameSlice, ok := md["name"]; ok {
fmt.Println(nameSlice)
for i, e := range nameSlice {
fmt.Println(i, e)
}
}
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
func main() {
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
syntax = "proto3";
option go_package = ".;proto";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
//将 sessionid放⼊ 放⼊cookie中 http协议
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"time"
"start/grpc_interceptor/proto"
)
func interceptor(ctx context.Context, method string, req, reply interface{}, cc *
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
fmt.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, rep
return err
}
func main(){
//stream
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
// 指定客户端interceptor
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial("localhost:50051", opts...)
if err != nil {
panic(err)
}
defer conn.Close()
c := proto.NewGreeterClient(conn)
r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name:"bobby"})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}
package main
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"start/grpc_interceptor/proto"
)
type Server struct{}
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*pro
error){
return &proto.HelloReply{
Message: "hello "+request.Name,
}, nil
}
func main(){
var interceptor grpc.UnaryServerInterceptor
interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServ
// 继续处理请求
fmt.Println("接收到新请求")
res, err := handler(ctx, req)
fmt.Println("请求处理完成")
return res, err
}
var opts []grpc.ServerOption
opts = append(opts, grpc.UnaryInterceptor(interceptor))
g := grpc.NewServer(opts...)
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
if err != nil{
panic("failed to listen:"+err.Error())
}
err = g.Serve(lis)
if err != nil{
panic("failed to start grpc:"+err.Error())
}
}