公司项目需要对接嵌入式设备,底层stm32使用的是TCP网络协议,go语言中原生TCP编程还是比较简单的,像网络方面的编程,其实很多操作都比较繁复,在编写时,就希望能够有类似的框架性质的库帮助统一所有网络的代码,以及减轻网络编写的负担。

找到一个项目,link

就像它README所说的,它不是一个完整网络层也不是一个框架,它只是一个脚手架,它可以帮助你快速的实现出你所需要的网络层或者通讯框架,帮你约束网络层的实现方式,不至于用不合理的方式实现网络层,除此之外它不会管更多的事情。

这样的话其实也就足够使用了,毕竟我要的功能并不复杂,仅需要为了屏蔽掉一些细节问题,除此之外,好像也并不需要什么多余的东西了。

bug fix

仓库中有对json解码的原生支持,但其中似乎存在一些问题,这里补齐一下。

当client端发送的数据为空时,这里会传入nil,也就会出现panic,需要判断body是否为空。

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
--- a/codec/json.go
+++ b/codec/json.go
@@ -2,7 +2,6 @@ package codec

import (
"encoding/json"
- "errors"
"io"
"reflect"

@@ -74,18 +73,10 @@ func (c *jsonCodec) Receive() (interface{}, error) {
return nil, err
}
var body interface{}
+
if in.Head != "" {
if t, exists := c.p.types[in.Head]; exists {
body = reflect.New(t).Interface()
+ } else {
+ return nil, errors.New("Receive Json have no Head match, Close connection")
}
+ }
+ if in.Body == nil {
+ return nil, errors.New("Receive Json have no Body field, Close connection")
}
err = json.Unmarshal(*in.Body, &body)

另外还有一处编译的地方,对于win32位来说会报错的地方。

math.MaxUint32会超出win32位int的大小,当然这是在编译时就会报错(并不会直接按照类型大小截取大小,反而会直接丢出错误)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
--- a/codec/fixlen.go
+++ b/codec/fixlen.go
@@ -54,11 +54,17 @@ func FixLen(base link.Protocol, n int, byteOrder binary.ByteOrder, maxRecv, maxS
byteOrder.PutUint16(b, uint16(size))
}
case 4:
- if maxRecv > math.MaxUint32 {
- maxRecv = math.MaxUint32
+ // if maxRecv > math.MaxUint32 {
+ // maxRecv = math.MaxUint32
+ // }
+ // if maxSend > math.MaxUint32 {
+ // maxSend = math.MaxUint32
+ // }
+ if maxRecv > math.MaxInt32 {
+ maxRecv = math.MaxInt32
}
- if maxSend > math.MaxUint32 {
- maxSend = math.MaxUint32
+ if maxSend > math.MaxInt32 {
+ maxSend = math.MaxInt32
}

自定义codec

和底层的通信采用字节流,需要自定义协议进行操作。这里只需要参照codec里面的协议,自定义一个协议就可以了。

codec其实很好理解,相当于socket的句柄传入包中,由包中函数进行自定义处理数据。

需要注意的一点是:io.Reader类的read函数传入的参数,是一个len不为零的byte的数组,也就是有接收数据的空间,需要提前分配,这里是我在编写的时候不注意的地方,测试程序的read老是过不去。

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package codec

import (
"errors"
"io"

"gitlab.com/link"
)

type ByteProtocol struct {
//data []byte
}

func (b *ByteProtocol) NewCodec(rw io.ReadWriter) (link.Codec, error) {
codec := &byteCodec{
p: b,
r: rw,
w: rw,
}

codec.closer, _ = rw.(io.Closer)
return codec, nil
}

func Byte() *ByteProtocol {
return &ByteProtocol{
//data: make([]byte, 0),
}
}

type byteCodec struct {
r io.Reader
w io.Writer
p *ByteProtocol
closer io.Closer
}

//
func (c *byteCodec) Receive() (interface{}, error) {

recvData := make([]byte, 4092)

cnt, err := c.r.Read(recvData)

return recvData[:cnt], err
}

func (c *byteCodec) Send(msg interface{}) error {

b, ok := msg.([]byte)
if !ok {
return errors.New("Send Byte Format Error")
}

_, err := c.w.Write(b)

return err
}

func (c *byteCodec) Close() error {
if c.closer != nil {
return c.closer.Close()
}
return nil
}

以上还可以将reader改为bufio.Readerbufio.Writer,但记住,带缓冲的IO,在写完数据后,一定要去记得显示调用Flush()函数,不然数据不会写入的。

建议使用bufio,例如可以使用peek函数,读取协议包(自定义)中实际大小,通过实际的大小去读取data数据,可以设计用来解决TCP沾包的问题。

Receive()中使用for循环读取包,当然这里调用Receive()就要用到Goroutine,然后数据通信则使用channel进行通信,我在实际的程序中就是这样操作的。