Go语言使用protobuf

以前使用c#,使用protobuf-net习惯了.net的方式,现在学习用go操作protobuf,从基础的来

本文github: https://github.com/zboyco/go-test/tree/master/protobuf


###1. 准备工作
1.1 下载protobuf的编译器protoc
下载地址 https://github.com/google/protobuf/releases
下载后把bin路径加入PATH环境变量

1.2 获取并安装proto-gen-go

1
go get github.com/golang/protobuf/protoc-gen-go

1.3 获取 goprotobuf 提供的支持库

1
go get github.com/golang/protobuf/proto

###2. 使用Protobuf

go使用xorm操作mysql

今天学习一下go语言对mysql的操作,因为以前在c#中习惯了用orm操作,所以今天了解了一下,尝试下用go语言的xorm操作mysql数据库.

xorm地址: https://github.com/go-xorm/xorm


###1. xorm安装

1
go get github.com/go-xorm/xorm

另外我们需要连接mysql,所以需要MySQL的驱动

1
go get github.com/go-sql-driver/mysql

###2. xorm创建连接
xorm将连接实例称为引擎,一个连接就是一个引擎,我们测试只需要一个连接即可,如果需要连接多个mysql,新建多个引擎就可以了

1
2
// 新建orm引擎
engine, err := xorm.NewEngine("mysql", "root:pwd@tcp(192.168.2.100:3307)/XormTest?charset=utf8")

###3. 设置字段映射方式

Go实现简单的Socket服务端笔记(十)

添加Session容器,增加超时自动关闭Session功能

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-10


要实现超时管理,就需要有个地方保存所有的会话(session),我们采用 map 来存储所有的 session ,因为 session 的保存不需要顺序,同时也有删除和增加的功能,map 正好适合.

增加一个池结构,用map实现

1
2
3
4
type sessionSource struct {
source map[int64]*AppSession //Seesion池
mutex sync.Mutex //锁
}

再增加两个参数,分别用来设置超时时间和清理间隔

代码如下

Go实现简单的Socket服务端笔记(九)

采用标准库scanner实现数据分离处理粘包

参考http://feixiao.github.io/2016/05/08/bufio/
使用标准库scanner实现数据分离处理粘包

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-9


直接使用scanner处理粘包,不用管理buffer,相对更加简单

修改socket.go中的handleClient函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 读取数据
func handleClient(server *Server, session *AppSession) {
// 获取连接地址
remoteAddr := session.conn.RemoteAddr()

fmt.Println("客户端[", session.ID, "]地址:", remoteAddr)

// 创建scanner
scanner := bufio.NewScanner(session.conn)

//根据协议定义分离规则
split := func(data []byte, atEOF bool) (int, []byte, error) {
if atEOF {
return 0, nil, errors.New("EOF")
}
if data[0] != '$' || data[3] != '#' {
return 0, nil, errors.New("数据异常")
}
if len(data) > 4 {
length := int16(0)
binary.Read(bytes.NewReader(data[1:3]), binary.BigEndian, &length)
if int(length)+4 <= len(data) {
return int(length) + 4, data[4 : int(length)+4], nil
}
}
return 0, nil, nil
}

// 设置分离函数
scanner.Split(split)

// 获取数据
for scanner.Scan() {
server.OnMessage(session, scanner.Bytes())
}

// 错误处理
if err := scanner.Err(); err != nil {
fmt.Println("客户端[", session.ID, "]数据接收错误, ", err)
session.conn.Close()
fmt.Println("客户端[", session.ID, "]连接已关闭!")
}
}

Go实现简单的Socket服务端笔记(八)

session中Read方法实现粘包拆包处理

定义简单协议,数据包头由4字节构成:
第1位固定为’$’
第2-3位为Body长度(uint16)
第4位固定为’#’
接收数据时若第1位和第4位不正确则认为接收到异常数据,同时关闭socket连接

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-8


为了实现粘包拆包处理,我们自己实现一个buffer类来管理数据
在server目录中增加buffer.go,这里主要参考了https://studygolang.com/articles/12088

完整代码如下

Go实现简单的Socket服务端笔记(七)

Session增加唯一ID,拆分socket中的Read方法

增加ID为了以后判断闲置超时; 拆分Read方法方便扩展协议

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-7

  1. 修改AppSession结构体,增加ID和activeDateTime属性,为超时管理做准备
    1
    2
    3
    4
    5
    6
    //客户端结构体
    type AppSession struct {
    ID int64 //连接唯一标识
    conn net.Conn //socket连接
    activeDateTime time.Time //最后活跃时间
    }
  2. 将socket.go 中接收数据的方法,移植到client.go中,作为AppSession的方法使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //读取数据
    func (session *AppSession) Read() ([]byte, error) {

    //定义一个数据接收Buffer
    var buf [10240]byte

    //读取数据,io.Reader 需要传入一个byte切片
    n, err := session.conn.Read(buf[0:])

    if err != nil {
    return nil, err
    }

    //更新最后活跃时间
    session.activeDateTime = time.Now()
    return buf[0:n], nil
    }

client.go完整代码如下:

Go实现简单的Socket服务端笔记(六)

增加AppSession结构体

OnMessage返回AppSession结构体,提供Send方法,服务器可以主动向客户端发送数据

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-6

新建一个AppSession结构体,将客户端会话放在AppSession中,方便扩展和管理

  1. 在server目录中增加client.go文件
    代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
package server

import "net"

//客户端结构体
type AppSession struct {
conn net.Conn //socket连接
}

func (client *AppSession) Send(buf []byte) {
client.conn.Write(buf)
}

  1. 修改socket.go

Go实现简单的Socket服务端笔记(五)

将读取的数据处理方法作为参数传入server中

将 OnMessage 和 OnError 通过Server结构公开变量传入

本文代码查看github:
https://github.com/zboyco/go-server/tree/step-5

项目结构如下:

1
2
3
|-go-server
|-server
|-socket.go

socket.go 中,Server结构增加两个方法,用来输出接收的消息和错误

1
2
3
4
5
6
7
//服务结构
type Server struct {
ip string
port int
OnError func(error)
OnMessage func([]byte)
}

然后在socket.go中需要的地方调用方法

代码如下: