摘要: 整体上来讲,HTTP每次请求比较浪费资源的。虽然HTTP也是走在TCP上面的,可是HTTP请求本身添加了不少本身的信息,所以会消耗带宽资源。因此一些公司就是用RPC做为内部应用的通讯协议。原文bash
若是你对Go也感兴趣, 能够关注个人公众号: GoGuider网络
RPC(Remote Procedure Call,远程过程调用)是一种经过网络从远程计算机程序上请求服务,而不须要了解底层网络细节的应用程序通讯协议。RPC协议构建于TCP或UDP,或者是HTTP上。异步
在Go中,标准库提供的net/rpc包实现了RPC协议须要的相关细节,开发者能够很方便的使用该包编写RPC的服务端和客户端程序。async
从上图看, RPC自己就是一个client-server模型。tcp
下面列举一个实例代码, 来了解RPC调用过程ide
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))
}
复制代码
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, &sum, 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端学习
client端ui
其实细心的朋友会注意到client.go 里面有client.Call 和 client.Go 调用;spa
查看源码能够看到client.Call 底层就是调用的client.Go.net
// 部分源码:
/ 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 } 复制代码