sockets - 如何为 Go 中写入的每个连接发送单独的数据包?

标签 sockets networking go tcp

问题

我想运行一个每秒有大量请求的负载测试。我在 Go 中编写了一个套接字发送器和一个接收器。发送方向端口 7357 发送大量数据包,每个数据包都包含以纳秒为单位的当前时间。接收方在端口 7357 上监听并解析每条消息,计算延迟。

问题是在读取时我在一个 conn.Read() 中得到了多个数据包。我知道这意味着我实际上每个数据包发送多条消息:每个 conn.Write() 不发送套接字数据包,但它等待一段时间然后与下一个合并(或接下来的几个)在发送之前。

问题

如何确保每个 conn.Write() 都作为单独的数据包通过套接字单独发送? 注意:我不想重新发明 TCP,我只想模拟来自多个外部实体的负载,每个实体发送一条消息。

采取的步骤

我已经搜索了文档,但似乎没有 conn.Flush() 或类似内容。我试过使用缓冲编写器:

writer := bufio.NewWriter(conn)
...
bytes, err := writer.Write(message)
err = writer.Flush()

没有错误,但我仍然在接收端收到混合数据包。我也试过在每个 conn.Write() 之后做一个 0 字节的假 conn.Read(),但它也没有用。发送诸如 \r\n 之类的消息终止符似乎没有任何区别。最后,默认情况下禁用 Nagle 算法,但我调用了 tcp.SetNoDelay(true) 作为衡量标准。

在 Node.js 中,我设法在每个 socket.write() 之后使用 setImmediate() 来实现这个技巧: setImmediate()在继续之前等待所有 I/O 完成。我怎样才能在 Go 中执行相同的操作以便获得单独的数据包?

代码片段

发送:

func main() {
  conn, _ := net.Dial("tcp", ":7357")
  defer conn.Close()
  for {
    timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
    conn.Write([]byte(timestamp))
    conn.Read(buff)
  }
}

接收:

func main() {
  listen, _ := net.Listen("tcp4", ":7357")
  defer listen.Close()
  for {
    conn, _ := listen.Accept()
    go handler(conn)
  }
}
func handler(conn net.Conn) {
  defer conn.Close()
  var buf = make([]byte, 1024)
  for {
    conn.Read(buf)
    data := string(buf[:n])
    timestamp, _ := strconv.ParseInt(data, 10, 64)
    elapsed := timestamp - time.Now().UnixNano()
    log.Printf("Elapsed %v", elapsed)
  }
}

为了易读性已删除错误处理,但在实际代码中对其进行了彻底检查。它在第一次运行 strconv.ParseInt() 时崩溃,并出现 value out of range 错误,因为它收到了很多合并的时间戳。

最佳答案

曾经有一条规则,在允许任何人编写任何使用 TCP 的代码之前,他们必须从内存中重复以下句子并解释其含义:“TCP 不是一种消息协议(protocol),它是一种不保留应用程序消息边界的可靠字节流协议(protocol)。”

除了您建议的解决方案根本无法通过 TCP 可靠地实现这一事实之外,它不是减少延迟的解决方案。如果网络不堪重负,使用更多数据包发送相同数据只会使延迟更糟。

TCP 是一种字节流协议(protocol)。它提供的服务是字节流。期间。

您似乎想要一个在 TCP 上工作的低延迟消息协议(protocol)。伟大的。设计并实现。

获得低延迟的主要技巧是使用应用程序级确认。 TCP ACK 标志将搭载在确认上,提供低延迟。

不要禁用 Nagling。只有当您无法设计出旨在首先与 TCP 一起使用的适当协议(protocol)时,才需要这种 hack。由于与您建议的解决方案相同的原因,它会在非理想条件下使延迟变得更糟,即使有可能,也是一个糟糕的主意。

但是您必须设计并实现消息协议(protocol)或使用现有协议(protocol)。您的代码期望 TCP(它不是消息协议(protocol))以某种方式向它传递消息。那是不会发生的,期间。

How can I make sure that each conn.Write() is sent individually through the socket as a separate packet? Note: I don't want to reinvent TCP, I just want to simulate the load from a number of external entities that send a message each.

即使你可以,那也不会如你所愿。即使它们以单独的数据包发送,也不能保证另一端的 read 不会合并。如果你想发送和接收消息,你需要一个 TCP 没有的消息协议(protocol)。

In Node.js I managed to do the trick with a setImmediate() after each socket.write(): setImmediate() waits for all I/O to finish before continuing. How can I do the same in Go so I get separate packets?

您可能已将其从“碰巧不起作用”更改为“当我尝试时碰巧起作用”。但由于我已经解释过的原因,你永远无法可靠地完成这项工作,你是在做傻事。

如果你想发送和接收消息,你需要精确定义“消息”是什么,并编写代码来发送和接收它们。没有可靠的捷径。 TCP 是一种字节流协议(protocol),句号。

如果您关心延迟和吞吐量,请设计一个优化的消息协议(protocol)以在 TCP 上分层以优化这些。不要禁用 Nagle,因为需要 Nagle 来防止病理行为。只有当您无法更改协议(protocol)并且坚持使用并非设计在 TCP 之上的协议(protocol)时,才应禁用它。禁用 Nagle 会把您的一只手绑在背后,并在糟糕的网络条件下通过增加发送数据所需的数据包数量而导致延迟和吞吐量显着下降,即使这没有任何意义。

您可能想要/需要应用程序级别的确认。这与 TCP 配合得很好,因为 TCP ACK 将搭载在应用程序级确认上。

关于sockets - 如何为 Go 中写入的每个连接发送单独的数据包?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46728989/

相关文章:

php - 查找两个 IPv4 地址之间的网络距离(不是地理距离)

postgresql - 你如何在 Golang Gorm 中使用 UUID?

windows - 如何在 Go 中读取文件属性

python - 读取 csv 并插入数据库性能

sockets - 在 Flutter 中监听 API 变化

sockets - UDP 代理,如何维护/重用/清除连接的客户端池?

java - Android AsyncTask 的 JavaFX 等价物是什么?

android - "netcfg wlan0 up"应用程序不工作

python - 如何知道网站的响应时间?

java - 客户端套接字连接被终止后,ObjectInputStream 的 readObject() 卡住