目录
- 一、rpc基础
- 1 - rpc入门
- 2 - 基础的rpc通信
- 3 - 基于接口的RPC服务
- 二、rpc编码
- 1 - gob编码
- 2 - json on tcp
- 3 - json on http(待补充)
- 三、prtotobuf编码
- 1 - prtotobuf概述
- 2 - protobuf编译器
- 3 - 序列化和反序列化
- 4 - 基于protobuf的RPC(待补充00:41:00到01:10:00)
- 四、proto3语法
- 1 - 消息定义
- 2 - 基础类型
- 3 - 枚举类型
- 4 - 数组类型
- 5 - Map
- 6 - Oneof
- 7 - Any
- 8 - 类型嵌套
- 9 - 引用包
- 五、gRPC
- 1 - gRPC概念
- 2 - gRPC简单实现
- 3 - gRPC流
一、rpc基础
1 - rpc入门
- ipc:Inter-Process Communication,进程间通信,进程间通信是指两个进程的数据之间产生交互
- rpc:RPC是远程过程调用的简称,是分布式系统中不同节点间流行的通信方式
- RPC传输协议
- 消息序列化与反序列化
2 - 基础的rpc通信
- server
package mainimport ("fmt""log""net""net/rpc"
)// servuce handler
type HelloService struct {
}// Hello的逻辑 就是 将对方发送的消息前面添加一个Hello 然后返还给对方
// 由于我们是一个rpc服务, 因此参数上面还是有约束:
//
// 第一个参数是请求
// 第二个参数是响应
//
// 可以类比Http handler
func (p *HelloService) Hello(request string, response *string) error {*response = fmt.Sprintf("hello,%s", request)return nil
}func main() {// 把我们的对象注册成一个rpc的 receiver// 其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数// 所有注册的方法会放在“HelloService”服务空间之下rpc.RegisterName("HelloService", &HelloService{})// 然后我们建立一个唯一的TCP链接listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("ListenTCP error:", err)}// 通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。// 没Accept一个请求,就创建一个goroutie进行处理for {conn, err := listener.Accept()if err != nil {log.Fatal("Accept error:", err)}// 前面都是tcp的知识, 到这个RPC就接管了// 因此 你可以认为 rpc 帮我们封装消息到函数调用的这个逻辑// 提升了工作效率, 逻辑比较简洁,可以看看他代码go rpc.ServeConn(conn)}
}
- client
package mainimport ("fmt""log""net/rpc"
)func main() {// 首先是通过rpc.Dial拨号RPC服务, 建立连接client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {log.Fatal("dialing:", err)}// 然后通过client.Call调用具体的RPC方法// 在调用client.Call时:// 第一个参数是用点号链接的RPC服务名字和方法名字,// 第二个参数是 请求参数// 第三个是请求响应, 必须是一个指针, 有底层rpc服务帮你赋值var reply stringerr = client.Call("HelloService.Hello", "jack", &reply)if err != nil {log.Fatal(err)}fmt.Println(reply)
}
3 - 基于接口的RPC服务
- rpc方法存在的优化
- client call 方法, 里面3个参数2个interface{}, 你再使用的时候 可能真不知道要传入什么, 这就好像你写了一个HTTP的服务, 没有接口文档, 容易调用错误
- 或者说,如何对rpc接口进行一次封装,用来限定接口的参数类型
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Donereturn call.Error
}
- interface\service\interface.go
package serviceconst (//统一rpc的注册名SERVICE_NAME = "HelloService"
)type HelloService interface {Hello(request string, response *string) error
}
- interface\server\main.go:添加接口约束
package mainimport ("fmt""log""main/interface/service""net""net/rpc"
)// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)type HelloService struct {
}func (p *HelloService) Hello(request string, response *string) error {*response = fmt.Sprintf("hello,%s", request)return nil
}func main() {rpc.RegisterName(service.SERVICE_NAME, &HelloService{})listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("ListenTCP error:", err)}for {conn, err := listener.Accept()if err != nil {log.Fatal("Accept error:", err)}go rpc.ServeConn(conn)}
}
- interface\client\main.go:添加接口约束
package mainimport ("fmt""main/interface/service""net/rpc"
)// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {client, err := rpc.Dial(network, address)if err != nil {return nil, err}return &HelloServiceClient{client: client,}, nil
}type HelloServiceClient struct {client *rpc.Client
}func (c *HelloServiceClient) Hello(request string, response *string) error {return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}func main() {c, err := NewHelloServiceClient("tcp", ":1234")if err != nil {panic(err)}var response stringif err := c.Hello("tom", &response); err != nil {panic(err)}fmt.Println(response)
}
二、rpc编码
1 - gob编码
- 什么是gob编码:标准库的RPC默认采用Go语言特有的gob编码,标准库gob是golang提供的“私有”的编解码方式,它的效率会比json,xml等更高,特别适合在Go语言程序间传递数据
- codec\gob.go
package codecimport ("bytes""encoding/gob"
)func GobEncode(obj interface{}) ([]byte, error) {buf := bytes.NewBuffer([]byte{})//编码后的结果输出到buf里encoder := gob.NewEncoder(buf)//编码obj对象if err := encoder.Encode(obj); err != nil {return []byte{}, err}return buf.Bytes(), nil
}func GobDecode(data []byte, obj interface{}) error {decoder := gob.NewDecoder(bytes.NewReader(data))return decoder.Decode(obj)
}
- codec\gob_test.go
package codecimport ("fmt""testing""github.com/stretchr/testify/assert"
)type TestStruct struct {F1 stringF2 int
}func TestGob(t *testing.T) {should := assert.New(t)gobBytes, err := GobEncode(&TestStruct{F1: "test_f1",F2: 10,})if should.NoError(err) {fmt.Println(gobBytes)}obj := TestStruct{}err = GobDecode(gobBytes, &obj)if should.NoError(err) {fmt.Println(obj)}
}
2 - json on tcp
- gob编码的缺点:gob是golang提供的“私有”的编解码方式,因此从其它语言调用Go语言实现的RPC服务将比较困难
- Go语言的RPC框架
- RPC数据打包时可以通过插件实现自定义的编码和解码
- RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上
- 所有语言都支持的比较好的一些编码
- MessagePack: 高效的二进制序列化格式。它允许你在多种语言(如JSON)之间交换数据。但它更快更小
- JSON: 文本编码
- XML:文本编码
- Protobuf 二进制编码
- json_tcp\service
package serviceconst (//统一rpc的注册名SERVICE_NAME = "HelloService"
)type HelloService interface {Hello(request string, response *string) error
}
- json_tcp\server\main.go
package mainimport ("fmt""log""main/interface/service""net""net/rpc""net/rpc/jsonrpc"
)// 通过接口约束HelloService服务
var _ service.HelloService = (*HelloService)(nil)type HelloService struct {
}func (p *HelloService) Hello(request string, response *string) error {*response = fmt.Sprintf("hello,%s", request)return nil
}func main() {rpc.RegisterName(service.SERVICE_NAME, &HelloService{})listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal("ListenTCP error:", err)}for {conn, err := listener.Accept()if err != nil {log.Fatal("Accept error:", err)}go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))}
}
- json_tcp\client\main.go
package mainimport ("fmt""main/interface/service""net""net/rpc""net/rpc/jsonrpc"
)// 约束客户端
var _ service.HelloService = (*HelloServiceClient)(nil)func NewHelloServiceClient(network string, address string) (*HelloServiceClient, error) {conn, err := net.Dial(network, address)if err != nil {return nil, err}client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))return &HelloServiceClient{client: client,}, nil
}type HelloServiceClient struct {client *rpc.Client
}func (c *HelloServiceClient) Hello(request string, response *string) error {return c.client.Call(fmt.Sprintf("%s.Hello", service.SERVICE_NAME), request, response)
}func main() {c, err := NewHelloServiceClient("tcp", ":1234")if err != nil {panic(err)}var response stringif err := c.Hello("tom", &response); err != nil {panic(err)}fmt.Println(response)
}
3 - json on http(待补充)
三、prtotobuf编码
1 - prtotobuf概述
- Protobuf概述
- Protobuf是Protocol Buffers的简称,它是Google公司开发的一种数据描述语言,并于2008年对外开源
- Protobuf刚开源时的定位类似于XML、JSON等数据描述语言,通过附带工具生成代码并实现将结构化数据序列化的功能
- Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言PRC接口的基础工具
- 编解码工具的参考选项
- 编解码效率
- 高压缩比
- 多语言支持
- protobuf使用流程
- protobuf简单例子
- syntax: 表示采用proto3的语法。第三版的Protobuf对语言进行了提炼简化,所有成员均采用类似Go语言中的零值初始化(不再支持自定义默认值),因此消息成员也不再需要支持required特性
- package:指明当前是main包(这样可以和Go的包名保持一致,简化例子代码),当然用户也可以针对不同的语言定制对应的包路径和名称
- option:protobuf的一些选项参数, 这里指定的是要生成的Go语言package路径, 其他语言参数各不相同
- message: 关键字定义一个新的String类型,在最终生成的Go语言代码中对应一个String结构体。String类型中只有一个字符串类型的value成员,该成员编码时用1编号代替名字
syntax = "proto3";package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";message String {string value = 1;
}
2 - protobuf编译器
-
关于数据编码:
- 在XML或JSON等数据描述语言中,一般通过成员的名字来绑定对应的数据
- 但是Protobuf编码却是通过成员的唯一编号来绑定对应的数据,因此Protobuf编码后数据的体积会比较小,但是也非常不便于人类查阅
- 我们目前并不关注Protobuf的编码技术,最终生成的Go结构体可以自由采用JSON或gob等编码格式,因此大家可以暂时忽略Protobuf的成员编码部分
-
protobuf编译器:
- 作用:把定义文件(.proto)(IDL: 接口描述语言), 编译成不同语言的数据结构
- 下载地址:https://github.com/protocolbuffers/protobuf/releases
- 将bin中的protoc.exe配置到环境变量中运行 ->
protoc --version
-
通用命令查看已设置的环境变量路径:
where protoc
-
include编译器库:这个根据需要,覆盖对应的include文件目录
-
安装protobuf的go语言插件:
- Protobuf核心的工具集是C++语言开发的,在官方的protoc编译器中并不支持Go语言。要想基于上面的hello.proto文件生成相应的Go代码,需要安装相应的插件
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
-
生成pb文件:
protoc -I="." --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/hello.proto
- -I:-IPATH, --proto_path=PATH, 指定proto文件搜索的路径, 如果有多个路径 可以多次使用-I 来指定, 如果不指定默认为当前目录
- –go_out: --go指插件的名称, 我们安装的插件为: protoc-gen-go, 而protoc-gen是插件命名规范, go是插件名称, 因此这里是–go, 而–go_out 表示的是 go插件的 out参数, 这里指编译产物的存放目录
- –go_opt: protoc-gen-go插件opt参数, 这里的module指定了go module, 生成的go pkg 会去除掉module路径,生成对应pkg
- pb/hello.proto: 我们proto文件路径
-
如果需要为当前所有的proto生成pb文件:进入到protobuf/pb目录执行
protoc -I="." --go_out=. --go_opt=module="gitee.com/infraboard/go-course/day21/pb" *.proto
-
安装依赖库:生成之后我们可以看到有需要用到一些库,这里可以使用
go mod tidy
进行同步安装
3 - 序列化和反序列化
- 如何实现序列化和反序列化:使用google.golang.org/protobuf/proto工具提供的API来进行序列化与反序列化
- Marshal:序列化
- UnMarshal:反序列化
- 测试序列化与反序列化:protobuf\pb\hello_test.go
package pb_testimport ("fmt""main/protobuf/pb""testing""github.com/stretchr/testify/assert""google.golang.org/protobuf/proto"
)func TestMarshal(t *testing.T) {should := assert.New(t)str := &pb.String{Value: "hello"}// object -> protobuf -> []bytepbBytes, err := proto.Marshal(str)if should.NoError(err) {fmt.Println(pbBytes)}// []byte -> protobuf -> objectobj := pb.String{}err = proto.Unmarshal(pbBytes, &obj)if should.NoError(err) {fmt.Println(obj.Value)}
}
4 - 基于protobuf的RPC(待补充00:41:00到01:10:00)
- 定义交互数据结构:pbrpc\service\pb\hello.proto
syntax = "proto3";package hello;
option go_package="pbrpc/service";message Request {string value = 1;
}message Response {string value = 1;
}
- 进入路径pbrpc:
protoc -I="./service/pb/" --go_out=./service --go_opt=module="pbrpc/service" hello.proto
- pbrpc\service\interface.go
package serviceconst (SERVICE_NAME = "HelloService"
)type HelloService interface {Hello(request *Request, response *Response) error
}
四、proto3语法
1 - 消息定义
- 定义消息类型
syntax = "proto3";/* SearchRequest represents a search query, with pagination options to* indicate which results to include in the response. */message SearchRequest {string query = 1;int32 page_number = 2; // Which page number do we want?int32 result_per_page = 3;
}
- 第一行是protobuf的版本, 我们主要讲下 message 的定义语法
- comment: 注射 /* */或者 //
- message_name: 同一个pkg内,必须唯一
- filed_rule: 可以没有, 常用的有repeated, oneof
- filed_type: 数据类型, protobuf定义的数据类型, 生产代码的会映射成对应语言的数据类型
- filed_name: 字段名称, 同一个message 内必须唯一
- field_number: 字段的编号, 序列化成二进制数据时的字段编号, 同一个message 内必须唯一, 1 ~ 15 使用1个Byte表示, 16 ~ 2047 使用2个Byte表示
<comment>message <message_name> {<filed_rule> <filed_type> <filed_name> = <field_number> 类型 名称 编号
}
- 如果你想保留一个编号,以备后来使用可以使用 reserved 关键字声明
message Foo {reserved 2, 15, 9 to 11;reserved "foo", "bar";
}
2 - 基础类型
- Value(Filed) Types:protobuf 定义了很多Value Types, 他和其他语言的映射关系如下
3 - 枚举类型
- 使用enum来声明枚举类型
syntax = "proto3";package hello;
option go_package="gitee.com/infraboard/go-course/day21/pb";enum Color {RED = 0;GREEN = 1;BLACK = 2;
}
- 对应生成的pb文件
- 枚举声明语法
- enum_name: 枚举名称
- element_name: pkg内全局唯一, 很重要
- element_name: 必须从0开始, 0表示类型的默认值, 32-bit integer
enum <enum_name> {<element_name> = <element_number>
}
- 别名:如果你的确有2个同名的枚举需求: 比如 TaskStatus 和 PipelineStatus 都需要Running,就可以添加一个:
option allow_alias = true;
enum EnumAllowingAlias {option allow_alias = true;UNKNOWN = 0;STARTED = 1;RUNNING = 1;}
- 枚举也支持预留值
enum Foo {reserved 2, 15, 9 to 11, 40 to max;reserved "FOO", "BAR";
}
4 - 数组类型
- 数组类型使用repeated
syntax = "proto3";package hello;
option go_package="protobuf/pb";message Host{string name = 1;string ip = 2;
}message SearchResponse {int64 total = 1;//定义一个HOST数组repeated Host hosts = 2;
}
5 - Map
- protobuf 声明map的语法:
map<key_type, value_type> map_field = N;
syntax = "proto3";package hello;
option go_package="protobuf/pb";message Host{string name = 1;string ip = 2;map <string,string> tags = 3;
}message SearchResponse {int64 total = 1;//定义一个HOST数组repeated Host hosts = 2;
}
6 - Oneof
- Oneof:限定类型只能是其中一种
message ProtobufEventHeader{string id = 1;map<string,string> headers = 2;
}message JSONEventHeader{string id = 1;bytes headers = 2;
}message Event{oneof header{ProtobufEventHeader protobuf = 1;JSONEventHeader json = 2;}
}
- 两种使用方法
- 采用断言来判断one of的类型:
e.GetHeader()
- 直接通过get获取,判断返回是否为nil
err1 := e.GetProtobuf()
err2 := e.GetJson()
- 采用断言来判断one of的类型:
7 - Any
- Any:当我们无法明确定义数据类型的时候, 可以使用Any表示
- 注意需要引入包:
import "google/protobuf/any.proto"
- protoc如果报错,需要在-I中指定google protobuf的路径
- 注意需要引入包:
// 这里是应用其他的proto文件, 后面会讲 ipmort用法
import "google/protobuf/any.proto";message ErrorStatus {string message = 1;repeated google.protobuf.Any details = 2;
}
func TestAny(t *testing.T) {es := &pb.ErrorStatus{Message: "hello"}anyEs, err := anypb.New(es)if err != nil {panic("err")}fmt.Println(anyEs) //[type.googleapis.com/hello.ErrorStatus]:{message:"hello"}obj := pb.ErrorStatus{}anyEs.UnmarshalTo(&obj)fmt.Println(obj.Message) //hello
}
8 - 类型嵌套
- 可以再message里面嵌套message(不建议使用): 但是不允许 匿名嵌套, 必须指定字段名称
message Outer { // Level 0message MiddleAA { // Level 1message Inner { // Level 2int64 ival = 1;bool booly = 2;}}message MiddleBB { // Level 1message Inner { // Level 2int32 ival = 1;bool booly = 2;}}}
9 - 引用包
- import “google/protobuf/any.proto”;:
- 上面这在情况就是读取的标准库, 我们在安装protoc的时候, 已经把改lib 挪到usr/local/include下面了,所以可以找到
- 如果我们proto文件并没有在/usr/local/include目录下, 通过-I 可以添加搜索的路径, 这样就编译器就可以找到我们引入的包了
protoc -I=. --go_out=./pb --go_opt=module="gitee.com/infraboard/go-course/day21/pb" pb/import.proto
五、gRPC
1 - gRPC概念
-
什么是grpc:gRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。gRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好
-
GRPC技术栈:Stub: 应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信
- 数据交互格式: protobuf
- 通信方式: 最底层为TCP或Unix Socket协议,在此之上是HTTP/2协议的实现
- 核心库: 在HTTP/2协议之上又构建了针对Go语言的gRPC核心库
- Stub: 应用程序通过gRPC插件生产的Stub代码和gRPC核心库通信,也可以直接和gRPC核心库通信
-
安装grpc插件:
# protoc-gen-go 插件之前已经安装
# go install google.golang.org/protobuf/cmd/protoc-gen-go@latest# 安装protoc-gen-go-grpc插件
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
- 查看当前插件的版本:``
2 - gRPC简单实现
- grpc\server\pb\hello.proto
- 根据需求进入对应的路径:C:\develop_project\go_project\proj1\grpc\server
- 生成命令:
protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.proto
- 可以看到生成了grpc\server\pb\hello_grpc.pb.go
- 如果是第一次生成还需要同步下:
go mod tidy
syntax = "proto3";package hello;
option go_package="grpc/server/pb";//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto //grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.protoservice HelloService{rpc hello (HelloRequest) returns(HelloResponse);
}message HelloRequest{string value = 1;
}message HelloResponse{string value = 1;
}
- server:grpc\server\main.go
package mainimport ("context""fmt""log""main/grpc/server/pb""net""google.golang.org/grpc"
)type HelloServiceServer struct {pb.UnimplementedHelloServiceServer
}func (p *HelloServiceServer) Hello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {return &pb.HelloResponse{Value: fmt.Sprintf("hello,%s", req.Value),}, nil
}func main() {//把实现类注册给 GRPC Serverserver := grpc.NewServer()pb.RegisterHelloServiceServer(server, &HelloServiceServer{})listen, err := net.Listen("tcp", ":1234")if err != nil {panic(err)}log.Printf("grpc listen addr: 127.0.0.1:1234")//监听Socket,HTTP2内置if err := server.Serve(listen); err != nil {panic(err)}
}
- client:grpc\client\main.go
package mainimport ("context""fmt""main/grpc/server/pb""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func main() {//第一步,建立网络连接conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {panic(err)}//grpc为我们生成一个客户端调用服务端的SDKclient := pb.NewHelloServiceClient(conn)resp, err := client.Hello(context.Background(), &pb.HelloRequest{Value: "tom"})if err != nil {panic(err)}fmt.Println(resp.Value)
}
3 - gRPC流
- 为什么要使用个RPC流:
- RPC是远程函数调用,因此每次调用的函数参数和返回值不能太大,否则将严重影响每次调用的响应时间
- 因此传统的RPC方法调用对于上传和下载较大数据量场景并不适合
- 为此,gRPC框架针对服务器端和客户端分别提供了流特性
- 如何制定gRPC流:
- 关键字stream指定启用流特性,参数部分是接收客户端参数的流,返回值是返回给客户端的流
- 定义gRPC流的语法:
rpc <function_name> (stream <type>) returns (stream <type>) {}
- grpc\server\pb\hello.proto
syntax = "proto3";package hello;
option go_package="grpc/server/pb";//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" pb/hello.proto //grpc生成
//PS C:\develop_project\go_project\proj1\grpc\server>
//protoc -I="." --go_out=. --go_opt=module="grpc/server" --go-grpc_out=. --go-grpc_opt=module="grpc/server" pb/hello.protoservice HelloService{rpc Channel(stream HelloRequest) returns(stream HelloResponse){}
}message HelloRequest{string value = 1;
}message HelloResponse{string value = 1;
}
- grpc\server\main.go
package mainimport ("fmt""io""log""main/grpc/server/pb""net""google.golang.org/grpc"
)type HelloServiceServer struct {pb.UnimplementedHelloServiceServer
}func (p *HelloServiceServer) Channel(stream pb.HelloService_ChannelServer) error {for {//接收请求req, err := stream.Recv()if err != nil {if err == io.EOF {//当前客户端退出log.Printf("client closed")return nil}return err}fmt.Printf("recv req value : %s\n", req.Value)resp := &pb.HelloResponse{Value: fmt.Sprintf("hello,%s", req.Value)}//响应请求err = stream.Send(resp)if err != nil {if err == io.EOF {log.Printf("client closed")return nil}return err //服务端发送异常, 函数退出, 服务端流关闭}}
}func main() {//把实现类注册给 GRPC Serverserver := grpc.NewServer()pb.RegisterHelloServiceServer(server, &HelloServiceServer{})listen, err := net.Listen("tcp", ":1234")if err != nil {panic(err)}log.Printf("grpc listen addr: 127.0.0.1:1234")//监听Socket,HTTP2内置if err := server.Serve(listen); err != nil {panic(err)}
}
- grpc\client\main.go
package mainimport ("context""fmt""main/grpc/server/pb""time""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure"
)func main() {//第一步,建立网络连接conn, err := grpc.DialContext(context.Background(), "127.0.0.1:1234", grpc.WithTransportCredentials(insecure.NewCredentials()))if err != nil {panic(err)}//grpc为我们生成一个客户端调用服务端的SDKclient := pb.NewHelloServiceClient(conn)stream, err := client.Channel(context.Background())if err != nil {panic(err)}//启用一个Goroutine来发送请求go func() {for {err := stream.Send((&pb.HelloRequest{Value: "tom"}))if err != nil {panic(err)}time.Sleep(1 * time.Second)}}()for {//主循环,负责接收服务端响应resp, err := stream.Recv()if err != nil {panic(err)}fmt.Println(resp)}
}