Go语言实现RPC

栏目: 服务器 · 发布时间: 6年前

RPC定义,来源于百度百科

  • RPC(Remote Procedure Call)— 远程过程调用 ,它是一种通过 网络 从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。 RPC协议 假定某些 传输协议 的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI 网络通信 模型中,RPC跨越了 传输层应用层 。RPC使得开发包括网络 分布式 多程序在内的应用程序更加容易。

  • RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复 信息 ,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。

  • 有多种 RPC模式和执行。最初由 Sun 公司提出。IETF ONC 宪章重新修订了 Sun 版本,使得 ONC RPC 协议成为 IETF 标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算 环境 (DCE)。

  • 个人的理解:不用管什么底层网络技术的协议,是一种通过网络从计算机程序上请求服务,通俗一点,我们写代码,要在一个地方,比如安卓,就需要在一个工程里面,才可以调用到其他的程序代码执行的过程。Go语言提供RPC支持使得开发网络分布式多程序在内的应用程序更加的 easy

RPC工作流程图

Go语言实现RPC

图片来源于gitHub

  • 1.调用客户端句柄;执行传送参数
  • 2.调用本地系统内核发送网络消息
  • 3.消息传送到远程主机
  • 4.服务器句柄得到消息并取得参数
  • 5.执行远程过程
  • 6.执行的过程将结果返回服务器句柄
  • 7.服务器句柄返回结果,调用远程系统内核
  • 8.消息传回本地主机
  • 9.客户句柄由内核接收消息
  • 10.客户接收句柄返回的数据

Go语言提供对RPC的支持: HTTP、TCP、JSPNRPC ,但是在 GoRPC 是独一无二的,它采用了 GoLang Gob 编码,只能支持 Go 语言!

  • GoLang Gob:是Golang包自带的一个数据结构序列化的编码/解码工具。编码使用Encoder,解码使用Decoder。一种典型的应用场景就是RPC(remote procedure calls)。

HTTP RPC Demo

  • 服务端的代码
package main

import (
    "fmt"
    "net/rpc"
    "net/http"
    "errors"
)
func main() {
     rpcDemo()
}
type Arith int
func rpcDemo() {
    arith:=new(Arith)
    //arith=== 0xc04204e090
    fmt.Println("arith===",arith)

    rpc.Register(arith)
    //HandleHTTP将RPC消息的HTTP处理程序注册到Debug服务器
    //DEFAUTUPCPATH和Debug调试路径上的调试处理程序。
    //仍然需要调用http.Services(),通常是在GO语句中。
    rpc.HandleHTTP()
    err:=http.ListenAndServe(":1234",nil)
    if err != nil {
        fmt.Println("err=====",err.Error())
    }
}
type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

//函数必须是导出的(首字母大写)
//必须有两个导出类型的参数,
//第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的
//函数还要有一个返回值error
func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    fmt.Println("这个方法执行了啊---嘿嘿--- Multiply ",reply)
    return nil
}
func (t *Arith) Divide(args *Args, quo *Quotient) error {
    if args.B == 0 {
        return errors.New("divide by zero")
    }
    quo.Quo = args.A / args.B
    quo.Rem = args.A % args.B
    fmt.Println("这个方法执行了啊---嘿嘿--- Divide quo==",quo)
    return nil
}
  • Go RPC 的函数只有符合四个条件才能够被远程访问,不然会被忽略
    error
    
func (t *T) MethodName(argType T1, replyType *T2) error
  • T、T1和T2类型必须能被 encoding/gob 包编解码。

  • 客户端的代码

package main

import (
    "log"
    "fmt"
    "os"
    "net/rpc"
    "strconv"
)

type ArgsTwo struct {
    A, B int
}

type QuotientTwo struct {
    Quo, Rem int
}

func main() {
    // 如果什么都不输入的话 ,就是以下的这个值
    //os***************** [C:\Users\win7\AppData\Local\Temp\go-build669605574\command-
    //line-arguments\_obj\exe\GoRPCWeb.exe 127.0.0.1] **********************

    fmt.Println("os*****************",os.Args,"**********************")
    if len(os.Args) != 4 { //   todo  第二个地址是  我们本地的地址
        fmt.Println("老子要退出了哦 傻逼 一号start--------》》》", os.Args[0], "《《《---------------server  end")
        os.Exit(1)
    }else{
        fmt.Println("长度是多少 "+strconv.Itoa( len(os.Args))+"才是准确的长度 哦---》")
    }
    //获取输入的地址是获取输入得 os 数据的 第一个位置的值
    serverAddress := os.Args[1]
    fmt.Println("severAddress==",serverAddress)
    // //DelayHTTP在指定的网络地址连接到HTTP RPC服务器
    ///在默认HTTP RPC路径上监听。
    client, err := rpc.DialHTTP("tcp", serverAddress)
    if err != nil {
        log.Fatal("发生错误了 在这里地方  DialHTTP", err)
    }
    i1,_:=strconv.Atoi( os.Args[2])
    i2,_:=strconv.Atoi( os.Args[3])
    args := ArgsTwo{i1, i2}
    var reply int
    //调用调用命名函数,等待它完成,并返回其错误状态。
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal("Call Multiply  发生错误了哦   arith error:", err)
    }
    fmt.Printf("Arith 乘法: %d*%d=%d\n", args.A, args.B, reply)

    var quot QuotientTwo
    //调用调用命名函数,等待它完成,并返回其错误状态。
    err = client.Call("Arith.Divide", args, ")
    if err != nil {
        log.Fatal("arith error:", err)
    }
    fmt.Printf("Arith 除法取整数: %d/%d=%d 余数 %d\n", args.A, args.B, quot.Quo, quot.Rem)

}
Go语言实现RPC

运行的结果图

E:\new_code\GoDemo\web_server>go run GoRPCWeb8.go 127.0.0.1:1234  20 3
os***************** [C:\Users\win7\AppData\Local\Temp\go-build011170718\command-
line-arguments\_obj\exe\GoRPCWeb8.exe 127.0.0.1:1234 20 3] *********************
*
长度是多少 4才是准确的长度 哦---》
severAddress== 127.0.0.1:1234
Arith 乘法: 20*3=60
Arith 除法取整数: 20/3=6 余数 2
  • go run GoRPCWeb8.go 127.0.0.1:1234 20 3
    • go run 运行的命令
    • GoRPCWeb8.go :这是文件的名称,需要到指定的目录下启动 cmd
    • 127.0.0.1:1234 : ip地址和端口号
    • 20 3 这是客服端传入的值:一个除数,一个被除数,传入到服务器做乘法运算 乘法: 20*3=60 和除法取整数: 20/3=6 余数 2 ,怎么做的,客户端不关心,给到服务端去完成
  • os.Args[0] = [C:\Users\win7\AppData\Local\Temp\go-build011170718\command- line-arguments\_obj\exe\GoRPCWeb8.exe 127.0.0.1:1234 20 3]
if len(os.Args) != 4 { //   todo  第二个地址是  我们本地的地址
        fmt.Println("老子要退出了哦 傻逼 一号start--------》》》", os.Args[0], "《《《---------------server  end")
        os.Exit(1)
    }else{
        fmt.Println("长度是多少 "+strconv.Itoa( len(os.Args))+"才是准确的长度 哦---》")
    }

TCP RPC Demo

  • 基于TCP协议实现的RPC,服务端的代码
package main

import (
    "fmt"
    "net/rpc"
    "net"
    "os"
    "errors"
)

func init() {
    fmt.Println("基于TCP协议实现的RPC,服务端的代码如下")
}
type Me struct {
    A,B int
}
type You struct {
    CC,D int
}
type Num int

/*
Go RPC的函数只有符合下面的条件才能够被远程访问,不然会被忽略
1 函数必须是导出的(首字母大写)
2 必须有两个导出类型的参数
3 第一个参数是接受的参数,第二个参数是返回给客户端的参数,第二个参数必须是指正类型的
4 函数还必须有一个返回值error
 */
func (n *Num) M(args *Me,reply *int) error  {
    *reply=args.A * args.B
    return nil
}


func (n *Num) F(args * Me,u *You ) error  {
    if  args.B==0{
        return errors.New("输入不能够为0 被除数")
    }
    u.D=args.A/args.B
    u.CC=args.A % args.B
    return nil
}



func main() {
    //内建函数new本质上说跟其它语言中的同名函数功能一样:new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。用Go的术语说,它返回了一个指针,指向新分配的类型T的零值。有一点非常重要:
    //new返回指针。
    num:=new(Num)
    rpc.Register(num)
    //ResolveTCPAddr返回TCP端点的地址。
    //网络必须是TCP网络名称。
    tcpAddr,err:=net.ResolveTCPAddr("tcp",":1234")

    if err != nil {
        fmt.Println("错误了哦")
        os.Exit(1)
    }
    listener,err:=net.ListenTCP("tcp",tcpAddr)
    for  {
        // todo   需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc 来处理
        conn,err:=listener.Accept()
        if err != nil {
            continue
        }
        rpc.ServeConn(conn)
    }
}
  • 基于TCP客户端代码
package main

import (
    "fmt"
    "os"
    "net/rpc"
    "log"
    "strconv"
)

func main() {
    fmt.Println("客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go")

    if len(os.Args)==4{
        fmt.Println("长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip=",os.Args[1],"嘿嘿,加上后面的被除数os.Args[2]=",os.Args[2],"和除数os.Args[3]=",os.Args[3])
        //os.Exit(1)
    }
    // 获取 ip 地址
    service:= os.Args[1]
    //连接 拨号连接到指定的网络地址的RPC服务器。
    client,err:=rpc.Dial("tcp",service)
    if err!=nil {
        log.Fatal("老子在连接Dial的发生了错误,我要退出了",err)
    }
    num1:=os.Args[2]
    i1,error1:=strconv.Atoi(num1)
    if error1!=nil {
        fmt.Println("自己不知道 自己输入错误了啊 请看error :",error1)
        os.Exit(1)
    }
    num2:=os.Args[3]
    i2,error2:=strconv.Atoi(num2)
    if error2!=nil {
        fmt.Println("自己不知道 自己输入错误了啊 请看error :",error2)
        os.Exit(1)
    }
    aa:=AAA{i1,i2}
    var reply  int
    err1:=client.Call("Num.M",aa,&reply)

    if err1 != nil{
        log.Fatal("我要退出了,因为我在Call的时候发生了 错误",err1)
    }
    fmt.Println("我进行正常结果如下")
    fmt.Printf("Num : %d*%d=%d\n",aa.A,aa.B,reply)

    var bb BDemo
    //调用调用命名函数,等待它完成,并返回其错误状态。
    err= client.Call("Num.F",aa,&bb)
    if err!=nil {
        log.Fatal("我对这个方法发生了过敏的反应 哈哈哈哈  err=====",err)
    }
    fmt.Printf("Num: %d/%d=%d 余数 %d\n",aa.A,aa.B,bb.DD,bb.CC)
    
}


// 定义两个类,那边需要操作的类
type AAA struct {
    A,B int
}
//记住这里不能够大写 两个连着一起大写 有点意思
//reading body gob: type mismatch: no fields matched compiling decoder for  DDDD
//  todo 为啥 第二个参数  只要是两个连在一起的DDDD   就会报错   reading body gob: type mismatch: no fields matched compiling decoder for
type BDemo struct {
    DD, CC int
}
  • 运行结果图

    Go语言实现RPC

    结果图

E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234  20 1
客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go
长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip= 127.0.0.1:1234 嘿嘿,加上后面
的被除数os.Args[2]= 20 和除数os.Args[3]= 1
我进行正常结果如下
Num : 20*1=20
Num: 20/1=0 余数 0

E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234  20 2
客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go
长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip= 127.0.0.1:1234 嘿嘿,加上后面
的被除数os.Args[2]= 20 和除数os.Args[3]= 2
我进行正常结果如下
Num : 20*2=40
Num: 20/2=0 余数 0

E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234  20 3
客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go
长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip= 127.0.0.1:1234 嘿嘿,加上后面
的被除数os.Args[2]= 20 和除数os.Args[3]= 3
我进行正常结果如下
Num : 20*3=60
Num: 20/3=0 余数 2
  • 在定义 BDemo 的时候, 如果定义的 DD, CC int 和服务端不一样,就会报错 reading body gob: type mismatch: no fields matched compiling decoder for ,其实发现好多种情况,也会出现这种错误,但是目前不知道为啥会这样,后续,等源码深入一点,回来看这个问题 todo2018/07/19
  • 这种 TCP 方式和上面的 HTTP 不同的是
    • HTTP:指定的网络地址连接到HTTP RPC服务器
//DelayHTTP在指定的网络地址连接到HTTP RPC服务器
    ///在默认HTTP RPC路径上监听。
    client, err := rpc.DialHTTP("tcp", serverAddress)
  • TCP:指定的网络地址连接到HTTP RPC服务器
client,err:=rpc.Dial("tcp",service)

JSON RPC

  • JSON RPC 是数据编码采用了 JSON ,而不是 gob 编码,其他和上面介绍的 RPC 概念一模一样的。

  • 服务端的代码如下

package main

import (
    "fmt"
    "net/rpc"
    "net"
    "net/rpc/jsonrpc"
)

//使用Go提供的json-rpc 标准包
func init() {
    fmt.Println("JSON RPC 采用了JSON,而不是 gob编码,和RPC概念一模一样,")
}
type Work struct {
    Who,DoWhat string
}

type DemoM string

func (m *DemoM) DoWork(w *Work,whoT *string) error  {
    *whoT="是谁:"+w.Who+",在做什么---"+w.DoWhat
    return nil
}

func main() {
    str:=new(DemoM)
    rpc.Register(str)

    tcpAddr,err:=net.ResolveTCPAddr("tcp",":8080")
    if  err!=nil{
        fmt.Println("大哥发生错误了啊,请看错误 ResolveTCPAddr err=",err)
    }

    listener,err:=net.ListenTCP("tcp",tcpAddr)
    if err!=nil {
        fmt.Println("发生错误了--》err=",err)
    }

    for  {
         conn,err:= listener.Accept()
        if err!=nil {
            continue
        }
        jsonrpc.ServeConn(conn)

    }

}
  • 客户端的代码
package main

import (
    "fmt"
    "os"
    "net/rpc/jsonrpc"
    "log"
)

func main() {
    fmt.Println("这是客户端,用来启动,通过命令行来启动")

    fmt.Println("客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go")

    if len(os.Args)==4{
        fmt.Println("长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip=",os.Args[1],"嘿嘿,加上后面的被除数os.Args[2]=",os.Args[2],"和除数os.Args[3]=",os.Args[3])
        //os.Exit(1)
    }

     service:=os.Args[1]
     client,err:=jsonrpc.Dial("tcp",service)
    if err != nil {
        log.Fatal("Dial 发生了错误了哦 错误的信息为   err=",err)
    }
    send:=Send{os.Args[2],os.Args[3]}
    var  resive  string
    err1:=client.Call("DemoM.DoWork",send,&resive)
    if err1!=nil {
        fmt.Println("shiming call error    ")
        fmt.Println("Call 的时候发生了错误了哦  err=",err1)
    }
    fmt.Println("收到信息了",resive)




}
// 类可以不一样 但是 Who 和DoWhat 要必须一样  要不然接收到不到值,等我在详细的了解了 才去分析下原因  感觉有点蒙蔽啊
type Send struct {
    Who, DoWhat string
}
  • 运行的结果如下

    Go语言实现RPC

    运行结果

E:\new_code\GoDemo\web_server>go run GoJSONRPCWeb11.go 127.0.0.1:8080  shiming g
ongzuo
这是客户端,用来启动,通过命令行来启动
客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go
长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip= 127.0.0.1:8080 嘿嘿,加上后面
的被除数os.Args[2]= shiming 和除数os.Args[3]= gongzuo
收到信息了 是谁:shiming,在做什么---gongzuo

E:\new_code\GoDemo\web_server>go run GoJSONRPCWeb11.go 127.0.0.1:8080  shiming q
iaodaima
这是客户端,用来启动,通过命令行来启动
客户端 其他端 去调用的地方  对应的例子是 GoTCPRPC9.go
长度必须等于4,因为呢,你输入的肯定是一个ip的地址ip= 127.0.0.1:8080 嘿嘿,加上后面
的被除数os.Args[2]= shiming 和除数os.Args[3]= qiaodaima
收到信息了 是谁:shiming,在做什么---qiaodaima
  • os.Args 是一个数组 var Args []string ,通过输入获取到,然后把这个客户端输入的内容传送到服务端,服务端做些操作,然后在返回给客户端
  • Go 已经提供了 RPC 良好的支持,通过 HTTP TCP JSONRPC 的实现,可以很方便开发分布式的 Web 应用,但是我目前还不会,在学习中。遗憾的是 Go 没有提供 SOAP RPC 的支持~~~

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

社交网站界面设计

社交网站界面设计

Christian Crumlish、Erin Malone / 樊旺斌、师蓉 / 机械工业出版社 / 2010-9-1 / 69.00元

《社交网站界面设计》提供100多种模式、原则以及最佳实践,并针对在设计社交网站时经常遇到的问题给出明确建议。本书将提供给你培养用户交互习惯和构建社区最具价值的参考。 本书作者将与你分享难得的经验,教会你平衡各种不同的因素,并与你的用户共同构建和谐健康的网络社区。 本书教会你 掌握创建任何网站时都会用到的原则 学习基本设计模式,以便向现有的网站中添加新的社交组件 学会在......一起来看看 《社交网站界面设计》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器