Delphi DataSnap 流程分析(一)

news2024/9/21 18:39:52

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;
begin
  if not FStarted then
    try
      StartTransports;
      FServerMethodProvider := TDSServerMethodProvider.Create;
      FServerMethodProvider.Server := self;
      FServerMethodProvider.Open;
      FStarted := True;
    finally
      if not FStarted then
      begin
        StopTransports;
        if FServerMethodProvider <> nil then
        begin
          FServerMethodProvider.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;
var
  Transport: TDSServerTransport;
  ServerComponent: TObject;
  Index: Integer;
begin
  for Index := 0 to FComponentList.Count - 1 do
  begin
    ServerComponent := FComponentList[Index];
    if ServerComponent is TDSServerTransport then
    begin
      Transport := 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;
var
  Scheduler: IIPSchedulerOfThreadPool;
  LSocketHandle: IIPSocketHandle;
begin
  inherited;
  FTcpServer := CreateTcpServer;
  FTcpServer.OnConnect := DoOnConnect;
  FTcpServer.OnDisconnect := DoOnDisconnect;
  FTcpServer.OnExecute := DoOnExecute;
  FTcpServer.UseNagle := false;
  FTcpServer.Bindings.Add.Port := FPort; //default IPv4
  if GStackPeers(IPImplementationID).SupportsIPv6 then
  begin
    LSocketHandle := FTcpServer.Bindings.Add;
    LSocketHandle.Port := FPort; //default IPv4
    LSocketHandle.IPVersion := TIPVersionPeer.IP_IPv6
  end;
  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;
begin
  inherited;
  RequiresServer;
  if Assigned(FHttpServer) then
  begin
    if FCertFiles <> nil then
      FCertFiles.SetServerProperties(FHttpServer);
    TDSHTTPServerIndy(FHttpServer).Active := True;
  end;
end;

就是这个

RequiresServer()方法在父类TCustomDSRESTServerTransport,CreateRESTServer()在TCustomDSHTTPServerTransport,CreateHttpServer()在TDSHTTPService:


procedure TCustomDSRESTServerTransport.RequiresServer;
begin
  if FRestServer = nil then
  begin
    FRESTServer := CreateRESTServer;
    InitializeRESTServer;
  end;
end;

function TCustomDSHTTPServerTransport.CreateRESTServer: TDSRESTServer;
begin
  FHttpServer := CreateHttpServer;
  Result := FHttpServer;
end;


function TDSHTTPService.CreateHttpServer: TDSHTTPServer;
var
  LHTTPServer: TDSHTTPServerIndy;
begin
  if Assigned(FCertFiles) then
    LHTTPServer := TDSHTTPSServerIndy.Create(Self.Server, IPImplementationID)
  else
    LHTTPServer := 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);
begin
  if Value and (FServer = nil) then
  begin
    FServer := PeerFactory.CreatePeer(FIPImplementationID, IIPHTTPServer, nil) as IIPHTTPServer;
    InitializeServer;
  end;
  if FServer <> nil then
    FServer.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;
begin
  if FServer <> nil then
  begin
    FServer.UseNagle := False;
    FServer.KeepAlive := True;
    FServer.ServerSoftware := FServerSoftware;
    FServer.DefaultPort := FDefaultPort;

    FServer.OnCommandGet := Self.DoIndyCommand;
    FServer.OnCommandOther := Self.DoIndyCommand;
  end;
end;

                                   
procedure TDSHTTPServerIndy.DoIndyCommand(AContext: IIPContext; ARequestInfo: IIPHTTPRequestInfo;
                                AResponseInfo: IIPHTTPResponseInfo);
var
  LContext: TDSHTTPContextIndy;
begin
  LContext := TDSHTTPContextIndy.Create(AContext, ARequestInfo, AResponseInfo);
  try
    DoCommand(LContext, LContext.FRequest, LContext.FResponse);
  finally
    LContext.Free;
  end;
end;

DoCommand()代码:


procedure TDSRESTServer.DoCommand(AContext: TDSHTTPContext; ARequestInfo: TDSHTTPRequest;
                                  AResponseInfo: TDSHTTPResponse);
var
  Request: string;
  NextRequest: string;
  NextContext: string;
  RestCtxt: string;
  StartDispatch: Boolean;
begin

  // HTTPDispatch object if necessary
  StartDispatch := not TDSHTTPApplication.Instance.Dispatching;
  if StartDispatch then
    TDSHTTPApplication.Instance.StartDispatch(AContext, ARequestInfo, AResponseInfo);
  try
{$IFNDEF POSIX}
  if CoInitFlags = -1 then
    CoInitializeEx(nil, COINIT_MULTITHREADED)
  else
    CoInitializeEx(nil, CoInitFlags);
{$ENDIF}
  try
    // check for context, if not found send the appropriate error message
    Request := ARequestInfo.URI;
    if Consume(FDSContext, Request, NextRequest) then
    begin
      Request := NextRequest;
      if Consume(FRESTContext, Request, NextRequest) then
      begin
        // datasnap/rest
        DoDSRESTCommand(ARequestInfo, AResponseInfo, NextRequest);
      end
      else if ConsumeOtherContext(Request, NextContext, NextRequest) then
      begin
        DoDSOtherCommand(AContext, ARequestInfo, AResponseInfo, NextContext, NextRequest, FDSServerName <> EmptyStr);
      end
      else
      begin
        RestCtxt := Trim(FRESTContext);
        if RestCtxt = EmptyStr then
          RestCtxt := SProtocolRestEmpty;

        AResponseInfo.ResponseNo := 501; {rest or other service not found in URI}
        AResponseInfo.ContentText := Format(SProtocolNotSupported, [Request, RestCtxt]);
        AResponseInfo.CloseConnection := true;
      end;
    end
    else
    begin
      // This may dispatch .js files for example
      DoCommandOtherContext(AContext, ARequestInfo, AResponseInfo, Request);
    end;
    if Assigned(Self.FTrace ) then
    begin
      FTrace(Self, AContext, ARequestInfo, AResponseInfo);
    end;
  finally
                                                     
    ClearInvocationMetadata();
{$IFNDEF POSIX}
    CoUnInitialize;
{$ENDIF}
  end;
  finally
    if StartDispatch then
      TDSHTTPApplication.Instance.EndDispatch;
  end;
end;

开始引入了Dispatch,到这里基本就明白了,后面的处理方式和一般的HTTP类似,只是简化了(DataSnap专用)。

可以看出,和一般的使用WebModule也就是WebReq方式还是有大的区别的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440734.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么把视频中动态的人物P掉,把视频中不要的人物去掉

怎么把视频中动态的人物P掉&#xff1f;很多小伙伴试过ps抠图&#xff0c;但是你试过视频人物抠图吗&#xff1f;其实道理是一样的&#xff0c;但是操作过程却变难了。今天就给大家带来一个简单的方法&#xff0c;轻松去除视频中的人物。不影响整个画面的呈现。 在拍摄旅游视频…

springcloud:快速上手定时任务框架xxl-job(十五)

0. 引言 实际开发中&#xff0c;我们常常遇到需要定时执行的任务&#xff0c;我们可以利用定时线程池或schedule框架等来实现定时任务&#xff0c;但这些方式都有效率、性能上的缺陷&#xff0c;在微服务框架下&#xff0c;我们期望一种更加规整、轻量、可靠的定时任务框架来帮…

【通信接口】UART、IIC、SPI

目录 一、预备知识 1、串行与并行 2、单工与双工 3、波特率 二、UART 三、IIC 四、SPI &#xff08;一对一、一对多&#xff09; 五、IIC、SPI异同点 参考文章&#xff1a;这些单片机接口&#xff0c;一定要熟悉&#xff1a;UART、I2C、SPI、TTL、RS232、RS422、RS485…

kafka-5 kafka的高吞吐量和高可用性

kafka的高吞吐量和高可用性 6.1 高吞吐量6.2 高可用&#xff08;HA&#xff09; 6.1 高吞吐量 kafka的高吞吐量主要是由4方面保证的&#xff1a; &#xff08;1&#xff09;顺序读写磁盘 Kafka是将消息持久化到本地磁盘中的&#xff0c;一般人会认为磁盘读写性能差&#xff…

【C++ 八】写文件、读文件

写文件、读文件 文章目录 写文件、读文件前言1 文本文件1.1 写文件1.2 读文件 2 二进制文件2.1 写文件2.2 读文件 前言 本文包含文本文件写文件、文本文件读文件、二进制写文件、二进制读文件。 程序运行时产生的数据都属于临时数据&#xff0c;程序一旦运行结束都会被释放 通…

中间表示- 活性分析

进行活性分析的动机 &#xff08;1&#xff09;在代码生成的讨论中&#xff0c;我们曾假设目标机器有无限多个&#xff08;虚拟&#xff09;寄存器可用&#xff0c;这简化了代码生成的算法&#xff0c;但对物理机器是个坏消息&#xff0c;因为机器只有有限多个寄存器&#xff…

Spring Boot 应用的打包和发布

1. 创建项目&#xff08;example-fast&#xff09; 基于 Spring Boot 创建一个 WEB 项目 example-fast。 2. 编译打包 2.1 采用 IDEA 集成的 Maven 环境来对 Spring Boot 项目编译打包&#xff0c;可谓是超级 easy 2.2 mvn 命令打包 # mvn clean 清理编译 # install 打包 #…

牛顿法、梯度下降法与拟牛顿法

牛顿法、梯度下降法与拟牛顿法 0 引言1 关于泰勒展开式1.1 原理1.2 例子 2 牛顿法2.1 x 为一维2.2 x 为多维 3 梯度下降法4 拟牛顿法4.1 拟牛顿条件4.2 DFP 算法4.3 BFGS 算法4.4 L-BFGS 算法 0 引言 机器学习中在求解非线性优化问题时&#xff0c;常用的是梯度下降法和拟牛顿…

数据结构入门(C语言版)二叉树概念及结构(入门)

二叉树概念及结构&#xff08;入门&#xff09; 树的概念及结构1.树的概念及结构1.1 树的概念1.2 树的相关知识1.3 树的结构体表示1.4 树的实际运用 2.二叉树概念及结构2.1 二叉树的概念2.2 现实中的二叉树2.3 特殊的二叉树2.4 二叉树的性质2.5 二叉树的存储结构 结语 树的概念…

【SpringCloud】3、使用Nacos作为服务配置中心

1、增加 maven 依赖 <!-- SpringCloud Alibaba Nacos Config --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </dependency>2、添加配置中心配置 spr…

(数字图像处理MATLAB+Python)第四章图像正交变换-第四、五节:Radon变换和小波变换

文章目录 一&#xff1a;Radon变换&#xff08;1&#xff09;Radon变换原理&#xff08;2&#xff09;Radon变换实现&#xff08;3&#xff09;Radon变换性质&#xff08;4&#xff09;Radon变换应用 二&#xff1a;小波变换&#xff08;1&#xff09;小波A&#xff1a;定义B&a…

【PyQt】PyQt5进阶——串口上位机及实时数据显示

文章目录 0 前期教程1 前言2 串口部分——QtSerialPort3 绘图部分3.1 QCustomPlot3.2 QtChart3.3 QWT3.4 Qt Designer中如何使用 参考链接 0 前期教程 【Python】PyQt5入门 1 前言 最近在用PyQt做一个串口上位机&#xff0c;需要串口通信和实时显示曲线。这里简单记录一些关键…

【已解决】最简单便捷的方法将多html合并为pdf

一、单页面转pdf 可以使用pdf24&#xff0c;https://tools.pdf24.org/zh/webpage-to-pdf。 也可以直接打印 二、多页面转pdf&#xff08;wkhtmltopdf方案&#xff09; 1、安装配置pdfkit&#xff1a;[https://blog.csdn.net/xc_zhou/article/details/80952168(https://blog.…

Charles安装及使用教程

一. 简介及安装 一、charles的使用 1.1 charles的说明 Charles其实是一款代理服务器&#xff0c;通过过将自己设置成系统&#xff08;电脑或者浏览器&#xff09;的网络访问代理服务器&#xff0c;然后截取请求和请求结果达到分析抓包的目的。该软件是用Java写的&#xff0…

Nestjs全网最佳翻译-概况-管道-Pipes

管道 带上装饰器 Injectable() 并实现了 PipeTransform 接口的类&#xff0c;就是管道。 管道有 2 个典型的应用场景&#xff1a; 数值转换&#xff1a;将输入的参数转换成目标类型&#xff0c;例如&#xff0c;string to number。 数值校验&#xff1a;对输入的参数进行校验…

cocos creator v3.6版本使用Intersection2D模块的circleCircle方法

在cocos creator v3版本中Intersection2D模块的circleCircle方法可以用来检测两个圆形是否相交 该方法可以实现的功能有&#xff1a; cocos creator吸铁石实现、cocos creator物体在固定位置吸附、cocos creator物体吸附效果、cocos creator吸铁石实现、cocos creator两个物体时…

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup

统计软件与数据分析Lesson9----爬虫解析库Beautiful Soup知识点总结 1.requests 模块1.1 查看requests功能函数1.2 发送请求1.3 传递URL参数1.4 获取响应内容 2.Beautiful Soup模块2.1 解析器2.2 对象类型2.2.1 Beautiful Soup2.2.2 标签Tag2.2.3 可遍历的字符串NavigableStrin…

Java——包含min函数的栈

题目链接 牛客在线oj题——包含min函数的栈 题目描述 定义栈的数据结构&#xff0c;请在该类型中实现一个能够得到栈中所含最小元素的 min 函数&#xff0c;输入操作时保证 pop、top 和 min 函数操作时&#xff0c;栈中一定有元素。 此栈包含的方法有&#xff1a; push(va…

SRv6实践项目(六):控制面完成链路和主机的发现

在本次实验中&#xff0c;我们需要利用ONOS完成对数据面的控制 1.使能packet的IO功能&#xff0c;验证链路发现 main.p4提供了和P4Runtime的通信的消息的定义格式&#xff0c;分别是PacketIn和PacketOut&#xff0c;他们都被加上了一个注解&#xff0c;表示这是一个控制器交互…

c++篇---缺省参数

文章目录 一、缺省参数概念二、缺省参数实例三、缺省参数声明和定义四、全缺省和半缺省 一、缺省参数概念 缺省参数 在调用该函数时&#xff0c;如果实参没有指定传内容&#xff0c;那么在函数中用形参时&#xff0c;就采用为函数参数指定的这个缺省值 但是如果在调用该函数时…