之前有写过两篇关于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之类的框架,他们也在框架层面上提供了解决方案。语言不够框架来凑,这不也挺好?