在Go中模拟tcp连接

发布于 2024-08-16 11:15:49 字数 191 浏览 8 评论 0原文

在 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

请别遗忘我 2024-08-23 11:15:49

不知道在提出问题时是否存在这种情况,但您可能需要 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

薄荷港 2024-08-23 11:15:49

编辑:我已将这个答案打包到一个包中,这使事情变得更简单 - 请参阅此处:https://github.com/jordwest/mock-conn


虽然 Ivan 的解决方案适用于简单的情况,但请记住,真正的 TCP 连接实际上是两个缓冲区,或者更确切地说是管道。例如:

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

如果您使用服务器既可以读取又可以写入的单个缓冲区,则最终可能会导致服务器自言自语。

这里有一个解决方案,允许您将 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 接口,您可以轻松添加LocalAddr() 的虚拟方法(如果您需要支持完整的 net.Conn 接口))

在测试中,您可以通过读取和写入 来充当客户端根据需要 >ClientReaderClientWriter 字段:

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

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)
    }
}
调妓 2024-08-23 11:15:49

为什么不使用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
}
丑丑阿 2024-08-23 11:15:49

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

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

不模拟的优点是可以更精确地测试您想要用真实事物测试的内容。

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

您可以编写一个模拟服务器,在测试中的 goroutine 中运行它,然后使用真实的 net.Conn 连接到它,而不是模拟 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())
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文