Golang官方提供的net/rpc
库使用encoding/gob
进行编解码,支持TCP或HTTP数据传输方式,由于其它语言不支持gob
编解码方式,因此使用net/rpc
库实现的RPC方法是没有办法进行跨语言调用。
import "net/rpc"
Golang的RPC支持三个级别的RPC,分别是TCP、HTTP、JSONRPC。net/rpc
与传统的RPC不同,它只支持Golang开发的服务器和服务端之间的交互,因为在内部采用过了encoding/gob
来编解码。
本地方法
type Result struct{
Num int
Data int
}
type Cal int
func (c *Cal) Square(num int) *Result{
return &Result{Num:num, Data:num*num}
}
cal := new(Cal)
result := cal.Square(10)
fmt.Printf("num = %d, square = %d\n", result.Num, result.Data)//num = 10, square = 100
RPC Service
-
net/rpc
包提供通过网络或其它I/O连接对一个对象的导出方法的访问 - 服务端注册一个对象,使其作为一个服务被暴露,服务的名字是该对象的类型名,注册后对象导出的方法就可以被远程访问。
- 服务端可以注册多个不同类型的对象(服务),禁止注册具有相同类型的多个对象。
Golang的RPC函数需要满足以下条件才能被远程调用,不然会被忽略:
func (t *T) MethodName(argType T1, replyType *T2) error
T、T1、T2类型都必须能被encoding/gob
包编解码,net/rpc
可利用HTTP和TCP来传递数据。
RPC服务必须满足以下五种要求
项目 | 名称 | 描述 |
---|---|---|
方法类型 | T | 导出的,首字母大写。 |
方法名 | MethodName | 导出的 |
方法参数 | - | 均为导出或内置的内容 |
方法参数 | replyType | 指针类型 |
方法返回值 | error | 类型为error |
net/rpc
对方法参数个数限制仅能有2个,分别为argType
和replyType
。
参数 | 类型 | 描述 |
---|---|---|
t | T | 服务对象所属类型 |
argType | T1 | 调用者提供的请求类型 |
replyType | *T2 | 返回给调用者的响应类型 |
根据net/rpc
规定改造本地方法
type Cal int
func (c *Cal) Square(num int, result *Result) error{
result.Num = num
result.Data = num * num
return nil
}
var result Result
cal := new(Cal)
cal.Square(10, &result)
fmt.Printf("num = %d, square = %d\n", result.Num, result.Data)//num = 10, square = 100
RPC Server
- RPC是一个典型的客户端/服务端架构模型
- 将本地方法放在服务端,注册服务器定义的方法。
- 服务端需要提供一个套接字服务,监听客户端发送的请求,解析获得客户端的请求参数。
- 获取请求参数执行服务器的调用函数,将返回结果交给客户端。
RPC HTTP Server
例如:实现net/rpc
库实现RPC方法,使用HTTP作为RPC的载体,通过net/http
包监听客户端连接请求。
$ vim server/main.go
package main
import (
"log"
"net/http"
"net/rpc"
)
type Result struct{
Num int
Data int
}
type Cal int
func (c *Cal) Square(num int, result *Result) error{
result.Num = num
result.Data = num * num
return nil
}
func main(){
//发布满足RPC注册条件的方法
err := rpc.Register(new(Cal))
if err!=nil{
log.Fatal(err)
}
//注册用于处理RPC消息的HTTP处理器
rpc.HandleHTTP()
//监听端口等待RPC请求
log.Println("RPC Server listen on 7000")
err = http.ListenAndServe(":7001", nil)
if err!=nil {
log.Fatal("ERROR:", err)
}
}
$ go run main.go
2021/04/09 20:49:03 RPC Server listen on 7000
RPC TCP Server
type User struct{
Id int
Name string
}
type RpcServer struct{}
func (s *RpcServer) User(user User, message *string) error{
*message = fmt.Sprintf("id = %d, name = %s", user.Id, user.Name)
return nil
}
rcvr := new(RpcServer)
//注册RPC服务
err := rpc.Register(rcvr)
if err!=nil{
log.Fatalln("TPC Server: rpc register error:", err)
}
//创建TCP服务端
addr,err := net.ResolveTCPAddr("tcp", ":7001")
if err != nil{
log.Fatalln("TCP Resolve Addr error:", err)
}
//指定地址监听TCP网络请求
listener,err := net.ListenTCP("tcp", addr)
if err!=nil {
log.Fatalln("TCP Listen error:", err)
}
//客户端
for{
//监听请求获取连接对象
conn,err := listener.Accept()
if err!=nil {
log.Println("connect error")
continue
}
//创建一个goroutine处理连接
go rpc.ServeConn(conn)
}
$ go run server/main.go
rpc.Register
-
Register
用来向Server
服务端注册RPC服务 - 默认
rpc.Register()
会将方法接收者(revr,receiver)的类型名作为方法名前缀。
func rpc.Register(rcvr interface{}) error
rpc.RegisterName
- 可调用
rpc.RegisterName()
方法来设置RPC方法名前缀
func rpc.RegisterName(name string, recv interface{}) error
rpc.HandleHTTP
-
rpc.HandleHTTP()
采用HTTP协议作为RPC载体,用于注册HTTP路由。
func HandleHTTP() {
DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath)
}
rpc.HandleHTTP()
会调用http.Handle()
在预定义路径上DefaultRPCPath
注册处理器,这个处理器最终被添加到net/http
包中的默认多路复用器。
func (server *Server) HandleHTTP(rpcPath, debugPath string) {
http.Handle(rpcPath, server)
http.Handle(debugPath, debugHTTP{server})
}
http.ListenAndServe
-
http.ListenAndServe
用于在指定端口上启动HTTP服务,请求RPC方法会交给RPC内部路由进行处理。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
-
http.ListenAndServer
第二个参数传入nil
时会使用默认的多路复用器
rpc.ServeConn
ServeConn
在单个连接上执行Server,ServeConn
会阻塞,服务该连接直到客户端挂起。
func ServeConn(conn io.ReadWriteCloser) {
DefaultServer.ServeConn(conn)
}
调用者应另开线程调用go server.ServeConn(conn)
ServeConn在该连接使用encoding/gob
包有线格式。
RPC Client
RPC HTTP Client
- 创建HTTP服务器调用远程方法
$ vim client/main.go
package main
import (
"log"
"net/rpc"
)
type Result struct{
Num int
Data int
}
func main(){
client,err := rpc.DialHTTP("tcp", "127.0.0.1:7001")
if err!=nil {
log.Fatal("RPC Client ERROR:", err)
}
var result Result
err = client.Call("Cal.Square", 10, &result)
if err!=nil {
log.Fatal("RPC Client Call Error:", err)
}
log.Printf("Num = %d, Data = %d\n", result.Num, result.Data)
}
$ go run main.go
2021/04/09 20:49:47 Num = 10, Data = 100
RPC TCP Client
type User struct{
Id int
Name string
}
//连接RPC服务端
client,err := rpc.Dial("tcp", "127.0.0.1:7001")
if err!=nil {
panic(err)
}
defer client.Close()
//发送请求
var reply string
user := &User{Id:1, Name:"root"}
err = client.Call("RpcServer.User", user, &reply)
if err!=nil{
panic(err)
}
log.Println(reply)
client.Call
-
client.Call
同步调用会阻塞当前程序直到结果返回
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
client.Go
-
client.Go
异步调用通过调用<-call.Done
阻塞当前程序直到RPC调用结束
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call
异步调用方式
var result Result
call := client.Go("Cal.Square", 10, &result, nil)
<-call.Done
log.Printf("Num = %d, Data = %d\n", result.Num, result.Data)
证书鉴权
有疑问加站长微信联系(非本文作者)