草庐IT

tcp - net.TCPConn 允许在 FIN 数据包后写入

coder 2023-06-28 原文

我正在尝试为一些服务器端代码编写单元测试,但我无法确定关闭测试用例。环回 TCP 连接似乎没有正确处理干净关闭。我在一个示例应用程序中重现了这一点,该应用程序同步执行以下操作:

  1. 创建客户端和服务器连接。
  2. 通过从客户端向服务器成功发送消息来验证连接。
  3. 使用 channel 告诉服务器调用 conn.Close() 并等待该调用完成。
  4. (尝试)通过再次调用客户端连接上的 Write 来验证连接是否完全断开。

第 4 步成功,没有错误。我试过使用 json.Encoder 和对 TCPConn.Write 的简单调用。我用 WireShark 检查了流量。服务器发送了一个 FIN 数据包,但客户端从来没有这样做(即使有 1s sleep )服务器甚至发送了一个 RST 数据包来响应(4),并且客户端 conn.Write 仍然返回 nil 作为它的错误。

这看起来完全是疯了。我在这里错过了什么吗?当前运行 Go v1.2.1/Darwin

编辑:强制重现

package main

import (
  "bufio"
  "fmt"
  "net"
)

var (
  loopback = make(chan string)
  shouldClose = make(chan struct{})
  didClose = make(chan struct{})
)

func serve(listener *net.TCPListener) {
  conn, err := listener.Accept()
  if err != nil {
    panic(err)
  }

  s := bufio.NewScanner(conn)
  if !s.Scan() {
    panic(fmt.Sprint("Failed to scan for line: ", s.Err()))
  }

  loopback <- s.Text() + "\n"

  <-shouldClose
  conn.Close()
  close(didClose)

  if s.Scan() {
    panic("Expected error reading from a socket closed on this side")
  }
}

func main() {
  listener, err := net.ListenTCP("tcp", &net.TCPAddr{})
  if err != nil {
    panic(err)
  }
  go serve(listener)

  conn, err := net.Dial("tcp", listener.Addr().String())
  if err != nil {
    panic(fmt.Sprint("Dialer got error ", err))
  }

  oracle := "Mic check\n"
  if _, err = conn.Write([]byte(oracle)); err != nil {
    panic(fmt.Sprint("Dialer failed to write oracle: ", err))
  }

  test := <-loopback
  if test != oracle {
    panic("Server did not receive the value sent by the client")
  }

  close(shouldClose)
  <-didClose

  // For giggles, I can also add a <-time.After(500 * time.Millisecond)
  if _, err = conn.Write([]byte("This should fail after active disconnect")); err == nil {
    panic("Sender 'successfully' wrote to a closed socket")
  }
}

最佳答案

这就是主动关闭 TCP 连接的工作原理。当客户端检测到服务器已关闭时,它应该关闭它的一半连接。

在您的情况下,您没有关闭客户端,而是发送了更多数据。这会导致服务器发送 RST 数据包以强制关闭连接,因为收到的消息无效。

如果您仍然不确定,这里是等效的 python 客户端+服务器,它显示相同的行为。 (我发现使用 python 很有帮助,因为它紧密遵循底层 BSD 套接字 API,而不使用 C)

服务器:

import socket, time

server = socket.socket()
server.bind(("127.0.0.1", 9999))
server.listen(1)
sock, addr = server.accept()
msg = sock.recv(1024)
print msg
print "closing"
sock.close()
time.sleep(3)
print "done"

客户:

import socket, time

sock = socket.socket()
sock.connect(("127.0.0.1", 9999))
sock.send("test\n")
time.sleep(1)
print "sending again!"
sock.send("no error here")
time.sleep(1)
print "sending one last time"
sock.send("broken pipe this time")

要正确检测连接上的远程关闭,您应该执行 Read(),并在返回时查找 io.EOF 错误。

// we technically need to try and read at least one byte, 
// or we will get an EOF even if the connection isn't closed.
buff := make([]byte, 1)
    if _, err := conn.Read(buff); err != io.EOF {
    panic("connection not closed")
}

关于tcp - net.TCPConn 允许在 FIN 数据包后写入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23879000/

有关tcp - net.TCPConn 允许在 FIN 数据包后写入的更多相关文章

  1. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - 如何模拟 Net::HTTP::Post? - 2

    是的,我知道最好使用webmock,但我想知道如何在RSpec中模拟此方法:defmethod_to_testurl=URI.parseurireq=Net::HTTP::Post.newurl.pathres=Net::HTTP.start(url.host,url.port)do|http|http.requestreq,foo:1endresend这是RSpec:let(:uri){'http://example.com'}specify'HTTPcall'dohttp=mock:httpNet::HTTP.stub!(:start).and_yieldhttphttp.shou

  4. Ruby 写入和读取对象到文件 - 2

    好的,所以我的目标是轻松地将一些数据保存到磁盘以备后用。您如何简单地写入然后读取一个对象?所以如果我有一个简单的类classCattr_accessor:a,:bdefinitialize(a,b)@a,@b=a,bendend所以如果我从中非常快地制作一个objobj=C.new("foo","bar")#justgaveitsomerandomvalues然后我可以把它变成一个kindaidstring=obj.to_s#whichreturns""我终于可以将此字符串打印到文件或其他内容中。我的问题是,我该如何再次将这个id变回一个对象?我知道我可以自己挑选信息并制作一个接受该信

  5. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  6. ruby-on-rails - RSpec:避免使用允许接收的任何实例 - 2

    我正在处理旧代码的一部分。beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)endRubocop错误如下:Avoidstubbingusing'allow_any_instance_of'我读到了RuboCop::RSpec:AnyInstance我试着像下面那样改变它。由此beforedoallow_any_instance_of(SportRateManager).toreceive(:create).and_return(true)end对此:let(:sport_

  7. ruby - Net::HTTP 获取源代码和状态 - 2

    我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur

  8. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  9. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  10. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

随机推荐