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

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

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

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

有帮助吗?

解决方案

其他提示

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

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

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

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

印地 9 对我们来说基本上是防弹、快速和可靠的。然而 Tiburon 转向 Indy 10 引起了我的一些担忧。

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

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

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

理解 - 但我认为在这种情况下,你原来问题的答案是没有德尔福 成语 对于异步套接字 IO,因为它实际上是一个高度专业化且不常见的需求。

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

C10K问题

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

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

Windows 消息样式异步(通过 WSAAsyncSelect 提供)确实在很大程度上是针对 Win 3.x 时代缺乏适当线程模型的解决方法。

然而,.NET 开始/结束是 不是 使用额外的线程。相反,它使用重叠 I/O,使用 WSASend / WSARecv 上的额外参数(特别是重叠完成例程)来指定延续。

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

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

这就是为什么如果您想要扩展,使用异步 I/O 很重要,也是 .NET 的原因 不是, ,我再说一遍,是 不是, ,简单地说“使用线程,[...]仅由框架管理”。

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

然而,Paul 的演示中不一定会跳出一些内容,即他在启动时向 JVM 指定了 -Xss:48k,并且他假设 JVM 的 NIO 实现是高效的,以便使其成为有效的比较。

印地确实 不是 指定类似缩小且严格约束的堆栈大小。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 转换是机械的),因此也不需要以代码清晰度为代价。

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

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

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

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 是一项直接的任务。

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

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

祝你好运!

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top