RPC和Protubuf(四)
Protofbuf与RPC实例
下面已网上最多的一个RPC实例进行分析。首先定义好proto文件,在proto目录下创建message.proto文件:
syntax = "proto3";
package proto;
// 订单请求参数
message OrderRequest {
string orderId = 1;
int64 timeStamp = 2;
}
// 订单信息
message OrderInfo {
string OrderId = 1;
string OrderName = 2;
string OrderStatus =3;
}
使用:protoc --go_out=. message.proto 生成相应的go文件,然后创建服务端代码:mes_server.go文件:
package main
import (
"errors"
"fmt"
"log"
"net"
"net/http"
"net/rpc"
"rpc_protobuf/proto"
"time"
)
// 订单服务
type OrderService struct {
}
// 获取订单函数,根据接口定义的请求和响应返回相应的结果
func (os *OrderService) GetOrderInfo(request *proto.OrderRequest, response *proto.OrderInfo) error {
// 最后返回的结果体满足proto的OrderInfo接口
orderMap := map[string]proto.OrderInfo{
"201907300001": proto.OrderInfo{OrderId: "201907300001", OrderName: "衣服", OrderStatus: "已付款"},
"201907310001": proto.OrderInfo{OrderId: "201907310001", OrderName: "零食", OrderStatus: "已付款"},
"201907310002": proto.OrderInfo{OrderId: "201907310002", OrderName: "食品", OrderStatus: "未付款"},
}
current := time.Now().Unix()
// 请求发起的时间,以及调用函数的时间进行比较
if request.TimeStamp > current {
fmt.Println("超时时间戳")
*response = proto.OrderInfo{OrderId: "0", OrderName: "", OrderStatus: "订单信息异常"}
} else {
// 取出数据
result := orderMap[request.OrderId]
if result.OrderId != "" {
*response = orderMap[request.OrderId]
} else {
return errors.New("server error")
}
}
return nil
}
func main() {
orderService := new(OrderService)
// 注册服务
//rpc.RegisterName("",orderService)) // 这个函数可以传入注册的服务空间
rpc.Register(orderService)
rpc.HandleHTTP()
log.Println("server starting....")
listen, err := net.Listen("tcp", ":1234")
if err != nil {
panic(err.Error())
}
// 针对监听到得每个请求,执行handler
http.Serve(listen, nil)
}
有了服务端代码后,紧接着我们要开始写客户端代码:mes_client.go:
package main
import (
"fmt"
"net/rpc"
"rpc_protobuf/proto"
"time"
)
func main() {
// 拨号
client, err := rpc.DialHTTP("tcp", ":1234")
if err != nil {
panic(err.Error())
}
timeStamp := time.Now().Unix()
// 根据接口提供的数据结构提交请求
request := proto.OrderRequest{OrderId:"201907310002", TimeStamp:timeStamp}
var response *proto.OrderInfo
// 调用远程服务,这里默认是结构体名字.方法
err = client.Call("OrderService.GetOrderInfo", request, &response)
if err != nil {
panic(err)
}
fmt.Println(*response)
}
RPC实现原理
GO语言中的RPC最简单的使用时通过Client.Call() 方法进行通过同步阻塞调用,该方法实现如下:我们打开源码
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
// 这里通过client.Go返回返回一个对象,最后调用该Call,结构体对象的.Done的channel中读出一个call
这一块可以自定义,改为异步调用,具体参考《GO 语言高级编程》P219
基于RPC实现监控功能
在很多系统中提供了监控(watch)功能的接口, 当系统满足某些条件时,Watch方法返回监控的结果,在这里可以尝试通过RPC实现基本的监控功能, 首先我们先定义好proto文件,如果是出现传输的数据是map,或者其他复杂的数据结构,我们需要重新定义proto文件,watch.proto文件:
syntax = "proto3";
package proto;
// 设置的数组定义
message SetList {
int32 id = 1;
repeated string val = 2; // repeated 标识可以重复的元素,如果是一个Map需要嵌套
}
// 超时时间设置
message SetTime {
int32 time = 1;
}
// 设置值结构
message KeyChanged {
string keyChange = 1;
}
// 设置返回值
message ReplyString {
string reply = 1;
}
然后通过下面的命令开始生成go文件,进入到proto目录下使用命令: protoc --go_out=. watch.proto,紧接着我们继续完成wat_service.go:
package main
import (
"fmt"
"log"
"math/rand"
"net"
"net/rpc"
"rpc_protobuf/proto"
"sync"
"time"
)
// 定义一个内存性键值对数据库
type KVStoreService struct {
m map[string]string
filter map[string]func(key string)
mu sync.Mutex
}
// 结构体初始化函数,调用函数前进行初始化
func NewKVStoreService() *KVStoreService {
return &KVStoreService{
m: make(map[string]string),
filter: make(map[string]func(key string)),
}
}
// 通过Set函数设置结构体的设置键值性数据,注意参数都是接口中传来的
func (p *KVStoreService) Set(kv *proto.SetList, reply *proto.ReplyString) error {
p.mu.Lock()
defer p.mu.Unlock()
key, value := kv.Val[0], kv.Val[1]
fmt.Println("exect:",p.m[key],value)
if oldValue := p.m[key]; oldValue != value {
for _, fn := range p.filter {
fmt.Println("ok")
fn(key)
}
}
p.m[key] = value
reply.Reply = "success reply"
return nil
}
// 监控函数用于客户端异步启动后监听服务
func (p *KVStoreService) Watch(timoutSencod *proto.SetTime, keyChanged *proto.KeyChanged) error {
id := fmt.Sprintf("watch-%s-%03d", time.Now(), rand.Int())
ch := make(chan string, 10)
p.mu.Lock()
p.filter[id] = func(key string) { ch <- key }
fmt.Println("watch",p.m,p.filter,ch)
p.mu.Unlock()
select {
case <-time.After(time.Duration(timoutSencod.Time) * time.Second):
return fmt.Errorf("time out")
case keys := <-ch:
fmt.Println("key change:", keys)
keyChanged.KeyChange = keys
return nil
}
}
func main() {
kvstoreService := NewKVStoreService()
kvstoreService.m["name"] = "wang"
// 注册服务
//rpc.RegisterName("",orderService)) // 这个函数可以传入注册的服务空间
rpc.Register(kvstoreService)
log.Println("server starting....")
listen, err := net.Listen("tcp", ":9999")
if err != nil {
panic(err.Error())
}
conn, err := listen.Accept()
// 针对监听到得每个请求,执行handler
rpc.ServeConn(conn)
}
然后是写客户端代码:
package main
import (
"fmt"
"log"
"net/rpc"
"rpc_protobuf/proto"
"time"
)
// 先启动监控服务,之后调用Call
func doClientWork(client *rpc.Client) {
go func() {
var keyChange = &proto.KeyChanged{}
var times = &proto.SetTime{
Time:30,
}
err := client.Call("KVStoreService.Watch",times,keyChange)
if err != nil {
log.Fatal(err)
}
fmt.Println("watch:",keyChange.KeyChange)
}()
// 需要停顿下否则filter数据未写入
time.Sleep(time.Second)
var reply = &proto.ReplyString{}
var request = &proto.SetList{
Id: 1,
Val: []string{"name","li"},
}
err := client.Call("KVStoreService.Set",request,reply)
fmt.Println(reply)
if err != nil {
log.Fatal(err)
}
time.Sleep(time.Second*3)
}
func main() {
// 拨号
client, err := rpc.Dial("tcp", ":9999")
if err != nil {
panic(err.Error())
}
// 调用远程服务,这里默认是结构体名字.方法
doClientWork(client)
time.Sleep(2*time.Second)
}

浙公网安备 33010602011771号