博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Golang-RPC(一):golang中的rpc实现
阅读量:3986 次
发布时间:2019-05-24

本文共 5554 字,大约阅读时间需要 18 分钟。

标砖库提供了 net/rpc 包用来实现基础的rpc调用。

net/rpc库使用encoding/gob进行编解码,支持tcphttp数据传输方式,由于其他语言不支持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参数就不会再返回给调用者。

至此为止,已经实现了服务端的功能定义,接下来就是让服务代码生效,需要将服务进行注册,并启动请求处理。

二、注册服务及监听请求 - HTTP

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的端口监听方式等待连接的,因此在客户端就需要通过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/rpc

在这里插入图片描述

四、注册服务及监听请求 - TCP

package 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) }}

五、客户端调用 - TCP

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/

你可能感兴趣的文章
mysql:sql drop database 删除数据库
查看>>
mysql:sql character set utf8mb4 新建utf8mb4表
查看>>
mysql:sql drop table (删除表)
查看>>
mysql:sql truncate (清除表数据)
查看>>
mysql:sql order by */* desc (查询)
查看>>
scrapy:xpath string(.)非常注意问题
查看>>
剑指Offer:字符流中第一个不重复的字符
查看>>
剑指Offer:链表中环的入口节点
查看>>
剑指Offer:数据流中的中位数
查看>>
剑指Offer:滑动窗口的最大值
查看>>
Qt组态图片区域事件响应
查看>>
一张图片与它的掩码 mask 蒙板
查看>>
GIMP 画布适应选区
查看>>
QSqlQuery执行多条Sql语句的方法
查看>>
渐变矩阵
查看>>
制作图像素材时,背景图片中的区域位置坐标定位
查看>>
渐变时间点计算
查看>>
实时监控组态监控
查看>>
控件坐标位置自适应算法
查看>>
自动恒温灌溉组态软件
查看>>