在Go中模拟TCP连接

huangapple go评论147阅读模式
英文:

Simulate a tcp connection in Go

问题

在Go语言中,TCP连接(net.Conn)是一个io.ReadWriteCloser。我想通过模拟TCP连接来测试我的网络代码。我有两个要求:

  1. 要读取的数据存储在一个字符串中
  2. 每当写入数据时,我希望它被存储在某种缓冲区中,以便我以后可以访问它

是否有适合这种需求的数据结构,或者有没有简单的方法来创建一个?

英文:

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?

答案1

得分: 6

不知道在问题提出时是否存在这个功能,但你可能想要使用net.Pipe(),它可以为你提供两个全双工的net.Conn实例,这两个实例相互连接。

英文:

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

答案2

得分: 5

EDIT: 我已经将这个答案整合到一个包中,使事情变得更简单 - 请参见这里:https://github.com/jordwest/mock-conn


尽管Ivan的解决方案对于简单情况是有效的,但请记住,一个真正的TCP连接实际上是两个缓冲区,或者说是两个管道。例如:

 服务器   |   客户端
 ---------+---------
  读取 <===  写入
 写入 ===>  读取

如果您使用一个服务器既从中读取又向其中写入的单个缓冲区,可能会导致服务器与自身对话。

这里有一个解决方案,允许您将MockConn类型作为ReadWriteCloser传递给服务器。ReadWriteClose函数只是简单地代理到管道末端的服务器的函数。

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

在模拟一个“服务器”连接时,只需将MockConn传递给您将使用net.Conn的位置(这显然只实现了ReadWriteCloser接口,如果需要支持完整的net.Conn接口,您可以轻松添加虚拟方法LocalAddr()等)

在您的测试中,您可以通过读取和写入ClientReaderClientWriter字段来扮演客户端的角色:

func TestTalkToServer(t *testing.T) {
    /*
     * 假设NewMockConn已经被调用,并且
     * 服务器正在等待传入的数据
     */

    // 向服务器发送一条消息
	fmt.Fprintf(mockConn.ClientWriter, "Hello from client!\n")

    // 等待服务器的响应
    rd := bufio.NewReader(mockConn.ClientReader)
	line, err := rd.ReadString('\n')

    if line != "Hello from server!" {
        t.Errorf("服务器响应与预期不符:%s\n", line)
    }
}
英文:

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

答案3

得分: 4

为什么不使用bytes.Buffer?它是一个io.ReadWriter,并且有一个String方法可以获取存储的数据。如果你需要将其变为io.ReadWriteCloser,你可以定义自己的类型:

type CloseableBuffer struct {
    bytes.Buffer
}

并定义一个Close方法:

func (b *CloseableBuffer) Close() error {
    return nil
}
英文:

Why not using bytes.Buffer? It's an io.ReadWriter and has a String method to get the stored data. If you need to make it an io.ReadWriteCloser, you could define you own type:

type CloseableBuffer struct {
    bytes.Buffer
}

and define a Close method:

func (b *CloseableBuffer) Close() error {
    return nil
}

答案4

得分: 0

在大多数情况下,您不需要模拟net.Conn。

您只需要模拟那些会增加测试时间、阻止测试并行运行(使用共享资源,如硬编码的文件名)或可能导致故障的内容(您可能会耗尽连接限制或端口,但在大多数情况下,这不是一个问题,当您在隔离环境中运行测试时)。

不模拟的优点是更精确地测试您想要使用真实对象进行的测试。

https://www.accenture.com/us-en/blogs/software-engineering-blog/to-mock-or-not-to-mock-is-that-even-a-question

而不是模拟net.Conn,您可以编写一个模拟服务器,在测试中以goroutine方式运行它,并使用真实的net.Conn连接到它。

一个快速而简单的示例:

port := someRandomPort()
srv := &http.Server{Addr: port}
go func(msg string) {
    http.HandleFunc("/hello", myHandleFUnc)
    srv.ListenAndServe()
}
myTestCodeUsingConn(port)
srv.Shutdown(context.TODO())
英文:

In majority of the cases you do not need to mock net.Conn.

You only have to mock stuff that will add time to your tests, prevent tests from running in parallel (using shared resources like the hardcoded file name) or can lead to outages (you can potentially exhaust the connection limit or ports but in most of the cases it is not a concern, when you run your tests in isolation).

Not mocking has an advantage of more precise testing of what you want to test with a real thing.

https://www.accenture.com/us-en/blogs/software-engineering-blog/to-mock-or-not-to-mock-is-that-even-a-question

Instead of mocking net.Conn, you can write a mock server, run it in a goroutine in your test and connect to it using real net.Conn

A quick and dirty example:

port := someRandomPort()
srv := &http.Server{Addr: port}
go func(msg string) {
    http.HandleFunc("/hello", myHandleFUnc)
    srv.ListenAndServe()
}
myTestCodeUsingConn(port)
srv.Shutdown(context.TODO())

huangapple
  • 本文由 发表于 2009年12月30日 05:07:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/1976950.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定