gsn
一个简单的Go网络工具,可以用于TCP或UDP。
支持 TCP UDP
描述
- 采用模块化设计,如同积木一样,可以自由控制数据格式
- 代码简单,使用方便
- 无外部依赖
开始
go get github.com/olocat/gsn
示例
1.启动一个TCP监听
package main
import (
"fmt"
"github.com/olocat/gsn"
)
func main() {
listener, err := gsn.ListenTCP(":8088", nil, nil)
if err != nil {
fmt.Println(err.Error())
return
}
listener.Start()
}
上面的代码虽然启动了一个监听,但并没有处理。
在 gsn 中,对行为的处理由 Behavior
接口来控制。
type Behavior interface {
OnConnect(net.Conn)
OnPackage(net.Conn, []byte)
OnRelease(net.Conn)
OnClose(net.Conn)
Convert(net.Conn) net.Conn
}
OnConnect 会在连接成功后执行 OnPackage 会在收到数据时执行 OnRelease 会在关闭连接前执行 OnClose 会在关闭连接后执行 Convert 用于设置连接参数,或用于将net.Conn转成自定义的连接类型
现有一个已经空实现 Behavior 的结构体 BaseBehavior
type BaseBehavior struct{}
func (s *BaseBehavior) OnConnect(net.Conn) {}
func (s *BaseBehavior) OnPackage(net.Conn, []byte) {}
func (s *BaseBehavior) OnRelease(net.Conn) {}
func (s *BaseBehavior) OnClose(net.Conn) {}
func (s *BaseBehavior) Convert(conn net.Conn) net.Conn { return conn }
如果自定的 Behavior 不需要实现全部的 Behavior 接口内的方法,可以选择直接继承 BaseBehavior。
2.启动带有行为的监听
package main
import (
"fmt"
"github.com/olocat/gsn"
"net"
)
type MyBehavior struct {
gsn.BaseBehavior
}
func OnConnect(conn net.Conn) {
fmt.Println("有一个连接进来了!", conn.RemoteAddr())
}
func OnPackage(conn net.Conn, stream []byte) {
fmt.Println("收到数据包", stream)
}
func main() {
listener, err := gsn.ListenTCP(":8088", nil, nil)
if err != nil {
fmt.Println(err.Error())
return
}
listener.Start()
}
如上述代码中,当有连接进来时,OnConnect 就会执行 当收到数据包后,OnPackage 就会执行
3.发起连接
使用 Dial 可以快捷的发起一个连接
package main
import (
"fmt"
"github.com/olocat/gsn"
"net"
)
type MyBehavior struct {
gsn.BaseBehavior
}
func OnPackage(conn net.Conn, stream []byte) {
fmt.Println("收到数据包", stream)
}
func main() {
conn, err := gsn.Dial("tcp", ":8088", &MyBehavior{}, nil)
if err != nil {
fmt.Println(err.Error())
return
}
//TODO
}
Dial 支持 TCP 和 UDP 也同样使用可以使用 Behavior 管理行为
4.发包与解包
gsn 默认提供三种收发包格式 bin,json,xml
- bin 即手动控制 stream 中每个字节表示的数据类型
- json 即使用 json 格式
- xml 同样,即使用 xml 格式
原本有想过支持 protobuf 的,但为了不引入每三方库,只得放弃。 但 gsn 也可以很方便的引入 protobuf,如有需要可以自行实现。
发包:
package main
import (
"fmt"
"github.com/olocat/gsn"
)
// Packer 发包示例
func Packer() {
packer := gsn.NewBinPacker()
packer.PackInt16(-32768)
packer.PackUint32(4294967295)
packer.PackUint64(18446744073709551615)
packer.PackBytes([]byte{0, 1, 2, 3, 255})
str := "一地在要工上是中国同和的有人我主产不为这民了发以经"
packer.PackUint32(uint32(len(str)))
packer.PackString(str)
packer.GetData() // 使用 GetData 获取封装好的数据 []byte 类型
}
// Unpacker 解包示例
func Unpacker(stream []byte) {
unpacker := gsn.NewBinUnpacker(stream)
v1, _ := unpacker.UnpackInt16() // -32768
v2, _ := unpacker.UnpackUint32() // 4294967295
v3, _ := unpacker.UnpackUint64() // 18446744073709551615
v4, _ := unpacker.UnpackBytes(5) // 5为 byte 数据长度
sz, _ := unpacker.UnpackUint32() // 字符串长度
str, _ := unpacker.UnpackString(int(sz))
fmt.Println(v1, v2, v3, v4)
fmt.Println(str)
//TODO
}
除此外还可以使用 json 或 xml
json 示例
package main
import (
"encoding/json"
"fmt"
"github.com/olocat/gsn"
)
func Packer() {
jsonData := map[string]any{
"boolData": true,
"numberData": 123456789,
"stringData": "abcdefghijklmnopqrstuvwxyz",
"chineseData": "一地在要工上是中国同和的有人我主产不为这民了发以经",
"mapData": map[string]any{
"innerData": 123456,
},
}
packer := gsn.NewJsonPacker()
err := packer.PackObject(jsonData)
if err != nil {
fmt.Println(err.Error())
return
}
packer.GetData() // 使用 GetData 获取封装好的数据 []byte 类型
}
func Unpacker(stream []byte) {
jsonData := map[string]any{}
err := json.Unmarshal(stream, &jsonData)
if err != nil {
fmt.Println(err.Error())
return
}
for k, v := range jsonData {
fmt.Println(k, v)
}
//TODO
}
xml 示例
package main
import (
"encoding/xml"
"fmt"
"github.com/olocat/gsn"
)
type Address struct {
City, State string
}
type Person struct {
XMLName xml.Name `xml:"person"`
Id int `xml:"id,attr"`
FirstName string `xml:"name>first"`
LastName string `xml:"name>last"`
Age int `xml:"age"`
Height float32 `xml:"height,omitempty"`
Married bool
Address
Comment string `xml:",comment"`
}
func Packer() {
pack := gsn.NewXmlPacker()
v := &Person{Id: 13, FirstName: "John", LastName: "Doe", Age: 42, Height: 172, Comment: " Need more details. "}
v.Address = Address{"Hanga Roa", "Easter Island"}
err := pack.PackObject(v)
if err != nil {
fmt.Println(err.Error())
}
pack.GetData() // 使用 GetData 获取封装好的数据 []byte 类型
}
func Unpacker(stream []byte) {
p := Person{}
err := xml.Unmarshal(stream, &p)
if err != nil {
fmt.Println(err.Error())
return
}
fmt.Println(p)
//TODO
}
5.收发包示例
package main
import (
"encoding/json"
"fmt"
"github.com/olocat/gsn"
"net"
"time"
)
type MyBehavior struct {
gsn.BaseBehavior
}
func (m *MyBehavior) OnPackage(conn net.Conn, stream []byte) {
jsonData := map[string]any{}
err := json.Unmarshal(stream, &jsonData)
if err != nil {
fmt.Println(err.Error())
return
}
//TODO
}
func main() {
listener, err := gsn.ListenTCP(":8088", &MyBehavior{}, nil)
if err != nil {
fmt.Println(err.Error())
return
}
listener.Start()
time.Sleep(time.Second)
// ---------- 连接并发包 -----------
conn, err := gsn.Dial("tcp", ":8088", nil, nil)
if err != nil {
fmt.Println(err.Error())
return
}
data := map[string]any{
"cmd": 1001,
"name": "NAME",
"data": "abcdefghijklmnopqrstuvwxyz",
"chinese": "一地在要工上是中国同和的有人我主产不为这民了发以经",
}
packer := gsn.NewJsonPacker()
_ = packer.PackObject(data)
_, _ = conn.Write(packer.GetData()) // 发送数据
time.Sleep(3 * time.Second) // 预留足够的时间来完成包处理
}
6.设置 Deadline
gsn 可以在 Behavior 的 Convert 内设置 Deadline
package main
import (
"fmt"
"github.com/olocat/gsn"
"net"
"time"
)
type MyBehavior struct {
gsn.BaseBehavior
}
func OnPackage(conn net.Conn, stream []byte) {
fmt.Println("收到数据包", stream)
}
func Convert(conn net.Conn) net.Conn {
conn.SetDeadline(time.Now().Add(10 * time.Second))
conn.SetReadDeadline(time.Now().Add(10 * time.Second))
conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
}
func main() {
conn, err := gsn.Dial("tcp", ":8088", &MyBehavior{}, nil)
if err != nil {
fmt.Println(err.Error())
return
}
//TODO
}
7.自定义Conn
由于 Behavior 中的每个方法都会传递 conn, 但很多时候 conn 需要自己定义, 所以 Convert 主要用于将 net.Conn 转成自定义的 conn
package main
import (
"fmt"
"github.com/olocat/gsn"
"net"
)
type MyConn struct {
net.Conn
Id uint32
Name string
}
type MyBehavior struct {
gsn.BaseBehavior
}
func OnPackage(conn net.Conn, stream []byte) {
fmt.Printf("收到一个包 Id:%d Name:%s\n", conn.(*MyConn).Id, conn.(*MyConn).Name)
}
func Convert(conn net.Conn) net.Conn {
return &MyConn{Conn: conn, Id: 100, Name: "this a name"}
}
func main() {
conn, err := gsn.Dial("tcp", ":8088", &MyBehavior{}, nil)
if err != nil {
fmt.Println(err.Error())
return
}
//TODO
}
说明
1.数据格式
数据格式为:
|--4b--|------|
|-包头-|--数据--|
没错,就是这么简单。 在发包的时候需要加上包头,包头表示总长(包含自己)
比如一个 12 个字节的数据包。
[ 0,0,0,12, 0,0,0,0,0,0,0,0 ]
前四位 0 0 0 12 表示该包总长度为 12 个字节,后 8 个 0 则为该包的数据
对于默认的 4b 包头来说,所能发送最大包长为 429496729 字节
如果需要调整包头,可以使用 SetHeadSize 方法。暂时支持 1b,2b,4b,8b 长度的包头
package main
import (
"fmt"
"github.com/olocat/gsn"
)
func main() {
listener, err := gsn.ListenTCP(":8088", nil, nil)
if err != nil {
fmt.Println(err.Error())
return
}
listener.SetHeadSize(gsn.FourWordHeadSize) // 包头改成 8b
listener.Start()
// 解包时也需要设计包头
pack := gsn.NewBinPacker()
pack.SetHeadSize(gsn.FourWordHeadSize) // 包头改成 8b
}
未来计划
- 优雅的退出
- protobuf 示例
- 心跳示例