Язык Go реализует RPC

задняя часть Go сервер удаленная работа

Определение RPC из энциклопедии Baidu

  • RPC (удаленный вызов процедур) —удаленный вызов процедур, этоИнтернетЗапрашивать услуги из удаленной компьютерной программы, не зная протоколов базовой сетевой технологии.RPC-протоколпредположить некоторыеПротокол передачиСуществование таких протоколов, как TCP или UDP, несет информационные данные для связи между программами. в ИООТелекоммуникациимодель, пролеты RPCтранспортный уровеньиприкладной уровень. RPC позволяет разрабатывать, включая работу в сетираспределенныйПриложения, включающие несколько программ, проще.

  • RPC использует модель клиент/сервер. Запрашивающая сторона — это клиент, а поставщик услуг — сервер. Сначала клиентский вызывающий процесс отправляет сообщение о вызове с параметрами процесса серверному процессу, а затем ожидает ответного сообщения. На стороне сервера процесс остается в спящем режиме до тех пор, пока не придет сообщение о вызове. При поступлении сообщения о вызове сервер получает параметры процесса, вычисляет результат и отправляет ответ.Информация, затем дождаться следующего сообщения о вызове, и, наконец, процесс вызова клиента получает ответное сообщение, получает результат процесса, после чего выполнение вызова продолжается.

  • Существуют различные режимы и реализации RPC. Первоначально предложен Sun Corporation. Хартия IETF ONC пересматривает версию Sun, делая протокол ONC RPC стандартным протоколом IETF. В настоящее время наиболее распространенной моделью и реализацией являются распределенные вычисления на основе открытого программного обеспечения.окрестности(ДСЕ).

  • Личное понимание: какой бы ни был лежащий в основе протокол сетевой технологии, это запрос услуг от компьютерной программы через сеть.Проще говоря, мы пишем код в одном месте, например Android, и он должен быть в проекта до того, как его можно будет вызвать, в процесс выполнения другого программного кода. Язык Go обеспечивает поддержку RPC, упрощая разработку приложений, включая мультипрограммы, распределенные по сети.easy

Схема работы RPC

图片来源于gitHub

  • 1. Вызвать дескриптор клиента, выполнить передачу параметров
  • 2. Вызвать ядро ​​локальной системы для отправки сетевых сообщений.
  • 3. Доставка сообщения на удаленный хост
  • 4. Дескриптор сервера получает сообщение и получает параметры
  • 5. Выполнение удаленных процедур
  • 6. Выполненный процесс возвращает результат дескриптору сервера
  • 7. Дескриптор сервера возвращает результат и вызывает ядро ​​удаленной системы.
  • 8. Сообщение отправляется обратно на локальный хост
  • 9. Дескриптор клиента получает сообщение от ядра
  • 10. Клиент получает данные, возвращенные дескриптором

Язык Go обеспечивает поддержку RPC:HTTP、TCP、JSPNRPC,Но когдаGoсерединаRPCуникален, он используетGoLang GobКодирование, поддерживает только язык Go!

  • GoLang Gob: это инструмент кодирования/декодирования сериализации структуры данных, который поставляется с пакетом Golang. Используйте Encoder для кодирования и Decoder для декодирования. Типичным сценарием приложения является RPC (удаленный вызов процедур).

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, &quot)
	if err != nil {
		log.Fatal("arith error:", err)
	}
	fmt.Printf("Arith 除法取整数: %d/%d=%d 余数 %d\n", args.A, args.B, quot.Quo, quot.Rem)

}

运行的结果图

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

  • RPC на основе протокола TCP, серверный код
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
}

  • График результатов операции
    结果图
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
}





  • Результат запуска следующий
    运行结果
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поддержка~~~