Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

请问可以和protobuf结合使用吗? #37

Closed
lcg635 opened this issue Aug 27, 2015 · 7 comments
Closed

请问可以和protobuf结合使用吗? #37

lcg635 opened this issue Aug 27, 2015 · 7 comments

Comments

@lcg635
Copy link

lcg635 commented Aug 27, 2015

No description provided.

@bg5sbk
Copy link
Contributor

bg5sbk commented Aug 28, 2015

可以的,跟序列化格式无关,跟分包协议也无关,参考已有的几个codec或利用已有的codec实现protobuf编解码器就可以了

@oikomi
Copy link
Contributor

oikomi commented Aug 28, 2015

@idada 达神有空可以实现一下:) 或者开一个目录等pr :)

@chuangyou
Copy link

.....这个自己实现不就好了。这么简单

@bg5sbk
Copy link
Contributor

bg5sbk commented Aug 31, 2015

大家好,我做了Protobuf的实验,基本上比较OK,具体应用到实际项目中可能需要再根据项目需求调整(比如根据包头来得知消息类型?)。

我用的库是这个:https://github.com/golang/protobuf

之前我没用过Protobuf,所以一开始花了点时间搭建环境,在Mac上搭建起来还是比较顺利的,就是要装上原始的Protobuf命令行工具,然后装上生成Go代码的Protobuf工具插件,插件必须让Protobuf工具可以搜索到,所以我把插件所在目录添加到PATH环境变量里。

然后就是按Protobuf项目给的Go示例代码,生成了一份test.pb.go代码出来,然后实现Protobuf的Codec。

因为Protobuf好像无法像Xml、Json、Gob这三个编码格式一样采用流编码和流解码,所以在Encoder和Decoder构造函数中必须强制要求外部传进来的是binary.PacketReaderbinary.PacketWriter,也就是说这个ProtobufCodec必须配合link.Packet()使用。(也可能是我对Protobuf的接口不熟悉,没找到流式编解码接口)

link的Codec实现是很灵活的,如果觉得一个个Codec套起来使用很烦,也可以直接就实现一个内置分包格式的项目专用Codec,这边只是一个简单示例,就不把各种可能性都列举一遍了。

下面是完整测试代码:

package main

import (
    "io"
    "sync"

    "github.com/funny/binary"
    "github.com/funny/link"
    "github.com/golang/protobuf/proto"
)

func main() {
    serverInitWait.Add(1)
    serverStopWait.Add(1)
    go server()
    go client()
    serverStopWait.Wait()
}

var (
    serverAddr     string
    serverInitWait sync.WaitGroup
    serverStopWait sync.WaitGroup
)

func server() {
    server, err := link.Serve("tcp", "0.0.0.0:0", link.Packet(link.Uint16BE, ProtobufCodec{}))
    if err != nil {
        panic(err)
    }
    serverAddr = server.Listener().Addr().String()
    serverInitWait.Done()

    session, err := server.Accept()
    if err != nil {
        panic(err)
    }

    newTest := &Test{}
    err = session.Receive(newTest)
    if err != nil {
        panic(err)
    }

    if newTest.GetLabel() != "hello" {
        println("data mismatch %q != 'hello'")
    } else {
        println("done")
    }

    session.Close()
    server.Stop()

    serverStopWait.Done()
}

func client() {
    serverInitWait.Wait()
    client, err := link.Connect("tcp", serverAddr, link.Packet(link.Uint16BE, ProtobufCodec{}))
    if err != nil {
        panic(err)
    }

    err = client.Send(&Test{
        Label: proto.String("hello"),
        Type:  proto.Int32(17),
        Optionalgroup: &Test_OptionalGroup{
            RequiredField: proto.String("good bye"),
        },
    })

    if err != nil {
        panic(err)
    }

    client.Close()
}

type ProtobufCodec struct {
}

func (_ ProtobufCodec) NewEncoder(w io.Writer) link.Encoder {
    return ProtobufEncoder{
        writer: w.(*binary.PacketWriter),
    }
}

func (_ ProtobufCodec) NewDecoder(r io.Reader) link.Decoder {
    return ProtobufDecoder{
        reader: r.(*binary.PacketReader),
    }
}

type ProtobufEncoder struct {
    writer io.Writer
}

func (pe ProtobufEncoder) Encode(msg interface{}) error {
    data, err := proto.Marshal(msg.(proto.Message))
    if err != nil {
        return err
    }
    _, err = pe.writer.Write(data)
    return err
}

type ProtobufDecoder struct {
    reader *binary.PacketReader
}

func (pe ProtobufDecoder) Decode(msg interface{}) error {
    data, err := pe.reader.ReadPacket()
    if err != nil {
        return err
    }
    return proto.Unmarshal(data, msg.(proto.Message))
}

这里一起给出test.pb.go的代码,方便大家实验:

// Code generated by protoc-gen-go.
// source: test.proto
// DO NOT EDIT!

/*
Package example is a generated protocol buffer package.

It is generated from these files:
    test.proto

It has these top-level messages:
    Test
*/
package main

import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

type FOO int32

const (
    FOO_X FOO = 17
)

var FOO_name = map[int32]string{
    17: "X",
}
var FOO_value = map[string]int32{
    "X": 17,
}

func (x FOO) Enum() *FOO {
    p := new(FOO)
    *p = x
    return p
}
func (x FOO) String() string {
    return proto.EnumName(FOO_name, int32(x))
}
func (x *FOO) UnmarshalJSON(data []byte) error {
    value, err := proto.UnmarshalJSONEnum(FOO_value, data, "FOO")
    if err != nil {
        return err
    }
    *x = FOO(value)
    return nil
}

type Test struct {
    Label            *string             `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
    Type             *int32              `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
    Reps             []int64             `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
    Optionalgroup    *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
    XXX_unrecognized []byte              `json:"-"`
}

func (m *Test) Reset()         { *m = Test{} }
func (m *Test) String() string { return proto.CompactTextString(m) }
func (*Test) ProtoMessage()    {}

const Default_Test_Type int32 = 77

func (m *Test) GetLabel() string {
    if m != nil && m.Label != nil {
        return *m.Label
    }
    return ""
}

func (m *Test) GetType() int32 {
    if m != nil && m.Type != nil {
        return *m.Type
    }
    return Default_Test_Type
}

func (m *Test) GetReps() []int64 {
    if m != nil {
        return m.Reps
    }
    return nil
}

func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
    if m != nil {
        return m.Optionalgroup
    }
    return nil
}

type Test_OptionalGroup struct {
    RequiredField    *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
    XXX_unrecognized []byte  `json:"-"`
}

func (m *Test_OptionalGroup) Reset()         { *m = Test_OptionalGroup{} }
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
func (*Test_OptionalGroup) ProtoMessage()    {}

func (m *Test_OptionalGroup) GetRequiredField() string {
    if m != nil && m.RequiredField != nil {
        return *m.RequiredField
    }
    return ""
}

func init() {
    proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
}

因为代码是两份,所以要用 go run main.go test.pb.go 这样的形式来运行测试代码。

@oikomi
Copy link
Contributor

oikomi commented Sep 1, 2015

赞达神 代码合到主线里吗

@LaoZhongGu
Copy link

作为个例子来举例就行了,不要做大而全的东西。按需定制就可以了。

@bg5sbk
Copy link
Contributor

bg5sbk commented Sep 1, 2015

@oikomi 不适合放入项目里的,只是演示的代码,实际项目里用Protobuf应该要做更多事情的,比如上面说的消息类型识别,不同项目会很不一样。

@bg5sbk bg5sbk closed this as completed Sep 22, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants