1. 介绍 Go
语言标准包(net/rpc
)已经提供了对RPC
的支持,而且支持三个级别的RPC:TCP、HTTP和JSONRPC
。但Go
语言的RPC
包是独一无二的RPC
,它和传统的RPC系统不同,它只支持Go语言开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob
来编码。
废话不多说,下面会分别实现: TCP版、HTTP版、JSONRPC版
的RPC。开始撸代码~~
2.TCP版
Go语言的RPC规则:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法。
2.1 服务端实现 package mainimport ( "log" "net" "net/rpc" "time" )type HelloService struct {}func (h *HelloService) Say(request string , response *string ) error { format := time.Now().Format("2006-01-02 15:04:05" ) *response = request + " -- " + format return nil }func main () { _ = rpc.RegisterName("HelloService" , new (HelloService)) listen, err := net.Listen("tcp" , ":1234" ) if err != nil { return } for { accept, err := listen.Accept() if err != nil { log.Fatalf("Accept Error: %s" , err) } go rpc.ServeConn(accept) } }
rpc.RegisterName()
函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在HelloService
服务的空间之下。然后建立一个唯一的TCP链接,并且通过rpc.ServeConn()
函数在该TCP链接上为对方提供RPC服务。
2.2 客户端实现 package mainimport ( "fmt" "log" "net/rpc" "time" )func main () { dial, err := rpc.Dial("tcp" , "127.0.0.1:1234" ) if err != nil { log.Fatal("Dial error " , err) } var result string for i := 0 ; i < 5 ; i++ { _ = dial.Call("HelloService.Say" , "go" , &result) fmt.Println("result:" , result) time.Sleep(time.Second * 2 ) } }
首先是通过rpc.Dial
拨号RPC
服务,然后通过client.Call()
调用具体的RPC
方法。在调用client.Call()
时,第一个参数是用点号链接的RPC
服务名字和方法名字,第二个和第三个参数分别是定义RPC方法的两个参数。
2.3 启动 & 请求 ➜ go run rpc/server/main.go ➜ go run rpc/client/main.go result: go -- 2022-01-08 21:42:18 result: go -- 2022-01-08 21:42:21 result: go -- 2022-01-08 21:42:23 result: go -- 2022-01-08 21:42:25 result: go -- 2022-01-08 21:42:27
3. HTTP版 3.1 服务端实现 package mainimport ( "fmt" "net/http" "net/rpc" )type MathService struct { }func (u *MathService) Multi(a int , sum *int ) error { *sum = a * a return nil }func main () { userService := new (MathService) err := rpc.Register(userService) if err != nil { return } rpc.HandleHTTP() err = http.ListenAndServe(":8080" , nil ) if err != nil { fmt.Println(err) } }
3.2 客户端实现 package mainimport ( "fmt" "net/rpc" )func main () { client, err := rpc.DialHTTP("tcp" , ":8080" ) if err != nil { fmt.Println("err " , err) return } var result int for i := 1 ; i < 10 ; i++ { err = client.Call("MathService.Multi" , i, &result) fmt.Printf("i:%v result:%v \n" , i, result) } }
3.3 启动 & 请求 ➜ go run rpc/http/server.go ➜ go run rpc/http/client.go i:1 result:1 i:2 result:4 i:3 result:9 i:4 result:16 i:5 result:25 i:6 result:36 i:7 result:49 i:8 result:64 i:9 result:81
4. JSON RPC 上面TCP版
和HTTP版
,数据编码采用的都是默认的gob
编码,而gob
编码是Go
特有的编码和解码的专用序列化方法,这也就意味着Gob
无法跨语言使用。而JSON RPC
则可以跨语言使用。
4.1 服务端实现 package mainimport ( "52lu/go-study-example/rpc/jsonrpc/dto" "fmt" "net" "net/rpc" "net/rpc/jsonrpc" "time" )type MathService struct { }func (m MathService) Sum(arg *dto.SumParam, res *dto.SumRes) error { fmt.Printf("arg: %#v\n" , arg) *res = dto.SumRes{ Sum: arg.A + arg.B, Time: time.Now(), } return nil }func main () { err := rpc.RegisterName("MathService" , new (MathService)) if err != nil { fmt.Println("rpc RegisterName err " , err) return } listen, err := net.Listen("tcp" , ":9090" ) if err != nil { fmt.Println("Listen err " , err) return } for { conn, err := listen.Accept() if err != nil { fmt.Println("conn err " , err) return } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
4.2 客户端实现 package mainimport ( "52lu/go-study-example/rpc/jsonrpc/dto" "fmt" "net" "net/rpc" "net/rpc/jsonrpc" )func main () { conn, err := net.Dial("tcp" , ":9090" ) if err != nil { fmt.Println(" rpc.Dial err " , err) return } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) arg := dto.SumParam{A: 20 , B: 80 } var sum dto.SumRes err = client.Call("MathService.Sum" , &arg, &sum) if err != nil { fmt.Println("client.Call err " , err) return } fmt.Printf("res:%+v \n" , sum) }
4.3 启动 & 请求 ➜ go run rpc/jsonrpc/server.go arg: &dto.SumParam{A:20, B:80} ➜ go run rpc/jsonrpc/client.go res:{Sum:100 Time:2022-01-10 23:12:48.880364 +0800 CST}