Delphi DataSnap 流程分析(一)
大家好啊,今天咱们来聊点儿关于DataSnap的那点儿事儿。DataSnap这个老伙计,在Delphi的世界里真的是个硬核角色。它的三种形态,DataSnap REST Application、DataSnap Server、还有DataSnap Webbroker Application,就像三个不同的武林高手,各有所长。
先说说DataSnap REST Application,这是最年轻的小鲜肉,专攻HTTP,支持WebModule和TDSHTTPWebDispatcher。它就像一位优雅的剑客,走哪儿都顺手,特别适合现在流行的RESTful API开发。有了它,前端的JavaScript小伙伴儿们可以轻松调用服务器方法,数据传输也贼快,用起来那是相当爽。
然后是DataSnap Server,这位可是老派的代表,支持TCP和HTTP,但没WebModule,用的是TDSHTTPService。就像一位用双刀的老将,虽然灵活,但套路有点绕,得通过Indy的HTTP来实现。说实话,这位老伙计用起来没WebModule方便,但胜在支持TCP,老粉丝们还是挺喜欢的。
最后是DataSnap Webbroker Application,这货其实和REST Application有点像,但更原始,也更灵活,只支持HTTP。就像一位不拘一格的画家,虽然自由,但需要自己处理不少细节,所以用的人不多,但有它的独特之处。
那文章里重点分析了DataSnap Server的流程,特别是HTTP通信的实现。咱们看到,TDSServer.Start会启动传输,但HTTP服务的启动得靠TDSHTTPService自己处理,涉及到一些Indy的组件,比如TIdHTTPServer。这就像武侠小说里的内力,表面上看似平静,实则暗流涌动。
整体而言,DataSnap的三种方式各有千秋,选哪个主要看你的需求。REST Application适合现代应用,Server适合传统项目,Webbroker则是灵活性和细节处理的高手。理解了它们的流程和实现,你在开发中就能游刃有余,像一位真正的武林高手了!
DataSnap 有三种方式:
1、DataSnap REST Application: Create a DataSnap Server with support for REST Communication and with pages that invoke server methods using Java Script and JSON.
2、DataSnap Server: The DataSnap Server Wizard provides an easy way to implenent a server application using DataSnap technology.
3、DataSnap Webbroker Application: The DataSnap WebBroker Application Wizard provides an easy way to implenent a server application using both The WebBroker and DataSnap technology.
1方式是最新的也是主要的运用方式。只支持HTTP。有WebModule,有TDSHTTPWebDispatcher。
2方式传统的运用方式,支持TCP和HTTP。没有WebModule,TDSHTTPService代替了TDSHTTPWebDispatcher。
3方式和1方式类似,比较"原始",也比较灵活。只支持HTTP。
早期的DataSnap只有方式2和3,方式2只支持TCP传输的。方式3的运用要自己处理许多细节,所以方式3用的不多。
现在的方式2添加了HTTP支持,但是其实现方式不是直接通过WebModule来实现,而是转了个弯,通过桥接Indy的Http来实现。
因为方式2没有了TWebModule,所以和其它两种方式的区别比较大。
我们先来简要分析下方式2的流程,然后主要分析方式1的流程,方式3的流程类似方式1,就不做分析了。
DataSnap Server 流程:
向导生成时,选择支持TCP和HTTP服务。服务器是自动启动的,ServerContainerUnit1.ServerContainer1.DSServer1.AutoStart := True;
只要运行服务端程序,就可以开始提供服务。如果要手动启动,则设置:
ServerContainerUnit1.ServerContainer1.DSServer1.AutoStart := False;
启动:
ServerContainerUnit1.ServerContainer1.DSServer1.Start;
停止:
ServerContainerUnit1.ServerContainer1.DSServer1.Stop;
TCP通信流程不管,看看HTTP通信流程。
向导生成的ServerContainer单元,包含了TDSServer(服务控制组件),TDSServerClass(用于导出方法到客户端),TDSTCPServerTransport(用于TCP通信),TDSHTTPService(HTTP服务),以及其它的辅助组件,用的是TDataMudule:
当用TDataMudule时,如果要提供HTTP服务,肯定要提供一个WebDisptcher。
(见:Delphi Web Server 流程分析_看那山瞧那水的博客-CSDN博客)
TDSTCPServerTransport和TDSHTTPService都有一个Server属性,指向TDSServer。
当DSServer1.Start时,是如何启动HTTP服务的?
procedure TDSServer.Start;
begin
inherited;
// Add a DBX "driver" for the server component
TDBXDriverRegistry.RegisterDriverClass(Name, TDSServerDriver);
end;
这里没有什么,只是添加了DBX驱动,DATASNAP的TCP通信是通过DBX框架实现的。
类继承关系:
TDSServer->TDSCustomServer->TComponent:
procedure TDSCustomServer.Start;
beginif not FStarted thentryStartTransports;FServerMethodProvider := TDSServerMethodProvider.Create;FServerMethodProvider.Server := self;FServerMethodProvider.Open;FStarted := True;finallyif not FStarted thenbeginStopTransports;if FServerMethodProvider <> nil thenbeginFServerMethodProvider.Close;FreeAndNil(FServerMethodProvider);end;end;end;
end;
这里只看到关于TCP的组件,HTTP在哪里呢?
没找到,先看看TDSHTTPService的继承关系:
TDSHTTPService->TCustomDSHTTPServerTransport->TCustomDSRESTServerTransport->TDSServerTransport->TDSServerComponent->TComponent
好像和HTTP都没什么关系,和TDSServerTransport有关系,TCP的有个组件TDSTCPServerTransport,也看看它的继承关系
TDSTCPServerTransport->TDSServerTransport,看到了,TDSHTTPService和TDSTCPServerTransport都是TDSServerTransport的子类。
前面看到了,TDSServer.Start,要启动了TDSTCPServerTransport:
StartTransports:
procedure TDSCustomServer.StartTransports;
varTransport: TDSServerTransport;ServerComponent: TObject;Index: Integer;
beginfor Index := 0 to FComponentList.Count - 1 dobeginServerComponent := FComponentList[Index];if ServerComponent is TDSServerTransport thenbeginTransport := TDSServerTransport(ServerComponent);Transport.DbxContext := FDbxContext;Transport.Start;end;end;
end;
调用了TDSServerTransport.Start:
但是TDSServerTransport本身没有这个方法,其父类的Start:
procedure TDSServerComponent.Start;
begin
//
end;
哎,是空的,看来是子类实现。代码里的Transport是TDSServerTransport,是TDSTCPServerTransport的父类,这个方法肯定是在TDSTCPServerTransport:
procedure TDSTCPServerTransport.Start;
varScheduler: IIPSchedulerOfThreadPool;LSocketHandle: IIPSocketHandle;
begininherited;FTcpServer := CreateTcpServer;FTcpServer.OnConnect := DoOnConnect;FTcpServer.OnDisconnect := DoOnDisconnect;FTcpServer.OnExecute := DoOnExecute;FTcpServer.UseNagle := false;FTcpServer.Bindings.Add.Port := FPort; //default IPv4if GStackPeers(IPImplementationID).SupportsIPv6 thenbeginLSocketHandle := FTcpServer.Bindings.Add;LSocketHandle.Port := FPort; //default IPv4LSocketHandle.IPVersion := TIPVersionPeer.IP_IPv6end;Scheduler := PeerFactory.CreatePeer(IPImplementationID, IIPSchedulerOfThreadPool, FTCPServer.GetObject as TComponent) as IIPSchedulerOfThreadPool;Scheduler.MaxThreads := MaxThreads;Scheduler.PoolSize := PoolSize;FTCPServer.Scheduler := Scheduler;FTcpServer.Active := True;
end;
这里还是没有涉及到HTTP,回头看看TDSHTTPService这边,
TDSHTTPService->TCustomDSHTTPServerTransport->TCustomDSRESTServerTransport->TDSServerTransport 这中间某个肯定实现了和HTTP的挂钩。
TDSHTTPService.Start:
procedure TDSHTTPService.Start;
begininherited;RequiresServer;if Assigned(FHttpServer) thenbeginif FCertFiles <> nil thenFCertFiles.SetServerProperties(FHttpServer);TDSHTTPServerIndy(FHttpServer).Active := True;end;
end;
就是这个
RequiresServer()方法在父类TCustomDSRESTServerTransport,CreateRESTServer()在TCustomDSHTTPServerTransport,CreateHttpServer()在TDSHTTPService:
procedure TCustomDSRESTServerTransport.RequiresServer;
beginif FRestServer = nil thenbeginFRESTServer := CreateRESTServer;InitializeRESTServer;end;
end;function TCustomDSHTTPServerTransport.CreateRESTServer: TDSRESTServer;
beginFHttpServer := CreateHttpServer;Result := FHttpServer;
end;function TDSHTTPService.CreateHttpServer: TDSHTTPServer;
varLHTTPServer: TDSHTTPServerIndy;
beginif Assigned(FCertFiles) thenLHTTPServer := TDSHTTPSServerIndy.Create(Self.Server, IPImplementationID)elseLHTTPServer := TDSHTTPServerIndy.Create(Self.Server, IPImplementationID);Result := LHTTPServer;LHTTPServer.HTTPOtherContext := HTTPOtherContext;
end;
CreateHttpServer()方法里出现了TDSHTTPServerIndy,看看它是什么,前面的Start()里有这一行代码:
TDSHTTPServerIndy(FHttpServer).Active := True;
TDSHTTPServerIndy = class(TDSHTTPServer), 是TDSHTTPServer的子类,启动代码:
procedure TDSHTTPServerIndy.SetActive(const Value: Boolean);
beginif Value and (FServer = nil) thenbeginFServer := PeerFactory.CreatePeer(FIPImplementationID, IIPHTTPServer, nil) as IIPHTTPServer;InitializeServer;end;if FServer <> nil thenFServer.Active := Value;
end;
有个名称叫 PeerIP(本意是对等IP),INDY里一些组件采用多端口技术时,有2组参数:
IP 、Port:代表本地IP地址和端口;
PeerIP、PeerPort:代表远端IP地址和端口;
服务端可以向PeerIP和PeerPort回应数据,这里是HTTP服务端。
(PeerIP的技术原理还没搞明白)
支持IIPHTTPServer接口的实现在IPPeerServer.pas(路径:D:\\Program Files (x86)\\Embarcadero\\Studio\\22.0\\source\\indy\\implementation\\IPPeerServer.pas)
部分代码:
TIdHTTPServerIP = class(TIdHTTPServer)
private
FSetDestroyedProc: procedure of object;
public
destructor Destroy; override;
end;
TIdHTTPServerPeer = class(TIdClassIP, IIPHTTPServer, IIPObject)
private
FHTTPServer: TIdHTTPServerIP;
FContexts: TDictionary<TIdContext, IIPContext>;
.....................................
FHTTPServer => FHTTPServer =>TIdHTTPServer
本质上也是一个HTTPSERVER,只是通过PeerIP技术来实现了。
procedure TDSHTTPServerIndy.InitializeServer;
beginif FServer <> nil thenbeginFServer.UseNagle := False;FServer.KeepAlive := True;FServer.ServerSoftware := FServerSoftware;FServer.DefaultPort :&