10

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

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

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

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

4

10 回答 10

2

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

于 2008-09-01T00:02:03.263 回答
2

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

于 2008-09-01T19:51:09.927 回答
1

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

好吧,Indy 长期以来一直是套接字 I/O 的“标准”库——它基于阻塞套接字。这意味着如果您想要异步行为,您可以使用额外的线程来连接/读取/写入数据。在我看来,这实际上是一个主要优势,因为不需要管理任何类型的状态机导航,或者担心回调过程或类似的东西。我发现我的“阅读”线程的逻辑比非阻塞套接字允许的更简洁,更便携。

Indy 9对我们来说基本上是防弹、快速和可靠的。然而,Tiburon 搬到 Indy 10 让我有点担心。

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

这让“嗯?” 直到我记得我们的线程库使用基于异常的技术来安全地终止“等待”线程。我们调用QueueUserAPC对引发 C++ 异常(不是从类 Exception 派生)的函数进行排队,该异常只能被我们的线程包装程序捕获。所有的析构函数都被调用,因此线程都干净地终止并在退出时整理。

于 2008-09-01T16:12:18.800 回答
1

“同步套接字不是我想要的。”

明白了 - 但我认为在这种情况下,你原来的问题的答案是没有用于异步套接字 IO 的 Delphi习惯用法,因为它实际上是一个高度专业化且不常见的要求。

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

C10K 问题

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

于 2008-09-02T09:57:59.407 回答
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不是,我重复一遍,不是,只是“使用线程,[...] 只是由框架管理”。

于 2008-09-04T19:18:27.220 回答
1

@Roddy - 我已经阅读了您指向的链接,它们都引用自 Paul Tyma 的演示文稿“数千个线程和阻塞 I/O - 编写 Java 服务器的旧方法又是新的”。

然而,Paul 的演示中不一定会跳出来的一些事情是,他在启动时为 JVM 指定了 -Xss:48k,并且他假设 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 转换是机械式的),因此代码清晰度也不需要任何成本。

于 2008-09-04T19:44:27.030 回答
1

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

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

我在寻找更好的组件/库来重新架构我的小型即时消息服务器时发现了它。我还没有尝试过,但它看起来很好,作为第一印象编码。

于 2009-01-13T00:40:49.473 回答
0

Indy 使用同步套接字是因为它是一种更简单的编程方式。早在 Windows 3.x 时代,异步套接字阻塞就被添加到了 winsock 堆栈中。Windows 3.x 不支持线程,没有线程就无法进行套接字 I/O。有关 Indy 为何使用阻塞模型的更多信息,请参阅这篇文章

.NET Socket.BeginRead/EndRead 调用使​​用线程,它只是由框架而不是由您管理。

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

于 2008-09-03T19:47:17.563 回答
0

对于异步的东西,试试 ICS

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

于 2008-09-10T03:27:22.943 回答
0

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

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

祝你好运!

于 2008-09-11T20:47:51.670 回答