Multithreading Delphi Web服务器可以接收多个调用吗?

Multithreading Delphi Web服务器可以接收多个调用吗?,multithreading,delphi,webserver,Multithreading,Delphi,Webserver,我有一个用Delphi制作的Web服务器,负责从MySQL服务器获取数据,并检索格式为JSON。下面是一个简单的示例,说明如何从DB获取loteamentos列表 type TWM = class(TWebModule) ... procedure WMactLoteamentosAction(Sender: TObject; Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);

我有一个用Delphi制作的Web服务器,负责从MySQL服务器获取数据,并检索格式为
JSON
。下面是一个简单的示例,说明如何从
DB
获取
loteamentos
列表

type
  TWM = class(TWebModule)
    ...
    procedure WMactLoteamentosAction(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);

    ...

procedure TWM.WMactLoteamentosAction(Sender: TObject; Request: TWebRequest;
  Response: TWebResponse; var Handled: Boolean);
var
  qryLoteamentos: TFDQuery;
  JsonArray: TJSONArray;
  JsonObject: TJSONObject;
begin
  Response.ContentType := APPLICATION_JSON + '; ' + CHARSET_UTF8;

  // Search for loteamentos
  qryLoteamentos := TFDQuery.Create(nil);
  with qryLoteamentos do
  begin
    Connection := FDConnection;
    Active := False;
    SQL.Clear;

    Open('SELECT * FROM ' + T_LOTEAMENTO);

    if qryLoteamentos.RecordCount > 0 then
    begin
      JsonArray := TJSONArray.Create;
      try
        First;
        while not Eof do
        begin
          JsonObject := TJSONObject.Create;
          CapturarCamposLoteamento(JsonObject, qryLoteamentos);
          JsonArray.AddElement(JsonObject);
          Next;
        end;
      finally
        Response.Content := JsonArray.ToString;
        JsonArray.DisposeOf;
      end;
    end
    else
      handleEmptyResponse(Response);
  end;
end;

该方法的逻辑并不重要,重要的是它从数据库中获取一个表并在
JSON
中检索它

应用程序将在一台机器上运行,MySQL将来自该机器的本地主机,用户将通过外部IP和端口访问Web服务器

因此,如果服务器在外部IP为例如45.65.89.187的机器的端口9070上运行

将按以下方式调用该方法:

<代码>获取->http://45.65.89.187/loteamentos

它将为我检索如下内容:

[{"id":1,"nome":"RESIDENCIAL ...","metros":"348516,57"},

{"id":2,"nome":"RESIDENCIAL ...","metros":"215465,65"}]
问题

  • 我的问题是,假设有100人在他们的手机上使用我的API。想象一下,100个人多次调用同一个端点
    /loteamentos
    。它不会使服务器崩溃吗

  • 我想知道,人们在同一时间调用同一个端点,不会在同一
    线程中创建一行,从而干扰服务器吗?我不应该让Web服务器以多线程方式运行吗

  • 我所做的


    我测试了在4部手机中多次从Web服务器调用端点。Web服务器开始以2MB的速度运行,在多次调用后,几分钟内将达到40MB。然后,我停止调用它,但它保持在40MB,并且不会变低。

    当来自客户端的第一个请求传入时,WebBroker应用程序将创建TWebModule的第一个实例。 每当客户端的第二个HTTP请求到达WebBroker应用程序时,WebBroker框架将搜索先前创建的WebModule实例是否空闲(空闲=它没有执行请求操作处理程序)。 如果没有WebModule的空闲实例,那么将实例化一个新的TWebModule。 其代码位于Web.WebReq.pas函数TWebRequestHandler.ActivateWebModules:TComponent; 默认情况下,当负载较高时,WebBroker应用程序将创建多达32个TWebModule实例。该数字32由属性Application.MaxConnections定义。 请注意,同时请求已经是多线程的,并且请求处理程序中的所有代码都必须是线程安全的。 单个TWebModule实例一次只能服务1个请求,任何其他并发请求都将由TWebModule的其他实例服务。 由于可能有多个TWebModule实例并行地服务请求,单个TWebModule实例中的查询应该使用自己的专用DB连接实例。 在测试负载处理时,您可以添加一个长睡眠(10000)来拥有许多繁忙的web请求处理程序,并检查应用程序如何响应。然后很容易达到Application.MaxConnections的限制,导致异常

    您的Web服务器可能会消耗40MB,因为WebBroker框架在负载达到峰值时创建了10个TWebModule实例(Application.InactiveConnections+Application.ActiveConnections=10)。如果TWebModule在其构造函数中分配对象,或者在其DFM中有许多组件,那么它们都将保持不变

    还要注意,当请求完成时,任何特定于客户端的数据都不应该驻留在TWebModule本身中。在第一个请求期间,TWebModule实例1可以为客户端A提供服务,而实例2可以同时为客户端B提供服务。
    在下一个同时请求中,实例2可能为客户端A提供服务,实例1可能为客户端B提供服务。

    Am使用Delphi10.1下的普通webBroker

    我有一个接受Json字符串的服务器。我解析它并将相同的数据发送回客户端

    我的假设是Webbroker默认有32个线程,当来自客户端的并发请求开始命中服务器并在32处停止时,将逐步创建这些线程(请参阅web.webreq.pas)

    为了测试这个场景,我创建了一个简单的客户机程序,该程序有一个For循环,该循环将通过一个带有JSon字符串的请求在服务器上不断触发

    对于10000个请求,大约需要14秒。只创建了webModule的一个实例……这很好,因为for循环以串行和同步模式发送请求

    当我运行客户端程序的另一个并行实例时,会创建WebModule的第二个实例……这很公平。当我运行多个并行的客户端实例时,就会创建WebModule的第三个实例……以此类推

    现在是有趣的部分

    还记得当我的第一个客户端程序运行时,10K请求所花费的时间是14秒吗?我观察到以下情况-随着我增加并发客户端的数量,处理所需的时间也会增加当3个客户端程序同时发出请求时,实际上需要42秒才能完成(总共30K个请求)

    如果服务器是真正的多线程服务器,那么至少有32个并发客户端请求处理来自每个客户端的单个10K记录所花费的时间应该是相同的,对吗

    你能澄清一下Web代理是否真的是多线程的吗?如果是这样的话,我错过了什么

    我已经在这里附加了客户端和服务器源代码

    **客户端代码如下所示

    在此处输入代码

    unit URestclient;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
      Vcl.Controls, Vcl.Forms, Vcl.Dialogs, REST.Types, REST.Client,
      Data.Bind.Components, Data.Bind.ObjectScope, Vcl.StdCtrls, JsonTools, system.DateUtils;
    
    type
      TForm21 = class(TForm)
        RESTClient1: TRESTClient;
        RESTRequest1: TRESTRequest;
        RESTResponse1: TRESTResponse;
        Button1: TButton;
        Memo1: TMemo;
        Label2: TLabel;
        Edit1: TEdit;
        Edit2: TEdit;
        Label1: TLabel;
        Label3: TLabel;
        Label4: TLabel;
        Edit3: TEdit;
        procedure Button1Click(Sender: TObject);
      private
        procedure AfterRun;
        function SendJSonDataToIVR(MachineID, JS: String): boolean;
        { Private declarations }
      public
        { Public declarations }
      end;
    
    var
      Form21: TForm21;
      RESTClient: TRESTClient;
      RESTRequest: TRESTRequest;
      RESTResponse: TRESTResponse;
    
      N: TJSONnode;
      JS : String;
      i : integer;
    
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm21.AfterRun;
    begin
      Memo1.Lines.Add(RESTResponse.content);
    end;
    
    procedure TForm21.Button1Click(Sender: TObject);
    var
      st, et :  TDatetime;
      S : String;
    begin
      Label2.caption := 'Started!';
      Label2.Repaint;
    
      memo1.Clear;
      JS := '{"CallStatus":"t","CallType":"N","BookingType":"I","CallerType":"C","RoomNo":"1788882",'
             +'"Channel":"1191","OriginalChannel":"1191","MobileNumber":"09123456789","DialTry":"1","MaxTry":"1"'
             +',"ServerID":"3","StartTime":"2020-10-13 10:41:40","EndTime":"2020-10-13 10:41:40",'
             +'"TransferNumber":"","Dstatus":"","userid":"1"}';
    
      S := 'http://' + trim(edit2.text) + ':' + trim(edit1.text) + '/tconnected';
      st := now;
    
      for i := 0 to strtoint(trim(Edit3.Text)) do
        SendJSonDataToIVR(S, JS);
    
      et := now;
    
      Label2.Caption := 'Time Taken (in ms) ' + MilliSecondsBetween(et, st).ToString;
    end;
    
    Function TForm21.SendJSonDataToIVR(MachineID : String; JS : String) : boolean;
    begin
      SendJSonDataToIVR := false;
      RESTClient := TRESTClient.Create('nil');
      RESTRequest := TRESTRequest.Create(nil);
      RESTRequest.Client := RESTClient;
    
      RESTClient.BaseURL := MachineID;
      RESTRequest.ClearBody;
      RESTRequest.AddBody(JS, ctAPPLICATION_JSON);
    
      RESTRequest.Method := TRESTRequestMethod.rmPost;
      RESTResponse := TRESTResponse.Create(nil);
      RESTRequest.Response := RESTResponse;
    
      RESTRequest.Execute();
      //Memo1.Lines.Add(RESTResponse.content);
    //  RESTRequest.ExecuteAsync(AfterRun, true, True);
    end;
    end.
    
    **服务器代码如下所示

    单元JsonTools如下所示


    如果您担心负载,您可能需要使用压力或负载测试工具,您能推荐一种吗?我从来没有用过它。你可以编写自己的客户端,每分钟调用服务器100次。这将非常容易。我想您在数据库连接方面的问题比在数据库连接方面的问题要多
    unit wmTConnected;
    
    interface
    
    uses
      System.SysUtils, System.Classes, Web.HTTPApp, JSontools,
      Data.DB,
      FireDAC.Stan.Def,
      FireDAC.Phys.PG,
      FireDAC.Phys.PGDef,
      FireDAC.DApt,
      FireDAC.Stan.Async,
      FireDAC.Stan.Option,
      FireDAC.Comp.Client, FireDAC.Stan.Intf,
      FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Pool,
      FireDAC.Phys, FireDAC.ConsoleUI.Wait;
    
    type
      TWebModule2 = class(TWebModule)
        procedure WebModule2TConnectedAction(Sender: TObject; Request: TWebRequest;
          Response: TWebResponse; var Handled: Boolean);
      private
        { Private declarations }
        procedure ParseJson(var Request: TWebRequest);
      public
        { Public declarations }
      end;
    
    var
      WebModuleClass: TComponentClass = TWebModule2;
      N : TJsonNode;
    
    implementation
    
    {%CLASSGROUP 'System.Classes.TPersistent'}
    
    {$R *.dfm}
    
    procedure TWebModule2.ParseJson(var Request: TWebRequest);
    begin
      N := TJsonNode.Create;
      try
        N.Parse(Request.Content);
        except
          begin
            Response.Content := 'Something went wrong during parsing of Incoming Json from Client machine';
          end;
      end;//try
    end;
    
    procedure TWebModule2.WebModule2TConnectedAction(Sender: TObject;
      Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
    begin
      ParseJson(Request);
      writeln(Timetostr(now));
      Response.Content := Request.Content;
    end;
    
    end.
    
    type
      TJsonNode = class
      public
        { A parent node owns all children. Only destroy a node if it has no parent.
          To destroy a child node use Delete or Clear methods instead. }
        destructor Destroy; override;
        { GetEnumerator adds 'for ... in' statement support }
        function GetEnumerator: TJsonNodeEnumerator;
        { Loading and saving methods }
        procedure LoadFromStream(Stream: TStream);
        procedure SaveToStream(Stream: TStream);
        procedure LoadFromFile(const FileName: string);
        procedure SaveToFile(const FileName: string);
        { Convert a json string into a value or a collection of nodes. If the
          current node is root then the json must be an array or object. }
        procedure Parse(const Json: string);
        { The same as Parse, but returns true if no exception is caught }
        function TryParse(const Json: string): Boolean;
        { Add a child node by node kind. If the current node is an array then the
          name parameter will be discarded. If the current node is not an array or
          object the Add methods will convert the node to an object and discard
          its current value.
     
          Note: If the current node is an object then adding an existing name will
          overwrite the matching child node instead of adding. }
        function Add(const Name: string; K: TJsonNodeKind = nkObject): TJsonNode; overload;
        function Add(const Name: string; B: Boolean): TJsonNode; overload;
        function Add(const Name: string; const N: Double): TJsonNode; overload;
        function Add(const Name: string; const S: string): TJsonNode; overload;
        { Delete a child node by index or name }
        procedure Delete(Index: Integer); overload;
        procedure Delete(const Name: string); overload;
        { Remove all child nodes }
        procedure Clear;
        { Get a child node by index. EJsonException is raised if node is not an
          array or object or if the index is out of bounds.
     
          See also: Count }
        function Child(Index: Integer): TJsonNode; overload;
        { Get a child node by name. If no node is found nil will be returned. }
        function Child(const Name: string): TJsonNode; overload;
        { Search for a node using a path string }
        function Find(const Path: string): TJsonNode;
        { Format the node and all its children as json }
        function ToString: string; override;
        { Root node is read only. A node the root when it has no parent. }
        property Root: TJsonNode read GetRoot;
        { Parent node is read only }
        property Parent: TJsonNode read FParent;
        { Kind can also be changed using the As methods:
     
          Note: Changes to Kind cause Value to be reset to a default value. }
        property Kind: TJsonNodeKind read FKind write SetKind;
        { Name is unique within the scope }
        property Name: string read GetName write SetName;
        { Value of the node in json e.g. '[]', '"hello\nworld!"', 'true', or '1.23e2' }
        property Value: string read GetValue write Parse;
        { The number of child nodes. If node is not an object or array this
          property will return 0. }
        property Count: Integer read GetCount;
        { AsJson is the more efficient version of Value. Text returned from AsJson
          is the most compact representation of the node in json form.
     
          Note: If you are writing a services to transmit or receive json data then
          use AsJson. If you want friendly human readable text use Value. }
        property AsJson: string read GetAsJson write Parse;
        { Convert the node to an array }
        property AsArray: TJsonNode read GetAsArray;
        { Convert the node to an object }
        property AsObject: TJsonNode read GetAsObject;
        { Convert the node to null }
        property AsNull: TJsonNode read GetAsNull;
        { Convert the node to a bool }
        property AsBoolean: Boolean read GetAsBoolean write SetAsBoolean;
        { Convert the node to a string }
        property AsString: string read GetAsString write SetAsString;
        { Convert the node to a number }
        property AsNumber: Double read GetAsNumber write SetAsNumber;
      end;