基于mormot.net.async.pas实现一个纯粹的Socket Server

源码是mORMot框架的一部分,专注于事件驱动的异步网络处理,支持高并发连接,使用轮询(poll/epoll/IOCP)机制来管理非阻塞套接字。它不是从零实现的底层Socket API,而是构建在mORMot的核心Socket库(mormot.net.sock)之上的高级抽象。
源码分析概述
- 核心组件:
- TPollAsyncSockets:低级轮询类,处理非阻塞套接字的读/写事件。支持Windows IOCP、epoll(Linux)或poll/select。负责缓冲区管理和事件通知(OnRead/AfterWrite)。
- TPollAsyncConnection:每个连接的抽象基类。管理缓冲区(fRd/fWr)、TLS支持、读/写锁。需要重写OnRead来处理传入数据。
- TAsyncConnections:管理多个连接的线程池抽象类。处理连接的添加/删除、垃圾回收(GC)、空闲超时。支持线程亲和性(CPU/Socket)。
- TAsyncServer:继承自TAsyncConnections,实现服务器端。监听端口,接受新连接,并将它们添加到轮询中。使用线程池处理读/写。
- TAsyncConnection:服务器连接的具体类(继承TPollAsyncConnection)。添加了远程IP、最后操作时间等字段,支持心跳(OnLastOperationIdle)。
- 关键机制:
- 非阻塞模式:所有套接字设置为非阻塞,依赖轮询检测事件。
- 线程池:TAsyncConnectionsThreads处理读/写。默认一个线程用于接受连接,一个用于写,其他用于读。
- 缓冲区:每个连接有独立的读/写缓冲(TRawByteStringBuffer),支持异步发送(Write方法)。
- 事件回调:OnStart/OnStop/OnFirstRead用于连接生命周期;OnRead处理数据;AfterWrite处理发送完成。
- 选项:TAsyncConnectionsOptions控制日志、TLS、轮询行为等。
- GC:连接关闭后延迟释放(KeepConnectionInstanceMS,默认100ms),避免并发问题。
- 适用性:源码适合高性能服务器(数千连接),但如果您想要“纯粹”的Socket Server(简单echo服务器或自定义协议),可以简化使用,只重写OnRead来处理数据,而忽略HTTP相关部分(如THttpAsyncServer)。
- 限制:
- 依赖mORMot框架的其他单元(如mormot.core.os、mormot.net.sock)。
- 默认支持TLS(acoEnableTls选项),但可以禁用。
- Windows下优先IOCP,POSIX下优先epoll。
如果您想从零实现Socket Server(不依赖框架),可以直接用Delphi的TIdTCPServer或WinSock API。但既然问题是基于此源码,我将指导如何用它实现。
如何实现纯粹的Socket Server
基于源码,您可以创建一个简单的TCP Echo服务器(客户端发送数据,服务器回显)。步骤如下:
1. 准备环境
- 确保有mORMot框架(从GitHub下载或使用源码)。
- 包含必要的单元:mormot.net.async, mormot.net.sock, mormot.core.base 等。
- 定义一个继承自TAsyncConnection的类来处理每个连接。
2. 定义连接类(继承TAsyncConnection)
这是核心:重写OnRead来处理传入数据。
unit MySocketConnection;interfaceusesmormot.net.async,mormot.core.base,mormot.core.log;typeTMySocketConnection = class(TAsyncConnection)protectedfunction OnRead: TPollAsyncSocketOnReadWrite; override;function OnLastOperationIdle(nowsec: TAsyncConnectionSec): boolean; override;end;implementationfunction TMySocketConnection.OnRead: TPollAsyncSocketOnReadWrite;
varlen: integer;data: RawByteString;
beginresult := soContinue; // 默认继续读if fRd.Len > 0 thenbegin// 从读缓冲区提取数据data := fRd.Copy; // 或 fRd.ReadAllfRd.Reset; // 清空缓冲// 处理数据:这里简单回显(echo)len := Length(data);if len > 0 thenbeginOwner.LogVerbose(self, 'Received', [data]); // 可选日志