Indy 服务器使用线程处理客户端连接,每当新客户端连接到服务器时,就会创建一个新线程来处理该连接。因此,如果有 100 个连接,就会有 100 个线程。此外,Indy 使用阻塞套接字,这意味着读写操作不完成函数就不会返回。
这种模型有一些优点,例如代码简单,因为代码是顺序执行的。但缺点是,随着连接数量增加,由于线程上下文切换,性能会越来越差。上下文切换是存储线程状态以便稍后恢复执行的过程,频繁的线程上下文切换会消耗大量 CPU 资源。如果服务器创建了 1000 个连接,即使没有数据交换,您也会看到 CPU 持续运转,这正是线程上下文切换造成的。
Indy 模型的替代方案
除了为每个连接使用一个线程之外,还有 IOCP(适用于 Windows)和 EPOLL(适用于 Linux)等替代方案,它们使用线程池处理连接并采用非阻塞套接字。当并发连接数较高时,这种模型效率更高,扩展性也远优于默认的 Indy 线程模型。
IOCP(Windows)
I/O 完成端口为多处理器系统上处理多个异步 I/O 请求提供了高效的线程模型。当进程创建 I/O 完成端口时,系统会创建一个关联的队列对象,专供线程处理这些请求。通过将 I/O 完成端口与预分配线程池结合使用,处理大量并发异步 I/O 请求的进程可以比在收到请求时临时创建线程更快速、更高效地完成工作。
IOCP 模型使用非阻塞套接字,用 AcceptEx 函数和线程池代替 Select 来处理客户端连接。
要在 sgcWebSockets Indy 服务器上启用 IOCP,请参考以下代码:
oServer := TsgcWebSocketHTTPServer.Create(nil); oServer.NotifyEvents := neNOSync; oServer.IOHandlerOptions.IOHandlerType := iohIOCP; oServer.Active := True;
将创建一个新的 WebSocket + HTTP 服务器,由线程池处理连接,使用的线程数量取决于服务器运行所在的 CPU 数量。
EPOLL(Linux)
Epoll 是 Linux 内核提供的一种可扩展 I/O 事件通知机制的系统调用,最初在 Linux 内核 2.5.44 版本中引入。其功能是监视多个文件描述符,以查看其中是否有 I/O 可以执行。它旨在替代较旧的 POSIX select 和 poll 系统调用,在监视的文件描述符数量较多的高要求应用场景中实现更好的性能。
以下表格对比了 100,000 次监视操作的性能:
| 操作次数 | poll | select | epoll |
| 10 | 0.61 | 0.73 | 0.41 |
| 100 | 2.9 | 3.0 | 0.42 |
| 1000 | 35 | 35 | 0.53 |
| 10000 | 990 | 930 | 0.66 |
可以看出,当需要监视的文件描述符超过 10 个左右时,epoll 的速度要快得多。
EPOLL 模型使用非阻塞套接字,用异步 Accept 函数和线程池代替 Select 来处理客户端连接。
要在 sgcWebSockets Indy 服务器上启用 EPOLL,请参考以下代码:
oServer := TsgcWebSocketHTTPServer.Create(nil); oServer.NotifyEvents := neNOSync; oServer.IOHandlerOptions.IOHandlerType := iohEPOLL; oServer.Active := True;
