前言:
上篇文章向大家介绍怎么样用go实现一个最简单的socket服务器,接下来我们会讲到socket编程中最重要的事情--分包处理 已经把所有代码整合了,希望给个星星支持一下 microSocket。
分包处理的必要:
为什么我们需要分包处理呢,一个socket的连接就相当于一个水管,数据从里面源源不断的流出来,服务端就像一个水盆一样不断地接收数据并处理。
现在我们来考虑一个这么一个场景 客户端告诉服务端两个请求 ‘aaaaa' 'bbbbbb' 服务端当然就是接收数据啦,可是服务端心情好一下子接收了所有的数据,就相当于是 ’aaaaabbbbbb',这下好了服务端就把这个当做一个请求来处理了,要是不重要还好,要是第二条协议很重要,就gg了,如果服务端代码不够严谨可能整个逻辑都混乱了。
这个时候我们就需要一般逻辑来把我们服务端接收到的数据分开来确保每一块数据都是单独的一个包,这也就是我为什么说这是socket编程最重要的一个环节了。
分包处理的实现:
- 设置包头分割
- 设置标志位尾部分割
一般分包处理主要有以上两种,我们框架用到的是第一种,废话不多说,上代码
func (this *Msf) connHandle(conn net.Conn, sess *session) {
defer conn.Close()
var errs error
tempBuff := make([]byte, 0)
readBuff := make([]byte, 14)
data := make([]byte, 20)
for {
n, err := conn.Read(readBuff)
if err != nil {
return
}
tempBuff = append(tempBuff, readBuff[:n]...)
tempBuff, data, errs = Depack(tempBuff)//对缓冲区进行分包处理
if errs != nil {
return
}
if len(data) == 0 {
continue
}
do(data)//伪代码data是一个单独的一个包,可以进行逻辑处理了
}
}
我们把在上一篇文章对每个连接处理的函数里面加了一点小小的逻辑我来解释一下
首先我们比上次多定义了一个tempBuff,这个也很容易理解,我们所有的从socket读到的数据全部会先放到这里面,这样的话,假设我们一次性读到了‘aaaaabbbbbb',我们现在把所有的数据放到tempBuff,然后再一个个包的分割。就不会出现原来的问题了。
上面的代码我们用到了一个Depack() 函数我们来看看这个具体是怎么实现的
const (
CONSTHEADER = "testHeader"
CONSTHEADERLENGTH = 10
CONSTMLENGTH = 4
)
func Enpack(message []byte) []byte {
return append(append([]byte(CONSTHEADER), IntToBytes(len(message))...), message...)
}
func Depack(buff []byte) ([]byte, []byte, error) {
length := len(buff)
//如果包长小于header 就直接返回 因为接收的数据不完整
if length < CONSTHEADERLENGTH+CONSTMLENGTH {
return buff, nil, nil
}
//如果header不是 指定的header 说明此数据已经被污染 直接返回错误
if string(buff[:CONSTHEADERLENGTH]) != CONSTHEADER {
return []byte{}, nil, errors.New("header is not safe")
}
msgLength := BytesToInt(buff[CONSTHEADERLENGTH : CONSTHEADERLENGTH+CONSTMLENGTH])
if length < CONSTHEADERLENGTH+CONSTMLENGTH+msgLength {
return buff, nil, nil
}
data := buff[CONSTHEADERLENGTH+CONSTMLENGTH : CONSTHEADERLENGTH+CONSTMLENGTH+msgLength]
buffs := buff[CONSTHEADERLENGTH+CONSTMLENGTH+msgLength:]
return buffs, data, nil
}
//将int转成四个字节
func IntToBytes(n int) []byte {
x := int32(n)
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
//将四个字节转成int
func BytesToInt(b []byte) int {
bytesBuffer := bytes.NewBuffer(b)
var x int32
binary.Read(bytesBuffer, binary.BigEndian, &x)
return int(x)
}
Depack就是不断地if if if 哈哈哈 不过不用怂,就这么点逻辑我们来一点一点分析绝对稳,我也是小白,完全明白初学者的感受
- 第一点这是最重要的一点,我们认为tcp 数据是安全的,是有序的,符合预期的,所以一旦有非符合预期的数据产生,算了,这个数据我们不要了!就是这么刚!
- 我们应该约定一个包的结构,这样我们分包的时候才有依据,’testHeader10xxxxabcd‘这个就是我们约定的包,包头+包头长+正文长度+正文 ,那个xxxx就是占四个字节长度的 正文长度,我们通过正文长度来 判断一个包什么时候应该结束
- 我们能够保证的是,tempbuff 里面的数据 ,一开始肯定是 header ,因为client 肯定是按照包 来发送的,我们也是按照包来处理的
- 先判断tempbuff里面的数据是不是有一个包头的数据,如果没有,那就说明数据还没读取完整,直接返回 不做处理
- 当tempbuff 达到包头的长度,就验证一下,包头是不是事先约定好的,如果不是就说明数据已经被污染了直接返回 空,tempbuff的数据也清空
- 当满足上面所有的条件的时候,我们就去tempbuff里面截取 那个四个字节的正文长度,并把它转回 int类型 ,判断当前的包长是不是小于 包头长加正文长度,如果小于那还是说明当前的包是不完整的,依然是直接返回
- 如果没有小于,就说明,当前的tempbuff肯定有一个包以上,注意,这里可能有一个半包!然后我们就按照相应的长度截取 消息正文,并且返回内容 ,同时把tempbuff我们已经处理的包给去掉。
结束
至此我们一个分包的逻辑就结束了,要是还是有什么不理解或者意见的欢迎留言提问,大家一起探讨。
下篇文章我们会讲一个框架最重要的一部分’路由‘
https://www.jianshu.com/p/4de4edf00cf8
网友评论