RPC是什么?Go语言使用它

RPC是远程过程调用协议的缩写,简单说就是他可以在本地网络下,调用远方的一个进程中的方法。比如我在广州自己的电脑上运行自己写的一个程序,该程序使用了RPC协议,然后就可以调用远在北京的一个程序,该程序当然也使用了RPC协议。这样的话就可以屏蔽了语言的差异,比如本地可以用C或Python语言写,远端可以使用JAVA或GO语言写。

他们之间使用的是网络通信,为了保证数据传输的稳定性和安全性,使用TCP进行连接,所以RPC算是位于传输层和应用层之间。

我们进行网络编程一般使用Socket编程,相比TCP编程,Socket是全双工的长连接,也就是说服务端和客户端之间可以同时收发,并且相比TCP编程,客户端和服务端建立连接之后就一直连着,除非超时或主动断开,这样的话就可以节约每次通信的都要建立连接而浪费资源。RPC编程时使用了Socket。

Socket服务端编程时一般先创建监听端口,然后建立连接,最后进行读写操作来收发信息,而客户端直接连接服务端,然后就可以进行读写操作来收发信息。RPC编程和Socket编程类似。

  • RPC的服务端首先注册rpc服务,然后创建socket监听端口,然后建立连接,最后绑定rpc服务
  • RPC的客户端直接连接服务端,然后就可以直接调用远程的方法了

Golang实现RPC编程

当然只有服务端注册了的方法,客户端才能使用注册了的方法。同时,对于服务端要注册的方法也有硬性要求,这里使用Golang语言实现:

  • 1)注册的方法必须是导出的,也就是包外可见。在Golang中首字母大写,java中使用public关键字
  • 2)注册的方法必须有两个参数,都是导出类型或内建类型。第一个参数是客户端传来的值,第二个参数是返回客户端的值
  • 3)注册的方法的第二个参数必须是指针
  • 4)注册的方法只有一个error接口类型的返回值
// 服务端
package main

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

type World struct {  // 使用结构体来模拟类的概念
}

func (this *World) HelloWorld(name string, respon *string) error {  // 关联了结构体,它就是一个方法了,等下要注册这个方法,注意这个方法的参数要符合上面说的要求
	*respon = "要是能重来,我要选" + name
	return nil  // 返回空
}

func main() {
	// func RegisterName(name string, rcvr interface{}) error {}  // 注册方法原型
    // 第一个参数随意指定服务名,客户端要使用到,第二个参数传递对象,结构体只是定义,没有实例化,所以要使用new()实例化它
	if err := rpc.RegisterName("Sayhello", new(World)); err != nil { // 注册rpc服务
		log.Println("注册失败", err)
	}
	listen, err := net.Listen("tcp", "127.0.0.1:20000") // 创建socket监听端口
	if err != nil {
		log.Println("监听失败", err)
		return
	}
	log.Println("监听中……")
	conn, err := listen.Accept() // 建立连接
	if err != nil {
		log.Println("连接失败", err)
		return
	}
	log.Println("已有一个连接过来了")
	defer log.Println("关闭连接成功")
	defer listen.Close()
	defer conn.Close()
	rpc.ServeConn(conn) // 绑定rpc服务

}
// 客户端
package main

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

func main() {
	rpc_cli, err := rpc.Dial("tcp", "127.0.0.1:20000")  // 使用rpc包的Dial方法连接
	if err != nil {
		log.Println("连接失败", err)
	}
	defer rpc_cli.Close()
	var reply string
    // 连接之后要使用Call方法调用
    // 第一个参数是(服务名.方法名),所以是Sayhello.HelloWorld
    // 第二个参数是要传递过去的参数,为字符型
    // 第三个参数是接受返回值的,注意是指针类型
	if err := rpc_cli.Call("Sayhello.HelloWorld", "李白", &reply); err != nil {
		log.Println(err)
	}
	fmt.Println(reply)
}

先运行服务端,然后运行客户端,就会看到如下面:
在这里插入图片描述
它的通信过程中,里面传的是什么?

使用netcat工具查看,在Linux中该工具叫做nc,在Windows中要自己寻找下载。在Windows中使用该工具进行监听,启动命令netcat.exe -l 127.0.0.1 -p 20000它就开始监听,-l 指定地址,也可以不写具体IP默认本地,但要写参数,-p指定端口,必须写,然后运行刚刚写的客户端代码,就可以看到如下,乱码了:在这里插入图片描述
在不同系统中,由于系统的设计不同,有些系统认为00010101 00101010是2142,但有些系统认为这是4221,所以为了屏蔽这种差异,使用序列化的方法解决。将一个对象序列化成字符串,在传输过程中转为比特流,到另一端后,将比特流转为字符串,然后将字符串反序列化成对象,该对象和本地创建的对象就没有区别了。在上面的代码中Golang使用了自己特有的序列化方法(又称gob),对对象进行了序列化和反序列化,由于是特有的,所以其他语言不能解析,而netcat工具不是Golang语言写的,所以你就会看到上面乱码了

使用其他通用的序列化、反序列化方法

上面写RPC时使用的是net/rpc这个包,现在使用net/rpc/jsonrpc这个包编程,用法一致

客户端的修改

现在我只修改客户端的代码

// 客户端
package main

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

func main() {
    rpc_cli, err := jsonrpc.Dial("tcp", "127.0.0.1:20000")
	if err != nil {
		log.Println("连接失败", err)
	}
	defer rpc_cli.Close()
	var reply string
	if err := rpc_cli.Call("Sayhello.HelloWorld", "李白", &reply); err != nil {
		log.Println(err)
	}
	fmt.Println(reply)
}

同样先运行netcat工具,再运行客户端,结果如下,英文正常了,但中文乱码:
在这里插入图片描述
为什么中文乱码? 因为我的Windows的编码是GBK(中文2字节),而程序使用的是UTF-8(中文3字节),对于英文和一些半角符号,他们在两种编码中都是在最低8位上,所以他们没有乱码,但中文乱码。我使用了我的远程阿里云主机Ubuntu系统,使用nc监听,该工具Linux自带,命令nc -l -p 20000开启监听(记得阿里云的防火墙策略开放该端口),然后修改代码中的地址指向远程主机,最后开启上面的客户端,在远程主机中显示结果为:
在这里插入图片描述
因此可以确定客户端就是给服务端发了这么一个东西。

服务端的修改

客户端修改了序列化的方法,不再是Golang自带的序列化方法了,那么服务端就不能正常和客户端通信了,所以要修改服务端的代码。只需在最后一行的rpc.ServeConn(conn)改为jsonrpc.ServeConn(conn),然后导包即可。

骚方法测试。。。。。

已经知道了它发送的就是=={“method”:“Sayhello.HelloWorld”,“params”:[“李白”],“id”:0}==这么一段字符串,那么使用netcat再次模拟客户端,使用命令echo '{"method":"Sayhello.HelloWorld","params":["李白"],"id":0}' |.\netcat.exe 127.0.0.1 20000,echo向终端显示信息,但使用了管道符就会发送向后面的命令,后面的命令就是模拟客户端的方式,向这个地址发送信息并持续监听。由于在Windows中操作,可以看到中文有乱码了,但那串乱码就是返回的字符要是能重来我要选李白
在这里插入图片描述

返回异常

在上面的图片中,也可以知道服务器返回的数据是这样的:{“id”:0,“result”:“要是能重来我要选李白”,“error”:null}

error参数也是注册的方法返回的值,同时返回的是nil

func (this *World) HelloWorld(name string, respon *string) error {
	*respon = "要是能重来,我要选" + name
	return nil
}

如果我们返回的不是nil呢

func (this *World) HelloWorld(name string, respon *string) error {
	*respon = "要是能重来,我要选" + name
	return errors.New("sorry I love you")
}

在这里插入图片描述
可以看到result参数为空了,而error参数为异常值了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值