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