摘要: 总体上来说, HTTP 每次请求比较浪费资源的. 虽然 HTTP 也是走在 TCP 上面的, 但是 HTTP 请求自己添加了很多自己的信息, 因此会消耗带宽资源. 所以一些公司就是用 RPC 作为内部应用的通信协议. 原文
如果你对 Go 也感兴趣, 可以关注我的公众号: GoGuider
RPC
RPC(Remote Procedure Call, 远程过程调用) 是一种通过网络从远程计算机程序上请求服务, 而不需要了解底层网络细节的应用程序通信协议. RPC 协议构建于 TCP 或 UDP, 或者是 HTTP 上.
在 Go 中, 标准库提供的 net/rpc 包实现了 RPC 协议需要的相关细节, 开发者可以很方便的使用该包编写 RPC 的服务端和客户端程序.
从上图看, RPC 本身就是一个 client-server 模型.
下面列举一个实例代码, 来了解 RPC 调用过程
- server.go
- package main
- import (
- "fmt"
- "log"
- "net"
- "net/http"
- "net/rpc"
- "os"
- "time"
- )
- type Args struct {
- A, B int
- }
- type Math int
- // 计算乘积
- func (t *Math) Multiply(args *Args, reply *int) error {
- time.Sleep(time.Second * 3) // 睡 1 秒, 同步调用会等待, 异步会先往下执行
- *reply = args.A * args.B
- fmt.Println("Multiply")
- return nil
- }
- // 计算和
- func (t *Math) Sum(args *Args, reply *int) error {
- time.Sleep(time.Second * 3)
- *reply = args.A + args.B
- fmt.Println("Sum")
- return nil
- }
- func main() {
- // 创建对象
- math := new(Math)
- //rpc 服务注册了一个 Math 对象 公开方法供客户端调用
- rpc.Register(math)
- // 指定 rpc 的传输协议 这里采用 http 协议作为 rpc 调用的载体 也可以用 rpc.ServeConn 处理单个连接请求
- rpc.HandleHTTP()
- l, e := net.Listen("tcp", ":1234")
- if e != nil {
- log.Fatal("listen error", e)
- }
- go http.Serve(l, nil)
- os.Stdin.Read(make([]byte, 1))
- }
- client.go
- package main
- import (
- "fmt"
- "log"
- "net/rpc"
- "time"
- )
- type Args struct {
- A, B int
- }
- func main() {
- // 调用 rpc 服务端提供的方法之前, 先与 rpc 服务端建立连接
- client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
- if err != nil {
- log.Fatal("dialHttp error", err)
- return
- }
- // 同步调用服务端提供的方法
- args := &Args{7, 8}
- var reply int
- // 可以查看源码 其实 Call 同步调用是用异步调用实现的. 后续再详细学习
- err = client.Call("Math.Multiply", args, &reply) // 这里会阻塞三秒
- if err != nil {
- log.Fatal("call Math.Multiply error", err)
- }
- fmt.Printf("Multiply:%d*%d=%d\n", args.A, args.B, reply)
- // 异步调用
- var sum int
- divCall := client.Go("Math.Sum", args, ∑, nil)
- // 使用 select 模型监听通道有数据时执行, 否则执行后续程序
- for {
- select {
- case <-divCall.Done:
- fmt.Printf("%d %d 是 %d, 退出执行!", args.A, args.B, sum)
- return
- default:
- fmt.Println("继续等待....")
- time.Sleep(time.Second * 1)
- }
- }
- }
运行命令
- go run server.go
- go run client.go
运行结果
Multiply:7*8=56
继续等待....
继续等待....
继续等待....
7+8=15, 出执行
调用过程解析
server 端
rpc 服务注册了一个 Math 对象 公开方法供客户端调用
采用 http 协议作为 rpc 调用的载体, 处理请求
client 端
调用 rpc 服务端提供的方法之前, 先与 rpc 服务端建立连接
使用 Call 方法调用远程方法
延伸
其实细心的朋友会注意到 client.go 里面有 client.Call 和 client.Go 调用;
查看源码可以看到 client.Call 底层就是调用的 client.Go
- // 部分源码:
- / Go invokes the function asynchronously. It returns the Call structure representing
- // the invocation. The done channel will signal when the call is complete by returning
- // the same Call object. If done is nil, Go will allocate a new channel.
- // If non-nil, done must be buffered or Go will deliberately crash.
- func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
- call := new(Call)
- call.ServiceMethod = serviceMethod
- call.Args = args
- call.Reply = reply
- if done == nil {
- done = make(chan *Call, 10) // buffered.
- } else {
- // If caller passes done != nil, it must arrange that
- // done has enough buffer for the number of simultaneous
- // RPCs that will be using that channel. If the channel
- // is totally unbuffered, it's best not to run at all.
- if cap(done) == 0 {
- log.Panic("rpc: done channel is unbuffered")
- }
- }
- call.Done = done
- client.send(call)
- return call
- }
- // 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)).Done
- return call.Error
- }
参考文章
gRPC 官方文档 https://grpc.io/docs/
gRPC 中文文档 http://doc.oschina.net/grpc
来源: http://www.tuicool.com/articles/MvQRzq7