TCP server application, much CPU usage [solved]

Hey there, I’m trying to use the below code in a TCP server application, it goes fine, but CPU usage reaches 100%. Could anyone help me?

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
}

Hi. One thing which you don’t have to do is to define ScanCRLF because the default Split-function removes extra \r on end of lines. Could you try to post the code again so &, >, " isn’t replaced with & ,> and " ?

1 Like

I tried 3 times to post but even it being inside [ code ] [ / code ] tag, it’s showing up like that, I guess that must be a bug in the forum script.

Try using backtick * 3 + go like this ```go on a line before the code and ``` on a line after the code. Then you also gets nice formatting with bold keywords etc.

Now my code is like below, processing stopped reaching 100% but it’s even not normal reaching over 40% with only 1 message, for every string I get, it reaches over 40% cpu usage;

Below where I changed;

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()
}

And below the entire code;

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
}

Appreciate any help.

I suggest you to use a small delay in your loops (eg. 100ms, especially in the one from listen()) to avoid overloading the processor.

This server is being developed to receive dozens of thousands messages from mobile phones, each cel phone sends a message /minute, if I put a delay on the tcp server, i’ll lose too many messages while it’s on sleep.

And the problem is not that yet, I got only 1 cel phone sending a 80 byte string every minute.

Well, doing procesor intensive tasks in a loop definitely overload the machine. Instead processing in real time the request i think a better aproach is to queue the requests and post-process with go routines, or simply throw the request directly to a go routine.

1 Like

I tried changing the client listen to this

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)
	}
}

And it prints both message and Rec but on my macbook pro (cheapest previous year variant). The cpu usage while being accessed by one telnet client did never make the value go above 0%. One other thought. If you plan on accessing the server from the client go routines make sure you handle this so the go can call the same funtion in parallell.

2 Likes

Have you tried closing client cmd window while connected to the server? Server crashes instantly…

Hey @leogazio,

There are a few changes you might want to consider for your listen function (Please see the comments). The crash is because you aren’t catching the error, which in this case is an EOF error when the client disconnects - You can handle an actual error case there if you like because it may not always be an EOF error, but this is just for example:

// 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()
}

Edit: Also just noticed @johandalabacka’ last reply which uses scanner which is a good option, however make sure after the loop you check for scanner errors too if you use that solution (P.s, you still want to make sure you’re closing client connections):

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

2 Likes

Hey there I solved my problem, special thanks to Benjamin and @johandalabacka.

Ty.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.