伐柯伐柯*其則不遠*我覬之子*籩豆有踐

4 , 5月2018

Go读取PHP的TCP发包并拆包

之前有写过两篇关于PHP使用socket通信时收发包的一些问题。在很多场景下,异构通讯不一定使用SOA或者RP […]

之前有写过两篇关于PHP使用socket通信时收发包的一些问题。在很多场景下,异构通讯不一定使用SOA或者RPC,而是直接通讯。下面以Golang为例,当不使用时,简单的发包解包方式。

客户端依然使用之前的PHP客户端。

我们先看一下server的收包情况:

代码非常简单:

buff := make([]byte, 1024)
n, err := conn.Read(buff)
if err == io.EOF{
   continue
}

if n > 0{
   fmt.Println("[buffer]"  + string(buff))
}

php客户端调用结果为:

[buffer] d"{\"id\":1,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
d"{\"id\":2,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}" d"{\"id
\":3,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}" d"{\"id\":4,\"da
ta\":[0,1,2,3,4,5,6,7,8,9,10]}" d"{\"id\":5,\"data\":[0,1
,2,3,4,5,6,7,8,9,10]}" d"{\"id\":6,\"data\":[0,1,2,3,4,5,
6,7,8,9,10]}" d"{\"id\":7,\"data\":[0,1,2,3,4,5,6,7,8,9,1
0]}" d"{\"id\":8,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
d"{\"id\":9,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"

很明显,包黏在一起了。

go的io包提供了一个函数Readfull相当于加强了 此文 中提到的php_read函数。我们看一下使用同样的协议,go如何完成拆包:

header := make([]byte, 4)
rlen, err := conn.Read(header)
if err == io.EOF || rlen == 0 {
   continue
}

bodyLen := binary.BigEndian.Uint32(header)
fmt.Printf("header length:%d\n" , int32(bodyLen))

body := make([]byte, bodyLen)
_, _ = io.ReadFull(conn, body)

fmt.Printf("body:%s\n" , string(body))

由此可见,由于此函数的存在,我们毫不费力就可以解决粘包问题。比PHP方案简化不少。

我们可以测试一下:

header length:100
body:"{\"id\":0,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":1,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":2,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":3,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":4,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":5,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":6,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":7,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":8,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"
header length:100
body:"{\"id\":9,\"data\":[0,1,2,3,4,5,6,7,8,9,10]}"

那么这个Readfull是怎么实现的呢?实际上非常简单:

func ReadFull(r Reader, buf []byte) (n int, err error) {
return ReadAtLeast(r, buf, len(buf))
}

func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) {
if len(buf) < min {//装不下
return 0, ErrShortBuffer
}
for n < min && err == nil {
var nn int
nn, err = r.Read(buf[n:])//重复读,一直到填充完
n += nn
}
if n >= min {
err = nil
} else if n > 0 && err == EOF {
err = ErrUnexpectedEOF
}
return
}

当然,直接用PHP写tcp服务端还是很少见的,现在大多数都是用类似swoole之类的框架,他们也在框架层面上提供了解决方案。语言不够框架来凑,这不也挺好?

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

© 2011 - 2024 laijim.com