从阻塞套接字切换到非阻塞套接字有什么好处?

8
我们有一个使用Delphi 2010和Indy 10开发的应用程序服务器。这个服务器每秒收到超过50个请求,工作得很好。但在某些情况下,我觉得Indy非常晦涩。它们的组件很好,但有时我发现自己只能通过查看源代码来理解一些简单的东西。Indy缺乏良好的文档和支持。
我最近遇到的最大问题是:当客户端非正常断开连接(例如客户端崩溃或关闭,而不告诉服务器它将要断开连接)时,我必须检测到并且Indy无法做到这一点。如果我想要这样做,我必须开发像心跳、轮询或TCP保持活动之类的算法。我不想再花费更多时间去完成至少我认为是组件工作的任务。经过一些研究,我发现这不是Indy的错,而是所有阻塞套接字组件的问题。

现在我真的在考虑将服务器的核心更改为另一个好的套件。我必须承认,我倾向于使用非阻塞套接字。基于此,我有一些问题:

  • 从阻塞套接字切换到非阻塞套接字的好处是什么?
  • 我能够检测到客户端的非正常断开吗?
  • 哪个组件套件的产品最好?我所说的最佳产品是:快速、良好的支持、良好的工具和易于实现。

我知道这可能是一个主观的问题,但我真的想听听你的意见。我最关心的是我的第一个问题。我不介意支付100、500、1000、10000美元,但我想要一个完整的解决方案。目前,我正在考虑Ip*works

编辑

我认为有些人没有理解我的意思。我不想创建自己的套接字。我一直在使用套接字工作很长时间了,已经感到厌倦了。真的。

非阻塞套接字可以检测到客户端的非正常断开。这是一个事实,并且它在互联网上有良好的文档。非阻塞套接字会一直检查新传入数据的套接字状态,并且可以检测到套接字无效。这不是心跳算法。心跳算法用于客户端,并定期发送数据包(也称为保持活动)到服务器以告诉它仍然存活。

编辑

我可能没有表达清楚,可能是因为英语不是我的母语。 我的意思不是说可以在不尝试从套接字发送或接收数据的情况下检测到断开的连接。我想说的是,每个非阻塞套接字都能够做到这一点,因为它们不断尝试从套接字读取新的传入数据。 为什么这么难理解呢?如果你们下载并运行ip * works演示程序,特别是echoserver和echoclient(都使用TCP),你们就可以自己测试。我已经测试过了,它像我期望的那样工作。即使你在非阻塞模式下使用旧的TCPSocketServer和TCPSocketClient,你也会看到我的意思。


2
非阻塞套接字和阻塞套接字一样,都无法检测到断开的连接。请参见下面的答案。 - Misha
1
PS Indy在客户端正常关闭导致底层连接真正断开时,断开服务器的工作做得非常好。我有一些非常强大的TCP服务器代码已经运行了多年,没有出现“断开”的连接问题。最后,我认为在服务器上使用阻塞套接字有更多的优势-它可以使架构更加清晰、可扩展,所以我的建议是不要改变。 - Misha
1
你可以在Indy中做同样的事情,无论是客户端还是服务器。只需使用IoHandler.CheckForDataOnSource然后从套接字读取IoHandler.InputBuffer.Size字节。我将此运行在每个连接的10毫秒循环上。那么有什么是你不能用Indy做到的吗? - Misha
我调用IoHandler.CheckForDataOnSource来查看是否有可用数据,然后使用IoHandler.InputBuffer.ExtractToBytes获取任何可用数据。所有这些都在我用于写入的同一线程中完成。如果您从http://www.csinnovations.com/framework_delphi.htm下载我的代码并查看AppTcpServerUnt.pas文件,您将能够准确地了解所有这些操作(或者我可以发送该单元以供您查看)。 - Misha
1
@Rafael,无论其他方面如何,你不能通过从套接字读取数据来检测死亡客户端——要做到这一点,必须向其写入数据。读取是一种被动操作,无法检测到已断开的连接。 - Misha
显示剩余8条评论
6个回答

15

"从阻塞套接字更改为非阻塞套接字有什么好处?
我能检测到客户端断开(非正常)连接吗?"

这里是我的建议,仅供参考 - 我不是套接字专家,但我对它们有丰富的经验。如果我有误,我相信会有人纠正我... :-)

假设您正在使用每秒50个连接的阻塞套接字运行服务器,并已经实现了处理客户端请求的线程机制。如果是这样,您不会从非阻塞套接字中获得任何好处。相反- 您将不得不更改您的服务器逻辑以基于事件触发器来处理事件主线程从非阻塞套接字中触发的事件 ,或使用常规轮询来了解您的套接字的情况。

非阻塞套接字不能像阻塞套接字一样在没有通知的情况下检测到客户端断开连接 - 它们没有超能力... 客户端和服务器之间的TCP/IP“对话”本质上是相同的 - 阻塞和非阻塞仅与您的应用程序与进行“对话”的套接字连接的交互有关。

如果您需要清除死连接,您需要在您的套接字上实现心跳或超时机制(我从来没有看到过不支持超时的现代套接字实现)。


-1,请看这个链接:https://forums.embarcadero.com/thread.jspa?threadID=49732。它证明非阻塞套接字可以检测到客户端的断开连接。 - Rafael Colucci
3
我认为你的负一评价是没有必要的 - "对于套接字状态,不时向它们发送小包" - 这被称为心跳,阻塞套接字也可以执行相同的操作。我建议你再次阅读那个线程。我还在开始时声明了。谢谢。 - Vector
1
@mjn。Indy有自己的线程池。每个请求都在自己的线程上下文中处理。他的意思是他没有实现自己的线程,而是使用了内置在Indy中的Indy线程池。是的,该应用程序仍然是多线程的 :) - Runner
@Mikey,不,这不是心跳。非阻塞套接字甚至可以在不向客户端发送数据包的情况下检查套接字状态。它可以查询套接字状态。这是因为它们不断检查套接字以获取新的传入数据。 - Rafael Colucci
@runner,是的..那就是我想说的。谢谢。 - Rafael Colucci
显示剩余2条评论

5
我从阻塞式socket切换到非阻塞式socket会获得什么好处?
从我的经验来看,可以提高速度、可用性和吞吐量。我有一个IndySockets客户端,每秒大约可以处理15个请求,当我直接使用异步sockets时,吞吐量增加到大约每秒90个请求(在同一台机器上)。在数据中心的服务器上进行了单独的基准测试,拥有30 Mbit连接,我能够实现每秒超过300个请求。
我能检测到客户端断开连接(非正常关闭)吗?
这是我还没有尝试过的事情,因为我的所有代码都在客户端上。
哪个组件套件拥有最佳产品?我所指的最佳产品是:快速,良好的支持,良好的工具和易于实现。
您可以在几天内构建自己的socket客户端,并且可以非常稳健和快速,比我见过的大多数“现成产品”要快得多。随意查看我的异步socket客户端:http://codesprout.blogspot.com/2011/04/asynchronous-http-client.html

更新:
(来自Mikey的评论)

我想请你对NBS如何增加吞吐量进行一般性的技术说明,以区别于一个经过适当设计的BS服务器。

让我们以高负载服务器为例:如果你的服务器需要在任何时候处理1000个连接,在使用阻塞套接字时,你将不得不创建1000个线程,即使它们大多数时间都是空闲的,CPU也会花费很多时间进行上下文切换。随着客户端数量的增加,你将不得不增加线程的数量以保持同步,而CPU不可避免地会增加上下文切换的次数。对于每一个使用阻塞套接字建立的连接,你都会产生生成新线程的开销,最终你还会产生清理线程的开销。当然,首先想到的是:为什么不使用线程池,你可以重复使用线程并减少创建/清理线程的开销。

这是在Windows上处理此问题的方式(因此与.NET有关):当然,你可以使用.NET线程池,但首先你会注意到它有两种类型的线程,这不是巧合:用户线程和I/O完成端口线程。异步套接字使用IO完成端口,它“允许单个线程在不同句柄上执行同时I/O操作,甚至在相同句柄上执行同时读取和写入操作。”(1)I/O完成端口线程专门设计用于以比你使用线程池中的用户线程更高效的方式处理I/O,除非你编写自己的内核模式驱动程序
"The completion port使用一些特殊的技巧,确保同时只能运行特定数量的线程-如果一个线程在内核模式下阻塞,它会自动启动另一个线程。"(2)
还有其他优点: "除了overlapped socket I/O的非阻塞优势外,另一个优点是性能更好,因为每个I/O调用都可以节省TCP堆栈缓冲区和用户缓冲区之间的缓冲区复制。"(3)

谢谢分享,但你的答案基于客户端视角。我相信我可以构建自己的非阻塞套接字,但我没有时间去做这件事,而且我工作的公司并不在乎我是否想购买一个套件,只要我能证明为什么需要这样的东西即可。 - Rafael Colucci
@Rafael,我理解你的情况,但我认为你会发现大多数套件都是基于.NET sockets的,而且为了使用它们对.NET sockets的封装,你需要编写的代码量与直接使用.NET sockets相差无几。如果我的回答不能完全满足你的需求,在处理第三方解决方案时可能仍然有用。 - Kiril
@lirik,我很感激你的回答,真的。它对我很有帮助。但是我必须不同意。有一些好的Delphi套件可供选择。ipworks就是其中之一。我真的不想浪费时间去做别人可能已经完成的事情。 - Rafael Colucci
@Rafael 没问题,我通常也不鼓励重新发明轮子,但这完全取决于你需要从第三方解决方案中挤出多少性能。祝好运! - Kiril
每秒90到300个请求并不算多。使用Indy 10.6,我的客户端库轻松达到每秒40,000个消息往返 - 发送和接收 - (持续),而服务器在同一虚拟机上运行。 - mjn
显示剩余7条评论

3
我多年来一直使用Indy和 Synapse TCP库,并且没有发现任何问题。我在客户端和服务器端使用线程,稳定性和性能都很好。 (在同一系统上运行服务器时,每秒六千个请求和响应消息是典型的。)
如果协议比简单的“发送字符串/接收字符串”更高级,则阻塞套接字非常有用。非阻塞套接字会导致消息协议处理程序与套接字读取/写入逻辑之间的耦合度更高,因此我很快就放弃了非阻塞代码。
没有任何库可以克服TCP/IP协议在检测连接丢失方面的限制。只有尝试读取或发送数据才能确定连接是否仍然存在。

正如我所说,非阻塞套接字可以做到这一点。它们会定期检查套接字状态。 - Rafael Colucci
1
@Rafael,非阻塞套接字在检测阻塞连接方面与阻塞套接字没有区别。TCP协议专门设计为在断开网络电缆和路由器崩溃时不会丢失连接。这样,在电缆重新插入或路由器恢复运行时,可以重用原始连接。请务必阅读我在答案中提供的链接。 - Misha

3
在Windows中,有第三个选项,即重叠I/O。非阻塞套接字是使用Windows消息开发的模型,旨在避免单线程GUI应用程序在等待数据时变得“阻塞”。在我看来,现代应用程序最好使用线程和重叠I/O进行设计。
例如,请参见http://support.microsoft.com/kb/181611

3
啊啊啊 - 关于总能检测到“断开”的连接的神话。如果您关闭了具有客户端连接的机器,那么服务器无法在不发送数据的情况下判断连接是否“死亡”。这是通过TCP协议的设计实现的。不要相信我的话 - 阅读这篇文章 (检测半打开(丢失)TCP/IP套接字连接)。

我并不是说可以在不发送或检查套接字的情况下检测到断开的连接。我是说每个非阻塞套接字都通过不断从新的传入数据中读取套接字来实现这一点。为什么这么难理解呢? - Rafael Colucci
1
@Rafael,首先您只能通过发送数据来检测到断开的连接。其次,使用Indy可以实现相同的功能。我已经运行了这段代码多年了,您可以在网上找到它。 - Misha
1
有人能告诉我为什么我因为正确而被踩了吗? - Misha
关于阻塞和非阻塞之间的区别仍然存在困惑。我已经阅读了Indy的介绍,所以我知道阻塞并不是邪恶的 :) - http://www.swissdelphicenter.ch/en/showarticle.php?id=4 - 并且没有理由认为非阻塞套接字在检测套接字状态方面更加“聪明”。 - mjn

2
本文介绍了阻塞和非阻塞之间的主要区别: 《Indy入门》,作者Chad Z. Hower
阻塞的优点: - 编程容易 - 阻塞非常容易编程。所有用户代码可以存在于一个地方,并按顺序执行。 - 易于移植到Unix - 由于Unix使用阻塞套接字,因此可以轻松编写可移植的代码。Indy利用此特性实现其单一源解决方案。 - 在线程中工作良好 - 由于阻塞套接字是顺序的,它们本质上是封装的,因此在线程中非常容易使用。
阻塞的缺点: - 客户端“冻结”用户界面 - 阻塞套接字调用直到完成任务才返回。当这些调用在应用程序的主线程中进行时,应用程序无法处理用户界面消息。这会导致用户界面“冻结”,因为更新、重绘和其他消息无法处理,直到阻塞套接字调用将控制返回给应用程序的消息处理循环。
“阻塞不是邪恶的”
阻塞套接字一直遭受无端攻击。与普遍看法相反,阻塞套接字并非邪恶的。
并不是所有阻塞套接字组件都存在无法检测客户端断开连接的问题。在这方面,非阻塞组件并没有技术优势。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接