golang之RPC学习

rpc

什么是rpc

我们知道Socket和HTTP采用的是类似"信息交换"模式,即客户端发送一条信息到服务端,然后(一般来说)服务器端都会返回一定的信息以表示响应。客户端和服务端之间约定了交互信息的格式,以便双方都能够解析交互所产生的信息。但是很多独立的应用并没有采用这种模式,而是采用类似常规的函数调用的方式来完成想要的功能。

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

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

rpc可以做什么

API、进程间通信。主要用于分布式应用间通信

RPC和rest风格api有什么不同?

本质区别就是REST是使用http协议,相比RPC的实现协议传输会传更多的内容,但是两个可以做相同的事情。

RPC工作原理

在这里插入图片描述
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:

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

go语言中的rpc

Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。

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

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

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

func (t *T) MethodName(argType T1, replyType *T2) error

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

任何的RPC都需要通过网络来传递数据,Go RPC可以利用HTTP和TCP来传递数据,利用HTTP的好处是可以直接复用net/http里面的一些函数。详细的例子请看下面的实现

rpc版的"hello world"

初级

  • 服务端:
package main

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

type HelloServer struct {}

func (p *HelloServer) Hello(request string, reply *string)  error {
	*reply = "Hello " + request
	return nil
}

func main(){
	 // 将HelloServer注册为一个RCP服务
	rpc.RegisterName("HelloServer", new(HelloServer))

	listener, err := net.Listen("tcp", ":1234")
	if (err != nil){
		log.Fatal("ListenTCP error:" , err)
	}

	conn, err := listener.Accept()
	if (err != nil){
		log.Fatal("Accept error:" , err)
	}

	rpc.ServeConn(conn)
}

  • 客户端:
package main

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

func main(){
	client, err := rpc.Dial("tcp", ":1234")
	if (err != nil){
		log.Fatal("Dialing error:" , err)
	}

	var reply string
	err = client.Call("HelloServer.Hello", "hello", &reply)
	if (err != nil){
		log.Fatal("Calling error:" , err)
	}

	fmt.Println(reply)
}

进化【等待研究】

  • 制定接口规范
package api

import "net/rpc"

const HelloServiceName = "path/to/pck.HelloServer"

type HelloServiceInterface = interface {
	Hello(request string, reply *string) error
}

/// 注册
func RegisterHelloServer(svc HelloServiceInterface) error  {
	return rpc.RegisterName(HelloServiceName, svc)
}

type HelloServiceClient struct {
	*rpc.Client
}



var _ HelloServiceInterface = (*HelloServiceClient)(nil) // HelloServiceClient 必须实现HelloServiceInterface方法
func (p *HelloServiceClient) Hello(request string, reply *string) error {
	return p.Client.Call(HelloServiceName+".Hello", request, reply)
}

func DialHelloService(netWork, address string)(*HelloServiceClient, error)  {
	c, err := rpc.Dial(netWork, address)
	if(err != nil){
		return nil, err
	}

	return &HelloServiceClient{Client:c}, nil
}

  • 客户端:
package main

import (
	"fmt"
	"log"
	"test/api"
)

func main(){
	// 拨号
	client, err := api.DialHelloService("tcp", ":1234")
	if (err != nil){
		log.Fatal("Dialing error:" , err)
	}

	var reply string
	err = client.Hello("hello", &reply)
	if (err != nil){
		log.Fatal("Calling error:" , err)
	}

	fmt.Println(reply)
}

  • 服务端:
package main

import (
	"log"
	"net"
	"net/rpc"
	"test/api"
)

type HelloServer struct {}

func (p *HelloServer) Hello(request string, reply *string)  error {
	*reply = "Hello " + request
	return nil
}

func main(){
	 // 将HelloServer注册为一个RCP服务
	api.RegisterHelloServer(new(HelloServer))

	listener, err := net.Listen("tcp", ":1234")
	if (err != nil){
		log.Fatal("ListenTCP error:" , err)
	}

	for{
		conn, err := listener.Accept()
		if (err != nil){
			log.Fatal("Accept error:" , err)
		}

		go rpc.ServeConn(conn)
	}
}

例子2:Http RPC

  • 服务端:
package main

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

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Arith int

// Arith.Multiply
func (t *Arith) Multiply(args *Args, reply *int) error {
	*reply = args.A * args.B
	fmt.Println(reply,"乘法执行了")
	return nil
}
//Arith.Divide
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(quo,"除法执行了")
	return nil
}



func main(){
	rpc.Register(new(Arith)) //注册一个Arith的RPC服
	rpc.HandleHTTP()  // 把该服务注册到了HTTP协议, 然后我们就可以利用http的方式来传递数据了

	/*err := http.ListenAndServe(":1234", nil)
	if err != nil {
		log.Fatal(err.Error())
	}
    */
    lister,err:=net.Listen("tcp","127.0.0.1:1234")
	if err != nil {
		log.Fatal(err.Error())
	}
	http.Serve(lister,nil)
}

  • 客户端:
package main

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

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}


func main(){
	client,err:=rpc.DialHTTP("tcp","127.0.0.1:1234")
	if err!=nil{
		log.Fatal("dialing:", err)
	}

	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith error:", 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 error:", err)
	}
	fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

}

例子3:tcp RPC

  • 服务端
package main

import (
	"errors"
	"fmt"
	"net"
	"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("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main() {

	arith := new(Arith)
	rpc.Register(arith)

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

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

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

}

func checkError(err error) {
	if err != nil {
		fmt.Println("Fatal error ", err.Error())
		os.Exit(1)
	}
}
  • 客户端:
package main

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

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}


func main(){
	//client,err:=rpc.DialHTTP("tcp","127.0.0.1:1234")   --- http fangshi
	// client, err := jsonrpc.Dial("tcp", service)  ----  JSON RPC
	client, err := rpc.Dial("tcp", "127.0.0.1:1234")  //tcp方式
	if err!=nil{
		log.Fatal("dialing:", err)
	}

	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith error:", 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 error:", err)
	}
	fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

}

例子4:json RPC

package main

import (
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"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("divide by zero")
	}
	quo.Quo = args.A / args.B
	quo.Rem = args.A % args.B
	return nil
}

func main() {

	arith := new(Arith)
	rpc.Register(arith)

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

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

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

}

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

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

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}


func main(){
	//client,err:=rpc.DialHTTP("tcp","127.0.0.1:1234")   --- http fangshi
	 client, err := jsonrpc.Dial("tcp", "127.0.0.1:1234") // ----  JSON RPC
	//client, err := rpc.Dial("tcp", "127.0.0.1:1234")  //tcp方式
	if err!=nil{
		log.Fatal("dialing:", err)
	}

	args := Args{17, 8}
	var reply int
	err = client.Call("Arith.Multiply", args, &reply)
	if err != nil {
		log.Fatal("arith error:", 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 error:", err)
	}
	fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值