本文共 5554 字,大约阅读时间需要 18 分钟。
标砖库提供了 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 repoimport ( "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 mainimport ( "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 mainimport ( "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/rpcpackage mainimport ( "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 mainimport ( "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 mainimport ( "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.DrinkPeaple.Eat
也就是客户端调用的第一个参数。
转载地址:http://ajaui.baihongyu.com/