Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simulate a tcp connection in Go

Tags:

networking

tcp

go

In Go, a TCP connection (net.Conn) is a io.ReadWriteCloser. I'd like to test my network code by simulating a TCP connection. There are two requirements that I have:

  1. the data to be read is stored in a string
  2. whenever data is written, I'd like it to be stored in some kind of buffer which I can access later

Is there a data structure for this, or an easy way to make one?

like image 777
marketer Avatar asked Dec 29 '09 21:12

marketer


People also ask

What is TCP in Golang?

TCP Connection in Go We can use the Dial function to create a connection to the server from a client. Meanwhile, the Listen function starts a connection server. The Accept method waits for an incoming request and returns a connection. We can handle multiple requests concurrently with Goroutines.

Which of the following function is used to open a TCP connection to server?

A client application can open a TCP/IP connection to a server application, running just about anywhere in the world.

What is a concurrent TCP server?

Traditionally, a concurrent TCP server calls fork to spawn a child to handle each client. This allows the server to handle numerous clients at the same time, one client per process.


2 Answers

EDIT: I've rolled this answer into a package which makes things a bit simpler - see here: https://github.com/jordwest/mock-conn


While Ivan's solution will work for simple cases, keep in mind that a real TCP connection is actually two buffers, or rather pipes. For example:

 Server   |   Client
 ---------+---------
  reads <===  writes
 writes ===>  reads

If you use a single buffer that the server both reads from and writes to, you could end up with the server talking to itself.

Here's a solution that allows you to pass a MockConn type as a ReadWriteCloser to the server. The Read, Write and Close functions simply proxy through to the functions on the server's end of the pipes.

type MockConn struct {
    ServerReader *io.PipeReader
    ServerWriter *io.PipeWriter

    ClientReader *io.PipeReader
    ClientWriter *io.PipeWriter
}

func (c MockConn) Close() error {
    if err := c.ServerWriter.Close(); err != nil {
        return err
    }
    if err := c.ServerReader.Close(); err != nil {
        return err
    }
    return nil
}

func (c MockConn) Read(data []byte) (n int, err error)  { return c.ServerReader.Read(data) }
func (c MockConn) Write(data []byte) (n int, err error) { return c.ServerWriter.Write(data) }

func NewMockConn() MockConn {
    serverRead, clientWrite := io.Pipe()
    clientRead, serverWrite := io.Pipe()

    return MockConn{
        ServerReader: serverRead,
        ServerWriter: serverWrite,
        ClientReader: clientRead,
        ClientWriter: clientWrite,
    }
}

When mocking a 'server' connection, simply pass the MockConn in place of where you would use the net.Conn (this obviously implements the ReadWriteCloser interface only, you could easily add dummy methods for LocalAddr() etc if you need to support the full net.Conn interface)

In your tests you can act as the client by reading and writing to the ClientReader and ClientWriter fields as needed:

func TestTalkToServer(t *testing.T) {
    /*
     * Assumes that NewMockConn has already been called and
     * the server is waiting for incoming data
     */

    // Send a message to the server
    fmt.Fprintf(mockConn.ClientWriter, "Hello from client!\n")

    // Wait for the response from the server
    rd := bufio.NewReader(mockConn.ClientReader)
    line, err := rd.ReadString('\n')

    if line != "Hello from server!" {
        t.Errorf("Server response not as expected: %s\n", line)
    }
}
like image 73
Jordan Avatar answered Sep 19 '22 16:09

Jordan


No idea if this existed when the question was asked, but you probably want net.Pipe() which provides you with two full duplex net.Conn instances which are linked to each other

like image 39
Jeff Jenkins Avatar answered Sep 21 '22 16:09

Jeff Jenkins