学习go rpc——示例

通过学习和试验,感觉go的rpc非常的方便易用,下面就将学习的过程总结一下。

1. go rpc简介

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

Go语言标准库能够自带一个rpc框架还是非常给力的,这可以很大程度的降低写后端网络通信服务的门槛,特别是在大规模的分布式系统中,rpc基本是跨机器通信的标配。rpc能够最大程度屏蔽网络细节,让开发者专注在服务功能的开发上面。Go标准包支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。

Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下:

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

举个例子,正确的RPC函数格式如下:

func (t *T) GoRPCMethodName(argType T1, returnType *T2) error

T、T1和T2类型必须能被encoding/gob包编解码。

任何的RPC都需要通过网络来传递数据,Go RPC可以利用HTTP和TCP来传递数据,利用HTTP的好处是可以直接复用net/http里面的一些函数。下面以最简单的示例分别进行说明。环境所限,以下代码我都是将服务器端跑在win7上,客户端跑在centos6.4虚拟机上。

2. HTTP RPC

服务器端示例代码:

package main

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

var S string

type MyRPC int

func (r *MyRPC) HelloRPC(S string, reply *string) error {
	fmt.Println(S)
	*reply = "This Server. Hello Client RPC."
	return nil
}

func main() {
	r := new(MyRPC)
	rpc.Register(r)
	rpc.HandleHTTP()
	err := http.ListenAndServe("172.16.34.222:1234", nil)
	if err != nil {
		fmt.Println("in main", err.Error())
	}
}



对象r是为了方便调用HelloRPC方法,参数S接收客户端传过来的参数,reply声明为指针类型,才能传递到客户端。编辑的时候,将ip改成自己的ip就可以了。服务器端通过rpc.HandleHTTP函数把该服务注册到HTTP协议上,然后就可以利用http的方式来传递数据了。

客户端示例代码:

package main

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

var S string

func main() {
	if len(os.Args) != 2 {
		fmt.Println("usage: ", os.Args[0], "ip:port")
		os.Exit(1)
	}
	addr := os.Args[1]
	client, err := rpc.DialHTTP("tcp", addr)
	if err != nil {
		log.Fatal("dialhttp: ", err)
	}

	var reply *string
	S = "This Client. Hello Server RPC."
	err = client.Call("MyRPC.HelloRPC", S, &reply)
	if err != nil {
		log.Fatal("call hellorpc: ", err)
	}

	fmt.Println(*reply)
}



指定变量S,用来向服务器端传递第一个参数内容,reply声明为指针类型,用来取得服务器端的reply内容。

通过上面的调用可以看到参数和返回值是我们定义的string类型;在服务端把它们当做调用函数的参数,在客户端作为client.Call的第2,3两个参数。客户端最重要的就是Call函数,它有3个参数,第1个是要调用的函数的名字,第2个是要传递的参数,第3个是要返回的参数(指针类型)。

3. TCP RPC

服务器端示例代码:

package main

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

var S string

type MyRPC int

func (r *MyRPC) HelloRPC(S string, reply *string) error {
	fmt.Println(S)
	*reply = "This Server. Hello Client RPC."
	return nil
}

func main() {
	r := new(MyRPC)
	rpc.Register(r)

	tcpAddr, err := net.ResolveTCPAddr("tcp", "172.16.34.222:1234")
	checkError(err)

	listener, err := net.ListenTCP("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("Fatal error ", err.Error())
		os.Exit(1)
	}
}


TCP RPC采用了TCP协议,需要自己控制连接,当有客户端连接上来后,需要把这个连接交给rpc来处理,也就是 rpc.ServeConn(conn)

客户端示例代码:

package main

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

var S string

func main() {
	if len(os.Args) != 2 {
		fmt.Println("usage: ", os.Args[0], "ip:port")
		os.Exit(1)
	}
	addr := os.Args[1]
	client, err := rpc.Dial("tcp", addr)
	if err != nil {
		log.Fatal("dialhttp: ", err)
	}

	var reply *string
	S = "This Client. Hello Server RPC."
	err = client.Call("MyRPC.HelloRPC", S, &reply)
	if err != nil {
		log.Fatal("call hellorpc: ", err)
	}

	fmt.Println(*reply)
}



客户端代码唯一需要改变的地方就是 rpc.DialHTTP("tcp", addr ),变为 rpc.Dial("tcp", addr)

4. JSON RPC

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

服务器端示例代码:

package main

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

var S string

type MyRPC int

func (r *MyRPC) HelloRPC(S string, reply *string) error {
	fmt.Println(S)
	*reply = "This Server. Hello Client RPC."
	return nil
}

func main() {
	r := new(MyRPC)
	rpc.Register(r)

	tcpAddr, err := net.ResolveTCPAddr("tcp", "172.16.34.222:1234")
	checkError(err)

	listener, err := net.ListenTCP("tcp", tcpAddr)
	checkError(err)

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

}

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



与TCP RPC相比就是将rpc.ServeConn(conn)改为jsonrpc.ServeConn(conn)。

客户端示例代码:

package main

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

var S string

func main() {
	if len(os.Args) != 2 {
		fmt.Println("usage: ", os.Args[0], "ip:port")
		os.Exit(1)
	}
	addr := os.Args[1]
	client, err := jsonrpc.Dial("tcp", addr)
	if err != nil {
		log.Fatal("dialhttp: ", err)
	}

	var reply *string
	S = "This Client. Hello Server RPC."
	err = client.Call("MyRPC.HelloRPC", S, &reply)
	if err != nil {
		log.Fatal("call hellorpc: ", err)
	}

	fmt.Println(*reply)
}



其变化也是将以前的rpc.Dial()改为jsonrpc.Dial()。

你可能感兴趣的:(学习go rpc——示例)