标砖库提供了 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 stringPrice float64Status 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参数就不会再返回给调用者。
至此为止,已经实现了服务端的功能定义,接下来就是让服务代码生效,需要将服务进行注册,并启动请求处理。
二、注册服务及监听请求 - HTTP
net/rpc
包为我们提供了注册服务和处理请求的一系列方法,结合本案例实现注册及处理逻辑,如下所示:
err := rpc.Register(new(repo.Order))
if err != nil {log.Fatal(err)
}
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的端口监听方式等待连接的,因此在客户端就需要通过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.OrderInfoerr = 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 :&#61; rpc.DialHTTP("tcp", "127.0.0.1:8100")if err !&#61; nil {log.Fatal("dialing:", err)}orderId :&#61; "aaaa"var orderInfo repo.OrderInfosyncCall :&#61; client.Go("Order.GetOne", orderId, &orderInfo, nil)<-syncCall.Donefmt.Println(orderInfo)
}
同时&#xff0c;net/rpc 还提供了一个简易的统计页供查看&#xff1a;
http://127.0.0.1:8100/debug/rpc
![在这里插入图片描述](https://img8.php1.cn/3cdc5/fd8c/3b4/8a816368929c3ca4.png?x-oss-process&#61;image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Jhb3hpYW95YQ&#61;&#61;,size_16,color_FFFFFF,t_70#pic_center)
四、注册服务及监听请求 - TCP
package mainimport ("log""net""net/rpc""demo1/go-rpc/repo"
)func main() {err :&#61; rpc.Register(new(repo.Order))if err !&#61; nil {log.Fatal(err)}l, err :&#61; net.Listen("tcp", ":8100")if err !&#61; nil {log.Fatal(err)}for {conn, e :&#61; l.Accept()if e !&#61; nil {continue}go rpc.ServeConn(conn)}
}
五、客户端调用 - TCP
package mainimport ("fmt""log""net/rpc""demo1/go-rpc/repo"
)func main() {client, err :&#61; rpc.Dial("tcp", "127.0.0.1:8100")if err !&#61; nil {log.Fatal("dialing:", err)}orderId :&#61; "aaaa"var orderInfo repo.OrderInfoerr &#61; client.Call("Order.GetOne", orderId, &orderInfo)if err !&#61; nil {log.Fatal("Order error:", err)}fmt.Println(orderInfo)
}
六、原理解析
首先注册了两个路由与handle&#xff0c;这是 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 &#61; "/_goRPC_"
DefaultDebugPath &#61; "/debug/rpc"
而且 /debug/rpc
在上面还用到过&#xff1b;
/_goRPC_
则是rpc服务的路由&#xff0c;然后通过传递的参数去调用指定对象的方法。
再就是注册服务&#xff0c;其实就是通过反射导出对象的可调用的方法存入一个map。这是 rpc.Register(new(repo.Order))
做的事情。
func (server *Server) register(rcvr interface{}, name string, useName bool) error {s :&#61; new(service)s.typ &#61; reflect.TypeOf(rcvr)s.rcvr &#61; reflect.ValueOf(rcvr)sname :&#61; reflect.Indirect(s.rcvr).Type().Name()if useName {sname &#61; name}if sname &#61;&#61; "" {s :&#61; "rpc.Register: no service name for type " &#43; s.typ.String()log.Print(s)return errors.New(s)}if !token.IsExported(sname) && !useName {s :&#61; "rpc.Register: type " &#43; sname &#43; " is not exported"log.Print(s)return errors.New(s)}s.name &#61; snames.method &#61; suitableMethods(s.typ, true)if len(s.method) &#61;&#61; 0 {str :&#61; ""method :&#61; suitableMethods(reflect.PtrTo(s.typ), false)if len(method) !&#61; 0 {str &#61; "rpc.Register: type " &#43; sname &#43; " has no exported methods of suitable type (hint: pass a pointer to value of that type)"} else {str &#61; "rpc.Register: type " &#43; sname &#43; " has no exported methods of suitable type"}log.Print(str)return errors.New(str)}if _, dup :&#61; server.serviceMap.LoadOrStore(sname, s); dup {return errors.New("rpc: service already defined: " &#43; sname)}return nil
}func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {methods :&#61; make(map[string]*methodType)for m :&#61; 0; m < typ.NumMethod(); m&#43;&#43; {method :&#61; typ.Method(m)mtype :&#61; method.Typemname :&#61; method.Name......methods[mname] &#61; &methodType{method: method, ArgType: argType, ReplyType: replyType}}return methods
}
为了更直观的理解&#xff0c;可以看下面的例子。
package mainimport ("fmt""reflect"
)type Peaple struct {
}func (p *Peaple) Eat() {}
func (p *Peaple) Drink() {}func main() {a :&#61; new(Peaple)getType :&#61; reflect.TypeOf(a)getValue :&#61; reflect.ValueOf(a)obj :&#61; reflect.Indirect(getValue).Type().Name()num :&#61; getType.NumMethod()for i :&#61; 0; i < num; i&#43;&#43; {m :&#61; getType.Method(i)fmt.Println(obj &#43; "." &#43; m.Name)}
}
输出结果
Peaple.Drink
Peaple.Eat
也就是客户端调用的第一个参数。