tcp 粘包及处理 go 和swoole
tcp粘包形成的原因
- tcp在发送和接收时会有个缓存,当短时间内发送大量小数据包时,会将数据线缓存起来,等达到一定数量才会发送,接收时也是。如果短时间发送大量数据,则会发生截断问题
tcp粘包处理
- 发送数据时会先拼接上数据长度,例如,用两个字节来存放数据长度,然后连接数据,接收时先取出长度,然后根据长度取出对应数据
客户端代码
func main() {
conn,_ := net.Dial("tcp","127.0.0.1:3333")
defer conn.Close()
msg := "处理tcp粘包"
msgLen := len(msg)
length := int16(msgLen) // int 转换成int16
pkg := new(bytes.Buffer) //创建写io
binary.Write(pkg,binary.BigEndian,length)
data := append(pkg.Bytes(),[]byte(msg)...) // 长度和消息拼接
fmt.Println("data",string(data))
//发送
conn.Write(data)
var data [1024]byte
n,_ := conn.Read(data[:])
fmt.Println("msg:",string(data[:n]))
}
服务端代码
// 服务端
func main() {
listen,err := net.Listen("tcp","127.0.0.1:3333")
if err != nil {
fmt.Println("err",err)
return
}
for {
// 接收客户端向服务端建立的连接
conn,err := listen.Accept() // 可以与客户端段建立连接,如果没有连接就挂起阻塞
if err != nil {
fmt.Println("err",err)
return
}
//处理客户连接
go handler(conn) // 可以利用协程处理,提高效率
}
}
func handler(c net.Conn) {
defer c.Close()
reader := bufio.NewReader(c)
for {
// 接收
msg,err := unpack(reader)
if err != nil {
fmt.Println("err",err)
break
}
fmt.Println("接收到数据:",string(data[:n]))
// 向客户端发送数据
c.Write([]byte("服务端"))
}
}
// 数据解析
func unpack(reader *bufio.Reader) (string,error) {
// 对字符串截取,长度数据
lenByte,_ := reader.Peek(2) //读取两个字节的数据
lengthBuff := bytes.NewBuffer(lenByte) // 建立缓冲区对数据进行读取
var length int16
err := binary.Read(lengthBuff,binary.BigEndian,&length)
/*
func Read(r io.Reader, order ByteOrder, data interface{}) error
从r中读取binary编码的数据并赋给data,data必须是一个指向定长值的指针或者定长值的切片。从r读取的字节使用order指定的字节序解码并写入data的字段里当写入结构体是,名字中有'_'的字段会被跳过,这些字段可用于填充(内存空间)。
第二个参数为要转换成的进制
**/
// 读取数据,读取的为2+msgLen,就是包长度和内容一起读取
if int16(reader.Buffered()) < length + 2 {
return "",errors.New("数据异常")
}
// 真正读取
pack := make([]byte,int(2+length)) // 创建一个切片,用于存储读取的数据,利用切片的长度去限定读取的长度??
_,err = reader.Read(pack)
if err != nil {
return "",err
}
fmt.Println("获取的数据",string(pack))
return string(pack),nil
}
swoole的话自带pack函数可以轻松的进行处理
//main.go 服务端 粘包部分服务端代码
func main() {
}
//swoole
<?php
$client = new Swoole\Client(SWOOLE_SOCK_TCP);
if(!$client->connect('127.0.0.1',3333,-1)){
exit("connect failed, error {$client->errCode}\n");
}
$context = "abcd";
$len = pack("n",strlen($context));
$send = $len.$context;
$client->send($send);
echo $clent->recv();
$client->close();
//swoole 作为服务端的代码
<?php
$server = new Swoole\Server('0.0.0.0',9501);
$server->set([
'open_length_check' => true,
'package_max_length' => 1*1024*1024,
'package_length_type' => 'n',
'package_length_offset' => 0,
'package_body_offset' => 2
]);
$server->on('connect',function($server,$fd){});
$server->on('receive',function($serv,$fd,$from_id,$data){
$serv->send($fd,"server")
});
$server->on('close',function(){});
$server->start();
网友评论