在Delphi中进行异步套接字编程的惯用方法是什么?

时间:2008-08-31 22:22:59

标签: delphi winapi sockets asynchronous networking

人们在Delphi中编写网络代码的正常方式是使用Windows风格的重叠异步套接字I / O吗?

这是我之前对这个问题的研究:

Indy组件似乎完全同步。另一方面,虽然ScktComp单元确实使用WSAAsyncSelect,但它基本上只与BSD样式的多路复用套接字应用程序异步。您将被转储到单个事件回调中,就像您刚刚从循环中的select()返回一样,并且必须自己完成所有状态机导航。

使用Socket.BeginRead / Socket.EndRead,.NET的情况要好得多,其中延续会直接传递给Socket.BeginRead,这就是你选择备份的地方。编码为闭包的延续显然具有您需要的所有上下文,以及更多。

10 个答案:

答案 0 :(得分:2)

我发现Indy虽然在开始时是一个更简单的概念,但由于需要在应用程序终止时终止套接字以释放线程,因此很难管理。此外,在操作系统补丁升级后,我让Indy库停止工作。 ScktComp适用于我的应用程序。

答案 1 :(得分:2)

@Roddy - 同步套接字不是我追求的。为了可能长时间连接而烧写整个线程意味着您将并发连接数限制为进程可以包含的线程数。由于线程使用了大量资源 - 保留堆栈地址空间,已提交堆栈内存和上下文切换的内核转换 - 当您需要支持数百个连接时,它们无法扩展,更不用说数千个或更多。

答案 2 :(得分:1)

  

人们写作的正常方式是什么   Delphi中的网络代码使用   Windows风格重叠异步   socket I / O?

嗯,Indy已经成为套接字I / O的'标准'库了很长一段时间 - 而且它基于阻塞套接字。这意味着如果您想要异步行为,则使用其他线程来连接/读取/写入数据。在我看来,这实际上是一个主要优势,因为不需要管理任何类型的状态机导航,或担心回调过程或类似的东西。我发现我的'阅读'线程的逻辑比非阻塞套接字允许的更简洁,更便携。

Indy 9 对我们来说大多是防弹,快速和可靠。然而,为了Tiburon而向Indy 10的迁移让我有点担心。

  

@Mike:“......需要杀死套接字来释放线程......”。

这样做了“嗯?”直到我记得我们的线程库使用基于异常的技术来安全地杀死'等待'线程。我们调用QueueUserAPC来排队一个引发C ++异常的函数(不是从类Exception派生的),它只能由我们的线程包装程序捕获。所有的析构函数都会被调用,因此所有的线程都会在出路时干净整洁地终止。

答案 3 :(得分:1)

  

“同步套接字不是我追求的。”

理解 - 但我认为在这种情况下,原始问题的答案是,对于异步套接字IO,没有Delphi idiom ,因为它实际上是一种高度专业化且不常见的要求。

作为附带问题,您可能会发现这些链接很有趣。它们都比Windows旧一点,而且比Windows更多。第二个意味着 - 在正确的环境中 - 线程可能没有您想象的那么糟糕。

The C10K problem

Why Events Are A Bad Idea (for High-concurrency Servers)

答案 4 :(得分:1)

@Chris Miller - 你在答案中所说的事实上是不准确的。

通过WSAAsyncSelect提供的Windows消息样式异步确实很大程度上是因为在Win 3.x天内缺少正确的线程模型。

.NET Begin / End,使用额外的线程。相反,它使用重叠的I / O,使用WSASend / WSARecv上的额外参数,特别是重叠的完成例程来指定延续。

这意味着.NET风格利用Windows操作系统的异步I / O支持来避免通过阻塞套接字来烧录线程。

由于线程通常很昂贵(除非你为CreateThread指定一个非常小的堆栈大小),在套接字上阻塞线程将阻止你扩展到10,000个并发连接。

这就是为什么要使用异步I / O进行扩展非常重要的原因,以及为什么.NET ,重复一遍,,只需“使用线程,[...]只是由框架管理”。

答案 5 :(得分:1)

@Roddy - 我已经阅读了你指向的链接,它们都引用了Paul Tyma的演讲“成千上万的线程和阻塞I / O--旧的Java服务器编写方式再次出现”。

然而,有些事情并不一定从Paul的演讲中跳出来,他在启动时指定了-Xss:48k到JVM,并且他假设JVM的NIO实现是高效的,以便它能够是一个有效的比较。

Indy 指定类似缩小且严格约束的堆栈大小。在Indy代码库中没有调用BeginThread(Delphi RTL线程创建例程,你应该用于这种情况)或CreateThread(原始WinAPI调用)。

默认堆栈大小存储在PE中,对于Delphi编译器,它默认为1MB的保留地址空间(操作系统以4K块的形式逐页提交空间;实际上,编译器需要生成代码到如果函数中有> 4K的本地人,则触摸页面,因为扩展由页面错误控制,但仅限于堆栈中的最低(保护)页面)。这意味着在最多2,000个并发线程处理连接后,你将耗尽地址空间。

现在,您可以使用{$ M minStackSize [,maxStackSize]}指令更改PE中的默认堆栈大小,但这会影响所有线程,包括主线程。我希望你不要做太多的递归,因为48K或(类似)不是很大的空间。

现在,保罗是否对Windows的异步I / O的不正常表现是正确的,我不是百分之百确定 - 我必须对其进行测量才能确定。然而,我所知道的是,关于线程编程比基于异步事件的编程更容易的论点,提出了一个错误的二分法

异步代码 不是基于事件的;它可以是基于延续的,就像在.NET中一样,如果你指定一个闭包作为你的延续,你可以免费获得为你维护的状态。此外,从线性线程式代码到延续传递式异步代码的转换可以由编译器机械化(CPS转换是机械的),因此代码清晰度也不需要花费。

答案 6 :(得分:1)

有一个免费的IOCP(完成端口)套接字组件:http://www.torry.net/authorsmore.php?id=7131(包含源代码)

  

“作者:Naberegnyh Sergey N ..高   性能套接字服务器基于   Windows完成端口和使用   Windows套接字扩展。 IPv6的   支持的。 “

我找到了它,同时寻找更好的组件/库来重新架构我的小即时消息服务器。我还没有尝试过,但它看起来很好,只是第一印象。

答案 7 :(得分:0)

Indy使用同步套接字,因为它是一种更简单的编程方式。异步套接字阻塞是在Windows 3.x天后添加到winsock堆栈的东西。 Windows 3.x不支持线程,没有线程就无法执行套接字I / O.有关Indy使用阻止模型的其他信息,请参阅this article

.NET Socket.BeginRead / EndRead调用正在使用线程,它只是由Framework而不是您管理。

@Roddy,自2006年Delphi以来,Indy 10已与Delphi捆绑在一起。我发现从Indy 9迁移到Indy 10是一项直接的任务。

答案 8 :(得分:0)

答案 9 :(得分:0)

使用ScktComp类,您需要使用ThreadBlocking服务器而不是NonBlocking服务器类型。使用OnGetThread事件将ClientSocket参数移交给您设计的新线程。一旦您实例化了TServerClientThread的继承实例,您将创建一个TWinSocketStream实例(在线程内),您可以使用它来读取和写入套接字。此方法使您远离尝试处理事件处理程序中的数据。这些线程可能只存在需要读取或写入的短时间内,或者为了重用而持续一段时间。

编写套接字服务器的主题相当广泛。您可以选择实施许多技术和实践。在TServerClientThread中读取和写入同一套接字的方法很简单,对于简单的应用程序来说很好。如果您需要高可用性和高并发性的模型,那么您需要研究像Proactor模式这样的模式。

祝你好运!