标砖库提供了 net/rpc
包用来实现基础的rpc调用。
net/rpc库使用encoding/gob进行编解码,支持tcp
或http
数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用。
主要有服务端和客户端。
首先是提供方法暴露的一方–服务器。
在编程实现过程中,服务器端需要注册结构体对象,然后通过对象所属的方法暴露给调用者,从而提供服务,该方法称之为输出方法,此输出方法可以被远程调用。当然,在定义输出方法时,能够被远程调用的方法需要遵循一定的规则。我们通过代码进行讲解:
func (t *T) MethodName(request T1,response *T2) error
上述代码是go语言官方给出的对外暴露的服务方法的定义标准,其中包含了主要的几条规则,分别是:
1、对外暴露的方法有且只能有两个参数,这个两个参数只能是输出类型或内建类型,两种类型中的一种。
2、方法的第二个参数必须是指针类型。
3、方法的返回类型为error。
4、方法的类型是可输出的。
5、方法本身也是可输出的。
package repo
import (
"errors"
)
type Order struct {
}
type OrderInfo struct {
Id string
Price float64
Status int
}
func (o *Order) GetOne(orderId string, orderInfo *OrderInfo) error {
if orderId == "" {
return errors.New("orderId is invalid")
}
*orderInfo = OrderInfo{
Id: orderId,
Price: 100.00,
Status: 1,
}
return nil
}
正常情况下,方法的返回值为是error,为nil。如果遇到异常或特殊情况,则error将作为一个字符串返回给调用者,此时,resp参数就不会再返回给调用者。
至此为止,已经实现了服务端的功能定义,接下来就是让服务代码生效,需要将服务进行注册,并启动请求处理。
net/rpc
包为我们提供了注册服务和处理请求的一系列方法,结合本案例实现注册及处理逻辑,如下所示:
// 调用net/rpc包的功能将服务对象进行注册
err := rpc.Register(new(repo.Order))
if err != nil {
log.Fatal(err)
}
// 通过该函数把Order中提供的服务注册到HTTP协议上,方便调用者可以利用http的方式进行数据传递
rpc.HandleHTTP()
// 在特定的端口进行监听
l, err := net.Listen("tcp", ":8100")
if err != nil {
log.Fatal(err)
}
err = http.Serve(l, nil)
if err != nil {
log.Fatal(err)
}
经过服务注册和监听处理,RPC调用过程中的服务端实现就已经完成了。接下来需要实现的是客户端请求代码的实现。
当然,你可以注册多个服务到同一个端口。
在服务端是通过Http的端口监听方式等待连接的,因此在客户端就需要通过http连接,首先与服务端实现连接。
package main
import (
"fmt"
"log"
"net/rpc"
"demo1/go-rpc/repo"
)
func main() {
client, err := rpc.DialHTTP("tcp", "127.0.0.1:8100")
if err != nil {
log.Fatal("dialing:", err)
}
orderId := "aaaa"
var orderInfo repo.OrderInfo
err = client.Call("Order.GetOne", orderId, &orderInfo)
if err != nil {
log.Fatal("Order error:", err)
}
fmt.Println(orderInfo)
}
打印信息
{
aaaa 100 1}
当然,客户端和服务端同时使用了 repo.OrderInfo
对象,所以应该将其放在公共的 pkg 中方便管理。
上述的Call方法调用实现的方式是同步的调用,除此之外,还有一种异步的方式可以实现调用。异步调用代码实现如下:
package main
import (
"fmt"
"log"
"net/rpc"
"demo1/go-rpc/repo"
)
func main() {
client, err := rpc.DialHTTP("tcp", "127.0.0.1:8100")
if err != nil {
log.Fatal("dialing:", err)
}
orderId := "aaaa"
var orderInfo repo.OrderInfo
syncCall := client.Go("Order.GetOne", orderId, &orderInfo, nil)
<-syncCall.Done
fmt.Println(orderInfo)
}
同时,net/rpc 还提供了一个简易的统计页供查看:
http://127.0.0.1:8100/debug/rpc
package main
import (
"log"
"net"
"net/rpc"
"demo1/go-rpc/repo"
)
func main() {
err := rpc.Register(new(repo.Order))
if err != nil {
log.Fatal(err)
}
l, err := net.Listen("tcp", ":8100")
if err != nil {
log.Fatal(err)
}
for {
conn, e := l.Accept()
if e != nil {
continue
}
go rpc.ServeConn(conn)
}
}
package main
import (
"fmt"
"log"
"net/rpc"
"demo1/go-rpc/repo"
)
func main() {
client, err := rpc.Dial("tcp", "127.0.0.1:8100")
if err != nil {
log.Fatal("dialing:", err)
}
orderId := "aaaa"
var orderInfo repo.OrderInfo
err = client.Call("Order.GetOne", orderId, &orderInfo)
if err != nil {
log.Fatal("Order error:", err)
}
fmt.Println(orderInfo)
}
首先注册了两个路由与handle,这是 rpc.HandleHTTP()
做的事情。
func (server *Server) HandleHTTP(rpcPath, debugPath string) {
http.Handle(rpcPath, server)
http.Handle(debugPath, debugHTTP{
server})
}
func HandleHTTP() {
DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
}
其中
DefaultRPCPath = "/_goRPC_"
DefaultDebugPath = "/debug/rpc"
而且 /debug/rpc
在上面还用到过;
/_goRPC_
则是rpc服务的路由,然后通过传递的参数去调用指定对象的方法。
再就是注册服务,其实就是通过反射导出对象的可调用的方法存入一个map。这是 rpc.Register(new(repo.Order))
做的事情。
func (server *Server) register(rcvr interface{
}, name string, useName bool) error {
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
if useName {
sname = name
}
if sname == "" {
s := "rpc.Register: no service name for type " + s.typ.String()
log.Print(s)
return errors.New(s)
}
if !token.IsExported(sname) && !useName {
s := "rpc.Register: type " + sname + " is not exported"
log.Print(s)
return errors.New(s)
}
s.name = sname
// Install the methods
s.method = suitableMethods(s.typ, true)
if len(s.method) == 0 {
str := ""
// To help the user, see if a pointer receiver would work.
method := suitableMethods(reflect.PtrTo(s.typ), false)
if len(method) != 0 {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
} else {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
}
log.Print(str)
return errors.New(str)
}
if _, dup := server.serviceMap.LoadOrStore(sname, s); dup {
return errors.New("rpc: service already defined: " + sname)
}
return nil
}
func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
methods := make(map[string]*methodType)
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
mtype := method.Type
mname := method.Name
...
...
methods[mname] = &methodType{
method: method, ArgType: argType, ReplyType: replyType}
}
return methods
}
为了更直观的理解,可以看下面的例子。
package main
import (
"fmt"
"reflect"
)
type Peaple struct {
}
func (p *Peaple) Eat() {
}
func (p *Peaple) Drink() {
}
func main() {
a := new(Peaple)
getType := reflect.TypeOf(a)
getValue := reflect.ValueOf(a)
obj := reflect.Indirect(getValue).Type().Name()
num := getType.NumMethod()
for i := 0; i < num; i++ {
m := getType.Method(i)
fmt.Println(obj + "." + m.Name)
}
}
输出结果
Peaple.Drink
Peaple.Eat
也就是客户端调用的第一个参数。