Golang TCP服务器应用CPU占用过高问题已解决

Golang TCP服务器应用CPU占用过高问题已解决 我正在尝试在TCP服务器应用中使用以下代码,运行正常但CPU使用率达到了100%。有人能帮我吗?

package main

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"log"
	"net"
	"strings"
)

// Client holds info about connection
type TCPClient struct {
	conn   net.Conn
	Server *server
}

// TCP server
type server struct {
	clients                  []*TCPClient
	address                  string // Address to open connection: localhost:9999
	onNewClientCallback      func(c *TCPClient)
	onClientConnectionClosed func(c *TCPClient, err error)
	onNewMessage             func(c *TCPClient, message string)
}

// dropCR drops a terminal \r from the data.
func dropCR(data []byte) []byte {
	if len(data) > 0 && data[len(data)-1] == '\r' {
		return data[0 : len(data)-1]
	}
	return data
}

func ScanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.Index(data, []byte{'\r'}); i >= 0 {
		// We have a full newline-terminated line.
		return i + 1, dropCR(data[0:i]), nil
	}
	// If we're at EOF, we have a final, non-terminated line. Return it.
	if atEOF {
		return len(data), dropCR(data), nil
	}
	// Request more data.
	return 0, nil, nil
}

// Read client data from channel
func (c *TCPClient) listen() {


	for {
		message, _ := bufio.NewReader(c.conn).ReadString('\r')
		Rec := strings.TrimSpace(strings.ToUpper(string(message)))
		c.Server.onNewMessage(c, strings.Replace(Rec, "\r", "", -1))
	}

	go c.listen()
}

// Send text message to client
func (c *TCPClient) Send(message string) error {
	hexbytes, _ := hex.DecodeString(message)
	_, err := c.conn.Write([]byte(hexbytes))
	return err
}

func (c *TCPClient) Conn() net.Conn {
	return c.conn
}

func (c *TCPClient) Close() error {
	return c.conn.Close()
}

// Called right after server starts listening new client
func (s *server) OnNewClient(callback func(c *TCPClient)) {
	s.onNewClientCallback = callback
}

// Called right after connection closed
func (s *server) OnClientConnectionClosed(callback func(c *TCPClient, err error)) {
	s.onClientConnectionClosed = callback
}

// Called when Client receives new message
func (s *server) OnNewMessage(callback func(c *TCPClient, message string)) {
	s.onNewMessage = callback
}

// Start network server
func (s *server) Listen() {
	listener, err := net.Listen("tcp", s.address)
	if err != nil {
		log.Fatal("Error starting TCP server.")
	}
	defer listener.Close()

	for {
		conn, _ := listener.Accept()
		client := &TCPClient{
			conn:   conn,
			Server: s,
		}
		go client.listen()
		s.onNewClientCallback(client)
	}
}

// Creates new tcp server instance
func NewTCP(address string) *server {
	server := &server{
		address: address,
	}

	server.OnNewClient(func(c *TCPClient) {})
	server.OnNewMessage(func(c *TCPClient, message string) {})
	server.OnClientConnectionClosed(func(c *TCPClient, err error) {})

	return server
}


更多关于Golang TCP服务器应用CPU占用过高问题已解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html

11 回复

你是否尝试过在连接到服务器时关闭客户端命令窗口?服务器会立即崩溃…

更多关于Golang TCP服务器应用CPU占用过高问题已解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我已经解决了我的问题,特别感谢Benjamin和@johandalabacka

谢谢。

我建议您在循环中加入一个小的延迟(例如100毫秒,特别是在listen()中的循环),以避免处理器过载。

尝试在代码前一行使用三个反引号加go,像这样 ```go,然后在代码后一行使用三个反引号。这样你还能获得漂亮的关键字加粗等格式效果。

我已经尝试发布了3次,但即使内容放在[code][/code]标签里,显示效果还是这样,我猜这应该是论坛脚本的一个bug。

func main() {
    fmt.Println("hello world")
}

在循环中执行处理器密集型任务确实会导致机器过载。我认为更好的方法不是实时处理请求,而是将请求加入队列并使用 Go 协程进行后处理,或者直接将请求传递给 Go 协程处理。

这个服务器正在开发用于接收来自手机的数万条消息,每部手机每分钟发送一条消息。如果我在TCP服务器上设置延迟,在休眠期间会丢失太多消息。

问题还不止于此,目前我只有一部手机每分钟发送一个80字节的字符串。

你好。有件事你其实不需要做,就是定义ScanCRLF,因为默认的Split函数会移除行尾多余的\r字符。你能重新发布一下代码吗?这样&、>、"就不会被替换成&、>和"了?

我尝试将客户端监听改为这样

func (c *TCPClient) listen() {

	scanner := bufio.NewScanner(c.conn)
	for scanner.Scan() {
		message := scanner.Text()
		fmt.Println(message)
		Rec := strings.TrimSpace(strings.ToUpper(message))
		fmt.Println(Rec)
		c.Server.onNewMessage(c, Rec)
	}
}

它同时打印了消息和Rec,但在我的MacBook Pro(去年最便宜的型号)上,即使被一个telnet客户端访问,CPU使用率也从未超过0%。另一个想法是,如果你计划从客户端go协程访问服务器,请确保处理好这个问题,这样go就可以并行调用相同的函数。

针对你的 listen 函数,有几个可能需要考虑修改的地方(请查看注释)。程序崩溃是因为你没有捕获错误,这里的情况是客户端断开连接时产生的 EOF 错误 - 如果你愿意,可以在这里处理实际的错误情况,因为并不总是 EOF 错误,但这只是个示例:

// Read client data from channel
func (c *TCPClient) listen() {
	// First: Want to close when done.
	defer c.Close()

	for {
		message, err := bufio.NewReader(c.conn).ReadString('\r')
		// Second: Need to catch this error or it will continually read.
		if err != nil {
			return
		}
		Rec := strings.TrimSpace(strings.ToUpper(string(message)))
		c.Server.onNewMessage(c, strings.Replace(Rec, "\r", "", -1))
	}

	// This: This isn't needed.
	// go c.listen()
}

另外刚刚注意到最后一个回复中使用了 scanner,这是个不错的选择,但如果你使用那个解决方案,请确保在循环后也检查 scanner 错误(顺便说一句,你仍然需要确保关闭客户端连接):

if err := scanner.Err(); err != nil { ... }

在您的TCP服务器代码中,CPU占用率达到100%的主要原因是listen()方法中的无限循环实现方式有问题。每次循环都创建新的bufio.Reader,并且没有正确处理读取错误和连接关闭。

以下是修复后的关键代码部分:

// Read client data from channel
func (c *TCPClient) listen() {
    defer c.conn.Close()
    
    reader := bufio.NewReader(c.conn)
    
    for {
        message, err := reader.ReadString('\r')
        if err != nil {
            if err == io.EOF {
                log.Printf("Client %s disconnected", c.conn.RemoteAddr())
            } else {
                log.Printf("Read error: %v", err)
            }
            c.Server.onClientConnectionClosed(c, err)
            return
        }
        
        Rec := strings.TrimSpace(strings.ToUpper(string(message)))
        c.Server.onNewMessage(c, strings.Replace(Rec, "\r", "", -1))
    }
}

主要修改点:

  1. 移除循环内的重复Reader创建:在循环外部创建bufio.NewReader(c.conn),避免每次循环都创建新Reader
  2. 添加错误处理:检查ReadString的返回错误,当连接关闭或出现错误时退出循环
  3. 移除递归调用:删除了go c.listen()这行,防止无限递归创建goroutine
  4. 添加连接关闭处理:使用defer确保连接正确关闭

完整的服务器启动示例:

func main() {
    server := NewTCP("localhost:9999")
    
    server.OnNewClient(func(c *TCPClient) {
        log.Printf("New client connected: %s", c.Conn().RemoteAddr())
    })
    
    server.OnNewMessage(func(c *TCPClient, message string) {
        log.Printf("Received message from %s: %s", c.Conn().RemoteAddr(), message)
    })
    
    server.OnClientConnectionClosed(func(c *TCPClient, err error) {
        log.Printf("Client disconnected: %s, error: %v", c.Conn().RemoteAddr(), err)
    })
    
    server.Listen()
}

这些修改将显著降低CPU使用率,因为现在每个连接只有一个Reader实例,并且当连接关闭时会正确退出循环,避免了无限循环和资源泄漏。

回到顶部