优化网络代码

huangapple go评论68阅读模式
英文:

Optimising network code

问题

我有一个接收来自多个TCP客户端的数据的Go服务器。数据的格式是自定义的,并且结束分隔符可能会出现在字节流中,因此使用字节填充来解决此问题。

我正在寻找代码中的热点,并且发现了一个非常大的热点,我相信可以通过提高效率来改进,但目前我还不确定如何做,因为提供的Go函数。

下面是代码,pprof显示热点是popPacketFromBuffer命令。它在每个字节接收后查看当前缓冲区,并查找独立的结束分隔符。如果连续有两个结束分隔符,则表示它在数据包本身内。

我尝试使用ReadBytes()而不是ReadByte(),但是看起来我需要指定一个分隔符,我担心这样会在数据流中截断一个数据包。而且无论如何,这样做是否比我目前的方法更高效?

popPacketFromBuffer函数中,for循环是热点。

有什么想法吗?

// 从通道读取客户端数据
func (c *Client) listen() {

	reader := bufio.NewReader(c.conn)

	clientBuffer := new(bytes.Buffer)

	for {
		c.conn.SetDeadline(time.Now().Add(c.timeoutDuration))

		byte, err := reader.ReadByte()

		if err != nil {
			c.conn.Close()
			c.server.onClientConnectionClosed(c, err)
			return
		}

		wrErr := clientBuffer.WriteByte(byte)
		if wrErr != nil {
			log.Println("Write Error:", wrErr)
		}

		packet := popPacketFromBuffer(clientBuffer)
		if packet != nil {
			c.receiveMutex.Lock()
			packetSize := uint64(len(packet))
			c.bytesReceived += packetSize
			c.receiveMutex.Unlock()

			packetBuffer := bytes.NewBuffer(packet)

			b, err := uncompress(packetBuffer.Bytes())
			if err != nil {
				log.Println("Unzip Error:", err)
			} else {
				c.server.onNewMessage(c, b)
			}
		}

	}
}

func popPacketFromBuffer(buffer *bytes.Buffer) []byte {

	bufferLength := buffer.Len()

	if bufferLength >= 125000 { // 1MB in bytes is roughly this
		log.Println("Buffer is too large ", bufferLength)
		buffer.Reset()
		return nil
	}

	tempBuffer := buffer.Bytes()
	length := len(tempBuffer)

	// 在零长度缓冲区提交时返回
	if length == 0 {
		return nil
	}

	endOfPacket := -1

	// 通过查找分隔符的实例来确定endOfPacket位置
	for i := 0; i < length-1; i++ {
		if tempBuffer[i] == endDelimiter {
			if tempBuffer[i+1] == endDelimiter {
				i++
			} else {
				// 找到一个单独的分隔符,将其视为数据包的结束
				endOfPacket = i - 2
				break
			}
		}
	}

	if endOfPacket != -1 {
		// 获取提供的数据包的内容
		extractedPacket := buffer.Bytes()

		// 提取最后一个字节,因为我们在读取操作中使用了超级贪婪的方式来检查填充
		carryByte := extractedPacket[len(extractedPacket)-1]

		// 现在我们从中提取了一个数据包,清除主缓冲区
		buffer.Reset()

		// 将carryByte添加到我们的新缓冲区中
		buffer.WriteByte(carryByte)

		// 确保数据包以有效的startDelimiter开头
		if extractedPacket[0] != startDelimiter {
			log.Println("Popped a packet without a valid start delimiter")
			return nil
		}

		// 删除开始和结束标记
		slice := extractedPacket[1 : len(extractedPacket)-2]

		return deStuffPacket(slice)
	}

	return nil
}
英文:

I have a Go server that takes input from a number of TCP clients that stream in data. The format is a custom format and the end delimiter could appear within the byte stream so it uses bytes stuffing to get around this issue.

I am looking for hotspots in my code and this throws up a HUGE one and I'm sure it could be made more efficient but I'm not quite sure how at the moment given the provided Go functions.

The code is below and pprof shows the hotspot to be popPacketFromBuffer command. This looks at the current buffer, after each byte has been received and looks for the endDelimiter on it's own. If there is 2 of them in a row then it is within the packet itself.

I did look at using ReadBytes() instead of ReadByte() but it looks like I need to specify a delimiter and I'm fearful that this will cut off a packet mid stream? And also in any case would this be more efficient than what I am doing anyway?

Within the popPacketFromBuffer function it is the for loop that is the hotspot.

Any ideas?

// Read client data from channel
func (c *Client) listen() {
reader := bufio.NewReader(c.conn)
clientBuffer := new(bytes.Buffer)
for {
c.conn.SetDeadline(time.Now().Add(c.timeoutDuration))
byte, err := reader.ReadByte()
if err != nil {
c.conn.Close()
c.server.onClientConnectionClosed(c, err)
return
}
wrErr := clientBuffer.WriteByte(byte)
if wrErr != nil {
log.Println(&quot;Write Error:&quot;, wrErr)
}
packet := popPacketFromBuffer(clientBuffer)
if packet != nil {
c.receiveMutex.Lock()
packetSize := uint64(len(packet))
c.bytesReceived += packetSize
c.receiveMutex.Unlock()
packetBuffer := bytes.NewBuffer(packet)
b, err := uncompress(packetBuffer.Bytes())
if err != nil {
log.Println(&quot;Unzip Error:&quot;, err)
} else {
c.server.onNewMessage(c, b)
}
}
}
}
func popPacketFromBuffer(buffer *bytes.Buffer) []byte {
bufferLength := buffer.Len()
if bufferLength &gt;= 125000 { // 1MB in bytes is roughly this
log.Println(&quot;Buffer is too large &quot;, bufferLength)
buffer.Reset()
return nil
}
tempBuffer := buffer.Bytes()
length := len(tempBuffer)
// Return on zero length buffer submission
if length == 0 {
return nil
}
endOfPacket := -1
// Determine the endOfPacket position by looking for an instance of our delimiter
for i := 0; i &lt; length-1; i++ {
if tempBuffer[i] == endDelimiter {
if tempBuffer[i+1] == endDelimiter {
i++
} else {
// We found a single delimiter, so consider this the end of a packet
endOfPacket = i - 2
break
}
}
}
if endOfPacket != -1 {
// Grab the contents of the provided packet
extractedPacket := buffer.Bytes()
// Extract the last byte as we were super greedy with the read operation to check for stuffing
carryByte := extractedPacket[len(extractedPacket)-1]
// Clear the main buffer now we have extracted a packet from it
buffer.Reset()
// Add the carryByte over to our new buffer
buffer.WriteByte(carryByte)
// Ensure packet begins with a valid startDelimiter
if extractedPacket[0] != startDelimiter {
log.Println(&quot;Popped a packet without a valid start delimiter&quot;)
return nil
}
// Remove the start and end caps
slice := extractedPacket[1 : len(extractedPacket)-2]
return deStuffPacket(slice)
}
return nil
}

答案1

得分: 2

看起来你每次从连接接收到每个字节时都调用了popPacketFromBuffer()函数。然而,popPacketFromBuffer()函数会复制整个缓冲区,并且每次都会检查每个字节是否为分隔符。也许这样做有些过于繁琐了。对我来说,你不需要使用循环。

for i := 0; i < length-1; i++ {
    if tempBuffer[i] == endDelimiter {
        if tempBuffer[i+1] == endDelimiter {
            i++
        } else {
            // 我们找到了一个单个的分隔符,所以认为这是一个数据包的结尾
            endOfPacket = i - 2
            break
        }
    }
}

popPacketFromBuffer()函数中,也许只需要测试最后两个字节是否满足条件:

if (buffer[len(buffer)-2] == endDelimiter) && (buffer[len(buffer)-1] != endDelimiter){
    // 这是一个数据包
}

这样就足够了。

英文:

Looks like you call popPacketFromBuffer() each time every single byte received from connection. However popPacketFromBuffer() copy hole buffer and inspect for delimeters every byte each tyme. Maybe this is overwhelming. For me you don't need loop

for i := 0; i &lt; length-1; i++ {
if tempBuffer[i] == endDelimiter {
if tempBuffer[i+1] == endDelimiter {
i++
} else {
// We found a single delimiter, so consider this the end of a packet
endOfPacket = i - 2
break
}
}
}

in popPacketFromBuffer() Maybe instead of loop just testing last two bytes

if (buffer[len(buffer)-2] == endDelimiter) &amp;&amp; (buffer[len(buffer)-1] != endDelimiter){
//It&#39;s a packet
}

would be enough for purpose.

huangapple
  • 本文由 发表于 2017年1月26日 00:32:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/41856794.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定