将信息使用gob压缩,通过TCP进行传输实现远程服务调用。
原教程
主要分为以下几步
- 约定数据包传输格式
- 约定数据的加密和解密
- 服务端对请求的回复的封装
- 客户端对函数注册的封装
项目结构
├── client.go
├── cmd
│ ├── client
│ │ └── main.go
│ ├── server
│ │ └── main.go
│ └── struct
│ └── user.go
├── encode.go
├── message.go
└── server.go
约定数据包传输格式
// message.go
//网络传输数据格式
//两端要约定好数据包的格式
//4字节header uint32
type Session struct {
conn net.Conn
}
func NewSession(conn net.Conn) *Session {
return &Session{conn: conn}
}
//对数据封包
func (s *Session) Write(data []byte) error {
buf := make([]byte, 4+len(data))
//写入头部,记录数据长度
binary.BigEndian.PutUint32(buf[:4], uint32(len(data)))
copy(buf[4:], data)
_, err := s.conn.Write(buf)
if err != nil {
return err
}
return nil
}
//对数据拆包
func (s *Session) Read() ([]byte, error) {
header := make([]byte, 4)
//读头
_, err := io.ReadFull(s.conn, header)
if err != nil {
return nil, err
}
//读数据
dataLen := binary.BigEndian.Uint32(header)
data := make([]byte, dataLen)
if _, err := io.ReadFull(s.conn, data); err != nil {
return nil, err
}
return data, nil
}
约定数据的加密和解密
使用gob进行压缩,适用于go程序的交互
// encode.go
// RPCData 定义RPC交互的数据结构
type RPCData struct {
// 访问的函数
Name string
// 访问时的参数
Args []interface{}
}
func NewRPCData(name string, args []interface{}) *RPCData {
return &RPCData{
Name: name,
Args: args,
}
}
//数据的压缩
func (data *RPCData) Encode() ([]byte, error) {
var buf bytes.Buffer
bufEnc := gob.NewEncoder(&buf)
if err := bufEnc.Encode(data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
//数据的解压
func (data *RPCData) Decode(b []byte) error {
buf := bytes.NewBuffer(b)
bufDec := gob.NewDecoder(buf)
if err := bufDec.Decode(data); err != nil {
return err
}
return nil
}
服务端对请求的回复的封装
// server.go
/*
服务端接收到的数据需要包括什么?
调用的函数名、参数列表,还有一个返回值error类型
服务端需要解决的问题是什么?
Map维护客户端传来调用函数,服务端知道去调谁
服务端的核心功能有哪些?
维护函数map
客户端传来的东西进行解析
函数的返回值打包,传给客户端
*/
type Server struct {
//监听地址
addr string
//map Map维护客户端传来调用函数,服务端知道去调谁
funcs map[string]reflect.Value
}
func NewServer(addr string) *Server {
return &Server{addr: addr, funcs: make(map[string]reflect.Value)}
}
// Register 注册服务
// 第一个参数函数名,第二个传入真正的函数
func (s *Server) Register(rpcName string, f interface{}) {
v := reflect.ValueOf(f)
if v.Kind() != reflect.Func {
panic(errors.New("invalid TypeOf Func"))
}
if _, ok := s.funcs[rpcName]; ok {
panic(errors.New("repeated rpcName"))
}
s.funcs[rpcName] = v
}
// Run 服务器等待调用的方法
func (s *Server) Run() {
listen, err := net.Listen("tcp", s.addr)
if err != nil {
panic(err)
}
for {
conn, err := listen.Accept()
if err != nil {
continue
}
go s.process(conn)
}
}
func (s *Server) process(conn net.Conn) {
session := NewSession(conn)
session.conn.SetDeadline(time.Time{})
for {
b, err := session.Read()
if err == io.EOF {
log.Println("EOF received")
return
}
if err != nil {
log.Println("Error reading session,err:", err)
return
}
rpcData := new(RPCData)
if err := rpcData.Decode(b); err != nil {
log.Println("Error decoding session,err:", err)
return
}
f, ok := s.funcs[rpcData.Name]
if !ok {
log.Println("Err Call Func Not Find,func name:", rpcData.Name)
return
}
//通过反射调用函数
t := f.Type()
if t.NumIn() != len(rpcData.Args) {
log.Println("Err NumIn Lens Not Equal")
return
}
inArgs := make([]reflect.Value, 0, len(rpcData.Args))
for i, arg := range rpcData.Args {
v := reflect.ValueOf(arg)
if t.In(i).Kind() != v.Type().Kind() {
log.Println("Err parameter Type Mismatch,need:", t.In(i).Kind(), "but:", v.Type().Kind())
return
}
inArgs = append(inArgs, v)
}
out := f.Call(inArgs)
outArgs := make([]interface{}, 0, len(out))
for _, o := range out {
outArgs = append(outArgs, o.Interface())
}
//响应内容
responseRPCData := NewRPCData(rpcData.Name, outArgs)
bytes, err := responseRPCData.Encode()
if err != nil {
log.Println("Encode error,err:", err)
return
}
if err := session.Write(bytes); err != nil {
log.Println("Send error,err:", err)
}
}
}
客户端对函数注册的封装
// client.go
type Client struct {
conn net.Conn
}
func NewClient(conn net.Conn) *Client {
return &Client{conn: conn}
}
// CallRPC 实现通用的RPC客户端
// 传入访问的函数名
// fPtr指向的是函数原型
// var select fun xx(User)
// cli.callRPC("selectUser",&select)
func (c *Client) CallRPC(rpcName string, fPtr interface{}) chan error {
//获取函数原型
fn := reflect.ValueOf(fPtr).Elem()
if fn.Type().Kind() != reflect.Func {
panic(errors.New("err fPtr Not Func"))
}
errChan := make(chan error, 1)
//远程函数调用
f := func(args []reflect.Value) []reflect.Value {
inArgs := make([]interface{}, 0, len(args))
for _, arg := range args {
inArgs = append(inArgs, arg.Interface())
}
session := NewSession(c.conn)
rpcData := NewRPCData(rpcName, inArgs)
b, err := rpcData.Encode()
if err != nil {
errChan <- errors.New("Err Encode err:" + err.Error())
return nil
}
if err = session.Write(b); err != nil {
errChan <- err
return nil
}
respBytes, err := session.Read()
if err != nil {
errChan <- err
return nil
}
respRPC := new(RPCData)
if err := respRPC.Decode(respBytes); err != nil {
errChan <- err
return nil
}
outArgs := make([]reflect.Value, 0, len(respRPC.Args))
for i, arg := range respRPC.Args {
//nil转换
if arg == nil {
// reflect.Zero()会返回类型的零值的value
// .out()会返回函数输出的参数类型
outArgs = append(outArgs, reflect.Zero(fn.Type().Out(i)))
continue
}
outArgs = append(outArgs, reflect.ValueOf(arg))
}
errChan <- nil
return outArgs
}
v := reflect.MakeFunc(fn.Type(), f)
fn.Set(v)
return errChan
}
测试
// cmd/struct/user.go 通用结构
package ss
type User struct {
Name string
Age int
}
// cmd/server/main.go
func SayHello(user ss.User) string {
return fmt.Sprintf("hello %s:%d", user.Name, user.Age)
}
func main() {
log.Println("server starting...")
gob.Register(ss.User{}) //传输时会使用空接口传输,所以需要注册下
srv := rpc.NewServer(":8080")
srv.Register("SayHello", SayHello)
srv.Run()
}
// cmd/client/main.go
func main() {
conn, err := net.Dial("tcp", ":8080")
if err != nil {
log.Fatalln("can't dial: ", err)
}
gob.Register(ss.User{})
cli := rpc.NewClient(conn)
var SayHello func(user ss.User) string
errChan := cli.CallRPC("SayHello", &SayHello)
defer close(errChan)
res := SayHello(ss.User{Name: "raja", Age: 20})
fmt.Println(res, <-errChan)
res = SayHello(ss.User{Name: "ww", Age: 21})
fmt.Println(res, <-errChan)
}
总结
一个简单的RPC框架,对go的反射使用更加熟悉了一些