英文:
Simulate a tcp connection in Go
问题
在Go语言中,TCP连接(net.Conn)是一个io.ReadWriteCloser。我想通过模拟TCP连接来测试我的网络代码。我有两个要求:
- 要读取的数据存储在一个字符串中
- 每当写入数据时,我希望它被存储在某种缓冲区中,以便我以后可以访问它
是否有适合这种需求的数据结构,或者有没有简单的方法来创建一个?
英文:
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:
- the data to be read is stored in a string
- 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
传递给服务器。Read
,Write
和Close
函数只是简单地代理到管道末端的服务器的函数。
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()
等)
在您的测试中,您可以通过读取和写入ClientReader
和ClientWriter
字段来扮演客户端的角色:
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。
您只需要模拟那些会增加测试时间、阻止测试并行运行(使用共享资源,如硬编码的文件名)或可能导致故障的内容(您可能会耗尽连接限制或端口,但在大多数情况下,这不是一个问题,当您在隔离环境中运行测试时)。
不模拟的优点是更精确地测试您想要使用真实对象进行的测试。
而不是模拟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.
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())
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论