🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] # 简介 golang本身有rpc包,可以方便的使用,来构建自己的rpc服务,下边是一个简单是实例,可以加深我们的理解 ![](https://box.kancloud.cn/286674b97311dc901ed172d5a15c481d_538x484.png) 1. 调用客户端句柄;执行传送参数 2. 调用本地系统内核发送网络消息 3. 消息传送到远程主机 4. 服务器句柄得到消息并取得参数 5. 执行远程过程 6. 执行的过程将结果返回服务器句柄 7. 服务器句柄返回结果,调用远程系统内核 8. 消息传回本地主机 9. 客户句柄由内核接收消息 10. 客户接收句柄返回的数据 `import "net/rpc"` rpc包提供了通过网络或其他I/O连接对一个对象的导出方法的访问。**服务端注册一个对象**,使它作为一个服务被暴露,服务的名字是该对象的类型名。注册之后,对象的导出方法就可以被远程访问。服务端可以注册多个不同类型的对象(服务),但注册具有相同类型的多个对象是错误的。 **只有满足如下标准的方法才能用于远程访问,其余方法会被忽略:** ~~~ - 方法是导出的 - 方法有两个参数,都是导出类型或内建类型 - 方法的第二个参数是指针 - 方法只有一个error接口类型的返回值 ~~~ 事实上,方法必须看起来像这样: ~~~ func (t *T) MethodName(argType T1, replyType *T2) error ~~~ # 测试rpc 服务端,写方法,注册rpc ~~~ package main import ( "fmt" "io" "net" "net/http" "net/rpc" ) func pandatext(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "hello world hello panda") } /** - 方法是导出的 - 方法有两个参数,都是导出类型或内建类型 - 方法的第二个参数是指针 - 方法只有一个error接口类型的返回值 事实上,方法必须看起来像这样: func (t *T) MethodName(argType T1, replyType *T2) error */ type Panda int //函数关键字(对象)函数名(对端发送过来的内容, 返回给对端的内容) 错误返回值 func (this *Panda) Getinfo(argType int, replyType *int) error { fmt.Println("打印对端发送过来的内容为: ", argType) //修改内容值 *replyType = argType + 12306 return nil } func main() { //页面的请求 http.HandleFunc("/panda", pandatext) //需要个对象 pd := new(Panda) //服务端注册一个对象 rpc.Register(pd) rpc.HandleHTTP() //监听网络 ln, err := net.Listen("tcp", ":10086") if err != nil { fmt.Println("网络错误") } http.Serve(ln, nil) } ~~~ 客户端,连接服务端,发送消息,接收消息 ~~~ import ( "fmt" "net/rpc" ) func main() { //建立网络连接 cli, err := rpc.DialHTTP("tcp", "127.0.0.1:10086") if err != nil { fmt.Println("---连接错误---") } var pd int //rpc服务端对象的方法,发送过去的值,接收变量的指针 e := cli.Call("Panda.Getinfo", 1000, &pd) if e != nil { fmt.Println("---call调用失败---") } fmt.Println("最后得到的值为: ", pd) } ~~~ # grpc使用protobuf ## 编写protobuf ~~~ syntax = "proto3"; package myproto; //定义服务 service Helloserver { //一个打招呼的函数 rpc Sayhello(HelloReq) returns (HelloRsp) {} //一个说名字的服务 rpc Sayname(NameReq) returns (NameRsq) {} } //客户端发送给服务端 message HelloReq { string name = 1; } //服务端返回给客户端 message HelloRsp { string msg = 1; } //客户端发送给服务端 message NameReq { string name = 2; } //服务端返回给客户端 message NameRsq { string msg = 2; } ~~~ 编译protobuf,用带grpc插件的方式编译 ~~~ protoc --go_out=./ *.proto # 不加grpc插件 protoc --go_out=plugins=grpc:./ *.proto # 添加grpc插件 ~~~ ## 编写服务端 根据protobuf编写 ~~~ package main import ( "context" "fmt" "google.golang.org/grpc" "net" pd "studygo/myproto" ) /** 复制的 .pd.go文件的 type HelloserverClient interface { //一个打招呼的函数 Sayhello(ctx context.Context, in *HelloReq, opts ...grpc.CallOption) (*HelloRsp, error) //一个说名字的服务 Sayname(ctx context.Context, in *NameReq, opts ...grpc.CallOption) (*NameRsq, error) } */ type server struct{} //rpc //函数关键字(对象)函数名(客户端发送过来的内容 , 返回给客户端的内容) 错误返回值 //grpc //函数关键字 (对象)函数名 (cotext,客户端发过来的参数 )(发送给客户端的参数,错误) //进行改下,绑定到你自己的结构体,参数带上包名 //一个打招呼的函数 func (this *server) Sayhello(ctx context.Context, in *pd.HelloReq) (out *pd.HelloRsp, err error) { //服务端发送给客户端,那边定义的是msg,in.Name,in是参数名,Name是protobuf定义的 fmt.Println("Sayhello客户端发送过来的: ", in.Name) return &pd.HelloRsp{Msg: "hello"}, nil } //一个说名字的服务 func (this *server) Sayname(ctx context.Context, in *pd.NameReq) (out *pd.NameRsp, err error) { //服务端发送给客户端,那边定义的是msg,in.Name,in是参数名,Name是protobuf定义的 fmt.Println("Sayname客户端发送过来的: ", in.Name) //客户端发过来的就是in.Name Name就是protobuf那定义的 return &pd.NameRsp{Msg: in.Name + "早上好"}, nil } func main() { //grpc监听创建网络 ln, err := net.Listen("tcp", ":10086") if err != nil { fmt.Println("网络错误: ", err) } //创建grpc的服务 srv := grpc.NewServer() //注册服务 //pd就是protobuf那边的包,Register后面跟服务名 pd.RegisterHelloserverServer(srv, &server{}) //等待网络连接 err = srv.Serve(ln) if err != nil { fmt.Println("网络错误: ", err) } } ~~~ 运行起来 ## 编写客户端 需要protobuf ~~~ package main import ( "fmt" pd "backend/myproto" "google.golang.org/grpc" "context" ) func main() { //客户端连接服务器 conn ,err :=grpc.Dial("127.0.0.1:10086",grpc.WithInsecure()) if err != nil { fmt.Println("网络异常: ", err) } //网络延迟关闭 defer conn.Close() //获得grpc句柄 c := pd.NewHelloserverClient(conn) //根据protobuf编写 //通过句柄调用函数 re, err := c.Sayhello(context.Background(), &pd.HelloReq{Name: "熊猫"}) if err != nil { fmt.Println("sayHello 服务调用失败") } fmt.Println("调用sayHello的返回", re.Msg) re1, err1 := c.Sayname(context.Background(), &pd.NameReq{Name: "abc"}) if err1 != nil { fmt.Println("sayname 服务调用失败") } fmt.Println("调用sayname的返回", re1.Msg) } ~~~ 执行下