Problem
I want to run a load test with a high number of requests per second. I have written a socket sender and a receiver in Go. The sender sends a lot of packets to port 7357, each one containing the current time in nanoseconds. The receiver listens in port 7357 and parses each message, computing the latency.
The problem is that when reading I get multiple packets in one conn.Read()
. I understand that this means that I am in fact sending multiple messages per packet: each conn.Write()
does not send a socket packet, but it waits for some time and then gets coalesced with the next (or the next few) before sending.
Question
How can I make sure that each conn.Write()
is sent individually through the socket as a separate packet? Note: I don't want to reinvent TCP, I just want to simulate the load from a number of external entities that send a message each.
Steps Taken
I have searched the documentation but there seems to be no conn.Flush()
or similar. I have tried using a buffered writer:
writer := bufio.NewWriter(conn)
...
bytes, err := writer.Write(message)
err = writer.Flush()
No errors, but still I get mixed packets at the receiving end. I have also tried doing a fake conn.Read()
of 0 bytes after every conn.Write()
, but it didn't work either. Sending a message terminator such as
does not seem to make any difference. Finally, Nagle algorithm is disabled by default, but I have called tcp.SetNoDelay(true)
for good measure.
In Node.js I managed to do the trick with a setImmediate()
after each socket.write()
: setImmediate()
waits for all I/O to finish before continuing. How can I do the same in Go so I get separate packets?
Code Snippets
Send:
func main() {
conn, _ := net.Dial("tcp", ":7357")
defer conn.Close()
for {
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
conn.Write([]byte(timestamp))
conn.Read(buff)
}
}
Receive:
func main() {
listen, _ := net.Listen("tcp4", ":7357")
defer listen.Close()
for {
conn, _ := listen.Accept()
go handler(conn)
}
}
func handler(conn net.Conn) {
defer conn.Close()
var buf = make([]byte, 1024)
for {
conn.Read(buf)
data := string(buf[:n])
timestamp, _ := strconv.ParseInt(data, 10, 64)
elapsed := timestamp - time.Now().UnixNano()
log.Printf("Elapsed %v", elapsed)
}
}
Error handling has been removed for legibility, but it is thoroughly checked in the actual code. It crashes when running the strconv.ParseInt()
the first time, with a value out of range
error since it receives a lot of timestamps coalesced.