Go语言中的RPC编程

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

上面就是关于RPC定义的介绍,其实RPC就是想实现函数调用模式的网络化。客户端就可以像调用本地函数一样,然后客户端把这些参数打包后通过网络传递到服务器端,服务器端解包到处理过程中执行,然后执行的结果反馈给客户端。

具体来说,在运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

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

Go RPC

Go在标准库中已经支持了RPC,而且支持三个级别的RPC:TCP,HTTP,JSONRPC。但Go中的RPC又都是通过Gob来统一编码的,因此它只能支持在Go语言开发的服务器和客户端之间的交互。
在被远程访问之前,还需要满足一些条件:

1)函数必须是导出的(首字母大写)
2)必须有两个导出类型的参数
3)第一个参数用来接收,第二个参数是返回给客户端的参数,并且第二个参数必须是指针类型
4)函数还要有一个返回值error

格式如下:

func (t *T) MethodName(arg1 argType, arg2 *argType) error

HTTP RPC

我们来看一个简单的HTTP的服务器端代码:

package main

import (
    "errors"
    "log"
    "net/http"
    "net/rpc"
    "os"
)

type Args struct {
    A, B int
}

type Quotient struct {
    Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
    *reply = args.A * args.B
    return nil
}

func (t *Arith) Divide(args *Args, quo *Quotient) error {
    if args.B == 0 {
        return errors.New("The divisor cannot be zero.")
    }
    quo.Quo = args.A / args.B
    quo.Rem = args.A % args.B
    return nil
}

func main() {
    arith := new(Arith)
    rpc.Register(arith)
    rpc.HandleHTTP()

    err := http.ListenAndServe(":8000", nil)
    if err != nil {
        log.Fatalln(err.Error())
        os.Exit(1)
    }
}

我们首先注册了一个Arith的RPC的服务,然后通过rpc.HandleHTTP函数把该服务注册到HTTP协议上,最后就可以利用HTTP的方式来传递数据。
我们再来看看客户端代码:

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ", os.Args[0], "server")
        os.Exit(1)
    }
    serverAddress := os.Args[1]

    client, err := rpc.DialHTTP("tcp", serverAddress+":8000")
    if err != nil {
        log.Fatal("Dial Wrong: ",err)
    }
    args := Args{17, 8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal("Arith Wrong: ", err)
    }
    fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, &reply)

    var quot Quotient
    err = client.Call("Arith.Divide", args, &quot)
    if err != nil {
        log.Fatal("Arith Wrong: ", err)
    }
    fmt.Printf("Arith: %d/%d=%d and %d\n", args.A, args.B, quot.Quo, quot.Rem)
}

程序中的关键就在于这个Call函数,它有三个参数,第一个是要调用的函数的名字,第二个要传递的参数,第三个是要返回的参数(必须是指针类型


TCP RPC

我们仍使用前面的内容来构建基于TCP的RPC的服务器端:

func main() {
    arith := new(Arith)
    rpc.Register(arith)

    tcpAddr, err := net.ResolveTCPAddr("tcp", "8000")
    checkError(err)
    
    listener, err := net.Listen("tcp", tcpAddr)
    checkError(err)
    
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        rpc.ServeConn(conn)
    }
}

func checkError(err error) {
    if err != nil {
        fmt.Println("Wrong: ", err.Error())
        os.Exit(1)
    }
}

在这里使用了TCP协议,然后需要自己连接控制,当有客户端连接上来后,需要把这个连接交给RPC来处理。
相应的RPC客户端代码如下:

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ", os.Args[0], "server:port")
        os.Exit(1)
    }
    server := os.Args[1]
    client, err := rpc.Dial("tcp", server)
    if err != nil {
        log.Fatal("Dial Wrong: ", err)
    }
    args := Args{17, 8}
    var reply int
    err = client.Call("Arith.Multiply", args, &reply)
    if err != nil {
        log.Fatal("Arith Wrong: ", err)
    }
    fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, &reply)

    var quot Quotient
    err = client.Call("Arith.Divide", args, &quot)
    if err != nil {
        log.Fatal("Arith Wrong: ", err)
    }
    fmt.Printf("Arith: %d/%d=%d and %d\n", args.A, args.B, quot.Quo, quot.Rem)
}

和HTTP的客户端代码相比,唯一的区别就在于这里使用的是Dial(tcp)函数,而前者使用的是DialHTTP


JSON RPC

JSON RPC采用的是json编码的方式,但其操作方式是与上面介绍的RPC方式是基本一致的,和TCP所创建的客户端和服务器端唯一的不同在于,JSON RPC在这里是用的连接方式是jsonrpc.ServerConn(conn)其余的内容完全一致。
代码在这里不再赘述。


RPC接口

Go语言的net/rpc很灵活,它在传输前后实现了编码解码器的接口定义。
RPC提供的编码解码器接口如下:

type ClientCodec interface {
    WriteRequest(*Request, interface{}) error
    ReadResponseHeader(*Response) error
    ReadResponseBody(interface{}) errror
    Close() error
}

type ServerCodes interface {
    ReadRequestHeader(*Request) error
    ReadRequestBody(interface{}) error
    WriteResponse(*Request, interface{}) error
    Close() error
}

我们可以通过定义很容易的看出来两个编码器的运行过程。
接口ClientCodec定义了RPC客户端如何在一个RPC会话中发送请求和读取响应。
接口ServerCodec定义了RPC服务器端如何在一个RPC会话中接收请求并发送响应。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容