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

时间:2020-03-05 18:45:45  来源:igfitidea点击:

人们用Delphi编写网络代码时使用Windows样式的重叠异步套接字I / O的通常方法是什么?

这是我对该问题的先前研究:

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

.NET的情况要好得多,使用Socket.BeginRead / Socket.EndRead,其中的延续直接传递到Socket.BeginRead,然后在此处进行备份。编码为闭包的延续显然具有我们需要的所有上下文,以及更多。

解决方案

回答

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

回答

What is the normal way people writing
  network code in Delphi use
  Windows-style overlapped asynchronous
  socket I/O?

好吧,Indy很久以来一直是套接字I / O的"标准"库,它基于阻塞套接字。这意味着,如果需要异步行为,则可以使用其他线程来连接/读取/写入数据。在我看来,这实际上是一个主要优势,因为无需管理任何类型的状态机导航,也不必担心回调过程或者类似的东西。我发现我的"读取"线程的逻辑比无阻塞套接字所允许的更混乱,并且更易于移植。

Indy 9对我们而言基本上是防弹,快速,可靠的。但是Tiburon的Indy 10转移使我有些担心。

@Mike: "...the need to kill sockets to free threads...".

放开了,"嗯?"直到我记得我们的线程库使用基于异常的技术来安全地杀死"正在等待"的线程。我们调用QueueUserAPC使产生C ++异常(不是从Exception类派生的异常)的函数排队,该异常仅应由我们的线程包装过程捕获。所有析构函数都将被调用,因此线程全部干净地终止并在出局时进行整理。

回答

@Roddy同步套接字不是我想要的。为了建立一个可能很长的连接而刻录整个线程意味着将并发连接的数量限制为进程可以包含的线程数。由于线程使用大量资源保留堆栈地址空间,已提交的堆栈内存以及用于上下文切换的内核转换,因此当我们需要支持数百个连接时(不超过数千个或者更多),它们不会扩展。

回答

"Synchronous sockets are not what I'm after."

理解了,但我认为在这种情况下,原始问题的答案是异步套接字IO根本没有Delphi习惯用法,因为它实际上是高度专业化且不常见的要求。

作为附带问题,我们可能会发现这些链接很有趣。它们都有些旧,并且比Windows更* nxy。第二个暗示着,在正确的环境中,线程可能没有我们想象的那么糟糕。

C10K问题

为什么事件不是一个好主意(对于高并发服务器)

回答

Indy使用同步套接字,因为它是一种更简单的编程方式。在Windows 3.x时代,异步套接字阻止已添加到winsock堆栈中。 Windows 3.x不支持线程,如果没有线程,我们将无法执行套接字I / O。有关Indy为什么使用阻止模型的其他信息,请参阅本文。

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

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

回答

@克里斯·米勒(Chris Miller)我们在回答中所说的实际上是不准确的。

WSAAsyncSelect提供的Windows消息样式异步确实在很大程度上是一种变通方法,因为在Win 3.x天内缺少适当的线程模型。

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

这意味着.NET样式利用Windows OS的异步I / O支持,以避免通过阻塞套接字来刻录线程。

由于线程通常来说很昂贵(除非我们为CreateThread指定非常小的堆栈大小),所以在套接字上阻塞线程将使我们无法扩展到10,000个并发连接。

这就是为什么要扩展时使用异步I / O十分重要的原因,而且,我重复一遍,为什么不使用.NET而不是简单地"使用线程,仅由框架管理"。

回答

@Roddy我已经阅读了我们指向的链接,它们都在Paul Tyma的演讲"成千上万的线程和阻塞的I / O"中引用。

但是,不一定从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或者(类似的)空间不是很大。

现在,Paul特别是对于Windows的异步I / O的非性能是否正确,我不是100%肯定要确定它。但是,我所知道的是,关于线程编程的争论比基于事件的异步编程更容易,它们提出了错误的二分法。

异步代码不必基于事件;它可以基于延续,就像.NET中一样,如果我们指定闭包作为延续,则可以免费维护状态。而且,可以通过编译器将线性线程样式的代码转换为连续通过样式的异步代码进行机械化(CPS转换是机械性的),因此在代码清晰度方面也无需付出任何代价。

回答

对于异步内容,请尝试ICS

http://www.overbyte.be/frame_index.html?redirTo=/products/ics.html

回答

对于ScktComp类,我们需要使用ThreadBlocking服务器而不是NonBlocking服务器类型。使用OnGetThread事件将ClientSocket参数传递给我们正在设计的新线程。实例化TServerClientThread的继承实例后,我们将创建TWinSocketStream实例(在线程内部),我们可以使用该实例读取和写入套接字。此方法使我们不必尝试在事件处理程序中处理数据。这些线程可能仅在需要读取或者写入的短时间内存在,或者为了重用而在一定时间内挂起。

编写套接字服务器的主题相当广泛。我们可以选择实施许多技术和实践。使用TServerClientThread读取和写入同一套接字的方法简单明了,适用于简单的应用程序。如果我们需要一个具有高可用性和高并发性的模型,则需要研究诸如Proactor模式之类的模式。

祝你好运!

回答

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

"By Naberegnyh Sergey N.. High
  performance socket server based on
  Windows Completion Port and with using
  Windows Socket Extensions. IPv6
  supported. "

我在寻找更好的组件/库来重新构造我的小型即时消息传递服务器时发现了它。我还没有尝试过,但是它的编码效果很好,给人的第一印象是。