热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

GolangRPC(一):golang中的rpc实现

标砖库提供了netrpc包用来实现基础的rpc调用。netrpc库使用encodinggob进行编解码,支持tcp或http数据传输方式,由于其他语言

标砖库提供了 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 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包为我们提供了注册服务和处理请求的一系列方法,结合本案例实现注册及处理逻辑,如下所示:

// 调用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.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

在这里插入图片描述

四、注册服务及监听请求 - 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; sname// Install the methodss.method &#61; suitableMethods(s.typ, true)if len(s.method) &#61;&#61; 0 {str :&#61; ""// To help the user, see if a pointer receiver would work.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

也就是客户端调用的第一个参数。


推荐阅读
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 如何实现织梦DedeCms全站伪静态
    本文介绍了如何通过修改织梦DedeCms源代码来实现全站伪静态,以提高管理和SEO效果。全站伪静态可以避免重复URL的问题,同时通过使用mod_rewrite伪静态模块和.htaccess正则表达式,可以更好地适应搜索引擎的需求。文章还提到了一些相关的技术和工具,如Ubuntu、qt编程、tomcat端口、爬虫、php request根目录等。 ... [详细]
  • 本文介绍了解决Netty拆包粘包问题的一种方法——使用特殊结束符。在通讯过程中,客户端和服务器协商定义一个特殊的分隔符号,只要没有发送分隔符号,就代表一条数据没有结束。文章还提供了服务端的示例代码。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文介绍了Windows操作系统的版本及其特点,包括Windows 7系统的6个版本:Starter、Home Basic、Home Premium、Professional、Enterprise、Ultimate。Windows操作系统是微软公司研发的一套操作系统,具有人机操作性优异、支持的应用软件较多、对硬件支持良好等优点。Windows 7 Starter是功能最少的版本,缺乏Aero特效功能,没有64位支持,最初设计不能同时运行三个以上应用程序。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
author-avatar
杨仕卫123
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有