RPC俗称远程过程调用,是发送tcp请求进行通信,是各个语言都有的,下面是一个最简单的例子,服务端启动服务,客户端通过rpc请求调用
服务端代码:
package main
import (
"fmt"
"net"
"net/rpc"
)
type Person struct {
Name string
Age int
}
type HelloService struct {
}
func (p *HelloService) Hello(msg string, person *Person) error {
person.Name = "zhangsan"
person.Age = 10
return nil
}
func main() {
rpc.RegisterName("HelloSer", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
conn, err := listener.Accept()
if err != nil {
fmt.Println("err2 = ", err)
return
}
rpc.ServeConn(conn)
}
客户端代码:
package main
import (
"fmt"
"net/rpc"
)
type Person struct {
Name string
Age int
}
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
var person Person
err = client.Call("HelloSer.Hello", "haha", &person)
if err != nil {
fmt.Println("err2 = ", err)
return
}
fmt.Println("person = ", person)
}
启动服务端后,执行客户端代码结果:
person = {zhangsan 10}
以上的例子是最简单的,只能连接一次,下面这个例子是稍微复杂一点,服务端启动后,可以支持多个客户端连接:
服务端代码
package main
import (
"fmt"
"net"
"net/rpc"
)
const HelloServiceName = "path/HelloService"
type Person struct {
Name string
Age int
}
type HelloService struct {
}
type HelloServiceInterface interface {
Hello(msg string, person *Person) error
}
func (p *HelloService) Hello(msg string, person *Person) error {
person.Name = "zhangsan"
person.Age = 123
return nil
}
func RegisterService(service HelloServiceInterface) error {
return rpc.RegisterName(HelloServiceName, service)
}
func main() {
RegisterService(new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("err2 = ", err)
return
}
go rpc.ServeConn(conn)
}
}
客户端代码
package main
import (
"fmt"
"net/rpc"
)
const HelloServiceName = "path/HelloService"
type Person struct {
Name string
Age int
}
type HelloServiceInterface interface {
Hello(msg string, person *Person) error
}
type HelloServiceClient struct {
Client *rpc.Client
}
func DialHelloService(network, address string) (*HelloServiceClient, error) {
client, err := rpc.Dial(network, address)
if err != nil {
return nil, err
}
return &HelloServiceClient{Client: client}, err
}
func (p *HelloServiceClient) Hello(msg string, person *Person) error {
err := p.Client.Call(HelloServiceName+".Hello", "haha", person)
return err
}
func main() {
client, err := DialHelloService("tcp", "localhost:1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
var person Person
err = client.Hello("haha", &person)
if err != nil {
fmt.Println("err2 = ", err)
return
}
fmt.Println("person1 = ", person)
}
结果:
person1 = {zhangsan 123}
13.2 跨语言的RPC
和Http一样,RPC也支持跨语言,如下所示:
服务端代码
package main
import (
"fmt"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type HelloService struct {
}
type Person struct {
Name string
Age int
}
func (p *HelloService) Hello(msg string, person *Person) error {
person.Name = "zhangsan"
person.Age = 123
return nil
}
func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("err2 = ", err)
return
}
go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
}
}
服务端启动后,在命令行窗口输入echo -e '{"method":"HelloService.Hello","params":["hello"],"id":1}' | nc localhost 1234,可以查看返回值,如下所示:
{"id":1,"result":{"Name":"zhangsan","Age":123},"error":null}
13.3 使用protobuf进行参数约束
protobuf可以用来对RPC请求的参数和响应进行约束,使得客户端和服务端按照一定的约束来写代码,达到规范的目标,如下是例子
hello.proto文件
syntax = "proto3";
option go_package="./;hello";
package main;
message Person {
string name = 1;
int32 age = 2;
}
执行protoc --go_out=. hello.proto命令生成hello.pb.go代码
最重要的是上方图片的type Person struct {}结构体的代码
生成go代码后就可以用在RPC服务端和客户端端了
服务端代码
package main
import (
"fmt"
"google.golang.org/protobuf/runtime/protoimpl"
"net"
"net/rpc"
)
type HelloService struct{}
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}
func (p *HelloService) Hello(msg string, person *Person) error {
person.Name = "zhangsan"
person.Age = 123
return nil
}
func main() {
rpc.RegisterName("HelloService", new(HelloService))
listener, err := net.Listen("tcp", ":1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
for {
conn, err := listener.Accept()
if err != nil {
fmt.Println("err2 = ", err)
return
}
go rpc.ServeConn(conn)
}
}
客户端代码
package main
import (
"fmt"
"google.golang.org/protobuf/runtime/protoimpl"
"net/rpc"
)
type Person struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
}
func main() {
client, err := rpc.Dial("tcp", "localhost:1234")
if err != nil {
fmt.Println("err1 = ", err)
return
}
var person Person
msg := "hahahaha"
err = client.Call("HelloService.Hello", msg, &person)
if err != nil {
fmt.Println("err2 = ", err)
return
}
fmt.Println("person = ", person.Name)
}
执行结果
person = zhangsan