Search code examples
jsongotcprpc

Go - TCP Client Often Misses Secondary JSON Reply


I'm trying to write a TCP client that sends out a JSON RPC to a locally hosted TCP server. The server should immediately return two replies. The TCP server is hosted on port 60000.

This is what the client code looks like:

package main

import (
    "fmt"
    "log"
    "bufio"
    "net"
)

func main() {
    d := net.Dialer{ }
    c, err := d.Dial("tcp", "127.0.0.1:60000")

    if err != nil {
        log.Println(err)
        return
    }

    data := `{"id": 1,"method":"mining.configure","params": [["version-rolling"],{"version-rolling.mask": "1fffe000","version-rolling.min-bit-count": 16}]}`

    fmt.Fprintf(c, data+"\n")

    for {
        message, _ := bufio.NewReader(c).ReadString('\n')
        fmt.Println(message)
    }   
}

This is what I'm sending out ( the "data" variable )

{
 "id": 1,
 "method":"mining.configure",
 "params": [["version-rolling"],
           {"version-rolling.mask": "1fffe000",
            "version-rolling.min-bit-count": 16}]
}

This is the expected reply:

{"error":null,"id":1,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"}}
{"id":null,"method":"mining.set_version_mask","params":["1fffe000"]}

Most of the time I only get the first reponse (has the result "field") without the second JSON that has the "method" field. Sometimes I get both.

But this is the usual reply:

{"error":null,"id":1,"result":{"version-rolling":true,"version-rolling.mask":"1fffe000"}}

I know that when code becomes non-deterministic like this it's because of an asynchronous issue going on. But every beginner tutorial I encountered teaches this exact structure for writing a TCP client. What's missing from it? I've tested the same RPC call to the server extensively with Python with the Twisted framework and I get back the both replies 100% of the time. This makes me certain the issue is within my client code and not the server.


Solution

  • Depending on the timing that data is received, the call to ReadString can buffer data past the \n. The code in the question discards the reader and data buffered in the reader on each iteration of the loop.

    To avoid discarding data, create the reader once and reuse inside the loop:

    r := bufio.NewReader(c)
    for {
        message, _ := r.ReadString('\n')
        fmt.Println(message)
    }