delphi 自学RTC RealThinClient 1-6  
官方Delphi 学习QQ群: 682628230(三千人)\n
频道

delphi 自学RTC RealThinClient 1-6


自学RTC——ServerLesson1

RTC全称RealThinClient,据说是Delphi做三层的神器之一,虽然听说已久,却始终未好好研究过,而且安装包里带的有示例源码,于是乎,索性拿这些源码开这么个系列,督促下自己。


步骤

创建一个新工程


从RTC Server组件页中找到RtcHttpServer组件放到窗体上


设置RtcHttpServer1的ServerPort属性为80


在窗体的OnCreate事件里写上代码:


RtcHttpServer1.Listen;

从RTC Server组件页中找到RtcDataProvider组件放到窗体上


设置RtcDataProvider1的Server属性为RtcHttpServer1


在RtcDataProvider1的OnCheckRequest事件中写上代码:


with Sender as TRtcDataServer do

  if UpperCase(Request.FileName)='/TIME' then

    Accept;

在RtcDataProvider1的OnDataReceived事件中写上代码:

with Sender as TRtcDataServer do

  if Request.Complete then

    Write('Current time is: '+TimeToStr(Now));

编译并运行


打开浏览器,访问网址http://localhost/time


示例源码

核心源码

procedure TForm1.RtcDataProvider1CheckRequest(Sender: TRtcConnection);

  begin

  with Sender as TRtcDataServer do

    if Request.FilePath.Equal(0,'TIME') then

      Accept;

  end;


procedure TForm1.RtcDataProvider1DataReceived(Sender: TRtcConnection);

  var

    a:integer;

  begin

  with Sender as TRtcDataServer do

    if Request.Complete then

      begin

      Write('

      Write('Request from '+Sender.PeerAddr+':'+Sender.PeerPort+'
');

      Write('Server time: '+TimeToStr(Now)+'
');


      Write('
URL = '+Request.URL+'
');


      Write('
URI = '+Request.URI+'
');


      Write('
FileName = '+Request.FileName+'
');


      Write('
FilePath.Count = '+IntToStr(Request.FilePath.Count)+'
');

      for a:=0 to Request.FilePath.Count-1 do

        Write('FilePath['+IntToStr(a)+'] = "'+Request.FilePath[a]+'"
');


      Write('
Query.ItemCount = '+IntToStr(Request.Query.ItemCount)+'
');

      for a:=0 to Request.Query.ItemCount-1 do

        Write('Query Item '+IntToStr(a)+': '+

              'Name = "'+Request.Query.ItemName[a]+'"; '+

              'Value = "'+Request.Query.ItemValue[a]+'"
');


      Write('');

      end;

  end;

测试

浏览器访问http://localhost/time:

Request from 127.0.0.1:60623

Server time: 21:12:30


URL = localhost/time


URI = /time


FileName = /time


FilePath.Count = 1

FilePath[0] = "time"


Query.ItemCount = 0

浏览器访问http://localhost/times:

返回错误


浏览器访问http://localhost/time/test/abc:

Request from 127.0.0.1:60704

Server time: 21:22:32


URL = localhost/time/test/abc


URI = /time/test/abc


FileName = /time/test/abc


FilePath.Count = 3

FilePath[0] = "time"

FilePath[1] = "test"

FilePath[2] = "abc"


Query.ItemCount = 0

浏览器访问http://localhost/time?test=abc:

Request from 127.0.0.1:60725

Server time: 21:24:06


URL = localhost/time?test=abc


URI = /time?test=abc


FileName = /time


FilePath.Count = 1

FilePath[0] = "time"


Query.ItemCount = 1

Query Item 0: Name = "test"; Value = "abc"

源码解读

Request.FilePath.Equal(0,'TIME') VS UpperCase(Request.FileName)='/TIME'

从响应结果可以看出:


两者都忽略了大小写


两者都不能响应/times


前者可以响应以/time开头的所有请求,并且把请求分解后放在FilePath[]里;后者则只能响应/time


两者都能响应带参数的/time



自学RTC——ServerLesson2


打开上节的工程


添加组件RtcDataProvider2并设置Server属性为RtcHttpServer1


在RtcDataProvider2的OnCheckRequest事件中写上代码:


with TRtcDataServer(Sender) do

  if UpperCase(Request.FileName)='/SQUARE' then

    Accept;

在RtcDataProvider2的OnDataReceived事件中写上代码:

var

  line:integer;

begin

with TRtcDataServer(Sender) do

  if Request.Complete then

    begin

    Write('

    Write('Here comes a table of square values ...
');

    for line:=1 to 100 do

      begin

      // 使用3个 write 和使用1个效果是一样的

      Write('Square of '+IntToStr(line)+' = ');

      Write(IntToStr(line*line));

      Write('
');

      end;

    Write('......... done.');

    Write('');

    end;

end;

编译并运行


打开浏览器,分别访问网址http://localhost/square、http://localhost/time


细节

TRtcDataProvider只响应自己接收的请求,互不干涉


TRtcDataServer(Sender) VS Sender as TRtcDataServer,考察下as与强转的效果和区别


使用多个write()与使用一个效果一样


带参数的/SQUARE

修改RtcDataProvider2的OnDataReceived事件的代码为:

var

  cnt,line:integer;

begin

with TRtcDataServer(Sender) do

  if Request.Complete then

    begin

    Write('

    Write('Here comes a table of square values ...
');

    cnt:=0;

    if Request.Query['cnt']<>'' then

      try

        cnt:=StrToInt(Request.Query['cnt']);

      except

      end;

    if (cnt

      begin

      cnt:=10;

      Write('Wrong "cnt" parameter.');

      Write('Using default value of 10.
');

      end;

    for line:=1 to cnt do

      begin

      Write('Square of '+IntToStr(line)+' = '+IntToStr(line*line));

      Write('
');

      end;

    Write('......... done.');

    Write('');

    end;

end;

编译并运行


打开浏览器,分别访问网址http://localhost/square、http://localhost/square?cnt=30、http://localhost/square?cnt=5392



自学RTC——ServerLesson3


打开上节的工程


添加组件RtcDataProvider3并设置Server属性为RtcHttpServer1,设置CheckOrder属性为900,使得RtcDataProvider3所处理的请求在其它请求之后(CheckOrder越小越先处理)


在当前exe所在路径下创建一个data文件夹,并在该文件夹内新建一个有内容的test.txt,然后编写一个GetFullFileName函数,用于从请求中提取文件名并转化为本地文件名


function GetFullFileName(fname: string): string;

var

  DocRoot: string;

begin

  DocRoot := ExtractFilePath(AppFileName);

  if Copy(DocRoot, length(DocRoot), 1) = '\' then

    Delete(DocRoot, length(DocRoot), 1);

  DocRoot := DocRoot + '\data';

  fname := StringReplace(fname, '/', '\', [rfreplaceall]);

  Result := ExpandFileName(DocRoot + fname);

  if UpperCase(Copy(Result, 1, length(DocRoot))) <> UpperCase(DocRoot) then

    Result := '';

end;

在RtcDataProvider3的OnCheckRequest事件中写上代码:

var

  fname: string;

begin

  with TRtcDataServer(Sender) do

  begin

    fname := GetFullFileName(Request.FileName);

    if (fname <> '') and (File_Exists(fname)) then

    begin

      Accept;

      Request.Info['fname'] := fname;

    end;

  end;

end;

在RtcDataProvider3的OnDataReceived事件中写上代码:

procedure TForm1.RtcDataProvider3DataReceived(Sender: TRtcConnection);

var

  fname: string;

begin

  with TRtcDataServer(Sender) do

    if Request.Complete then

    begin

      fname := Request.Info['fname'];

      if File_Exists(fname) then

        Write(Read_File(fname))

      else

        Write;

    end;

end;

编译并运行


打开浏览器,访问网址

http://localhost/test.txt


自学RTC——ServerLesson4


对于上节的示例中,比较适合发送小文件,若直接用于发送大文件的话,很容易把服务器的内存资源耗尽。当请求大文件时,可以限制每次发送大文件时使用的内存大小(例如16000 B)。


打开上节的工程


修改RtcDataProvider3的OnCheckRequest事件:


var

  fname: string;

begin

  with TRtcDataServer(Sender) do

  begin

    fname := GetFullFileName(Request.FileName);

    if (fname <> '') and (File_Exists(fname)) then

    begin

      Accept;

      Request.Info['fname'] := fname;

      Response.ContentLength := File_Size(fname);

      WriteHeader;

    end;

  end;

end;

修改RtcDataProvider3的OnDataReceived事件:

var

  fname: string;

  len: cardinal;

begin

  with TRtcDataServer(Sender) do

    if Request.Complete then

    begin

      if Response.ContentLength > Response.ContentOut then

      begin

        fname := Request.Info['fname'];

        if File_Size(fname) = Response.ContentLength then

        begin

          len := Response.ContentLength - Response.ContentOut;

          if len > 16000 then

            len := 16000;

          Write(Read_File(fname, Response.ContentOut, len));

        end

        else

          Disconnect;

      end;

    end;

end;

关联RtcDataProvider3的OnDataSent事件到OnDataReceived事件的实现,使两个事件都指向同一段代码


编译并运行


在data文件夹下放入一个大文件(例如test.mp3),访问网址http://localhost/test.mp3


流程分析

为了更进一步了解整个过程,把RtcHttpServer1和RtcDataProvider3的每个事件都记录进日志:


[22:36:08.272] RtcHttpServer1ListenStart->

[22:36:08.292] RtcDataProvider3ListenStart->

[22:36:26.108] RtcHttpServer1ClientConnect->

[22:36:26.118] RtcHttpServer1Connecting->

[22:36:26.130] RtcHttpServer1Connect->

[22:36:26.160] RtcHttpServer1DataIn->

[22:36:26.164] RtcDataProvider3CheckRequest->请求文件:F:\RTC\bin\data\test.ppt [12521984 B]

[22:36:26.169] RtcHttpServer1RequestAccepted->

[22:36:26.175] RtcDataProvider3DataOut->

[22:36:26.178] RtcHttpServer1DataOut->

[22:36:26.257] RtcHttpServer1PeekRequest->

[22:36:26.262] RtcDataProvider3DataReceived->剩余:12521984 B

[22:36:26.276] RtcDataProvider3DataOut->

[22:36:26.281] RtcHttpServer1DataOut->

[22:36:26.286] RtcDataProvider3DataSent->剩余:12505984 B

[22:36:26.301] RtcDataProvider3DataOut->

[22:36:26.311] RtcHttpServer1DataOut->

[22:36:26.316] RtcDataProvider3ReadyToSend->

[22:36:26.323] RtcDataProvider3DataSent->剩余:12489984 B

[22:36:26.338] RtcDataProvider3DataOut->

[22:36:26.345] RtcHttpServer1DataOut->

...

[22:36:50.488] RtcDataProvider3ReadyToSend->

[22:36:50.494] RtcDataProvider3DataSent->剩余:9984 B

[22:36:50.507] RtcDataProvider3DataOut->

[22:36:50.515] RtcHttpServer1DataOut->

[22:36:50.522] RtcDataProvider3ReadyToSend->

[22:36:50.528] RtcDataProvider3DataSent->

[22:36:50.534] RtcDataProvider3ResponseDone->

[22:36:50.540] RtcHttpServer1ResponseDone->

[22:38:45.919] RtcHttpServer1Disconnecting->

[22:38:45.950] RtcHttpServer1Disconnect->

[22:38:45.968] RtcHttpServer1ClientDisconnect->

[22:46:04.945] RtcDataProvider1ListenStop->

[22:46:04.955] RtcHttpServer1ListenStop->

服务端启动,RtcHttpServer1开始监听,RtcDataProvider3随着也开始监听


在浏览器中请求文件,RtcHttpServer1收到客户端的连接,RtcHttpServer1开始连接,连接成功,RtcHttpServer1收到数据(请求),RtcDataProvider3也收到请求


RtcDataProvider3在OnCheckRequest事件中检查请求,文件存在,接受请求


RtcHttpServer1确定请求被接受,RtcDataProvider3把文件名写入请求信息Request.Info['fname']中备用,把文件大小写入到响应长度Response.ContentLength里,然后把响应头发送出去(实际是发送给RtcHttpServer1,然后RtcHttpServer1把响应最终发出)


RtcHttpServer1Peek请求,RtcDataProvider3触发OnDataReceived事件


RtcDataProvider3接收到完整的请求后才进行处理,文件未发现修改(大小未改变)且文件超过16000 B,只发送16000 B(发给RtcHttpServer1,RtcHttpServer1再转发)


RtcDataProvider3数据发出,触发OnDataSent事件,事件处理中发现文件未发完,准备数据继续发送(依然是只发送16000 B,由RtcHttpServer1转发),发送后又触发OnDataSent事件……


直到文件数据全部发送完,RtcDataProvider3完成响应,RtcHttpServer1也完成响应


浏览器关闭,RtcHttpServer1开始断开连接,连接断开


关闭服务端,RtcDataProvider3停止监听,RtcHttpServer1也停止监听




自学RTC——BrowserUpload


核心代码

OnCheckRequest事件中的代码:

with TRtcDataServer(Sender) do

  if Request.FilePath.Equal(0, 'UPLOAD') then

    Accept;

OnDataReceived事件中的代码:

var

  fname: string;

  cnt: integer;

begin

  with TRtcDataServer(Sender) do

  begin

    if Request.Method = 'GET' then

    begin

      Write('

      Write('Type some text, if you like:
');

      Write('
');

      Write('Please specify a single file to upload:
');

      Write('
');

      Write('Please specify one or more files to upload:
');

      Write('
');

      Write('');

      Write('');

    end

    else

    begin

      Request.Params.AddText(Read);

      if Request.Complete then

      begin

        Write('

        if Request.Params['textline'] <> '' then

          Write('You typed this text:
' + Request.Params['textline'] + '

');

        // Uploading a single file ...

        if Request.Params.IsFile('onefile') then

        begin

          if not DirectoryExists(eUploadFolder.Text) then

            CreateDir(eUploadFolder.Text);

          fname := Request.Params['onefile'];

          if Request.Params.GetFile('onefile', eUploadFolder.Text + '\' +

            ExtractFileName(fname)) then

            Write('File "' + fname + '" was uploaded using the "single file" field.
')

          else

            Write('Error receiving File "' + fname + '".
');

        end

        else

          Write('Single file was NOT uploaded (OneFile parameter empty)
.');

        // Uploading one or more files ...

        if Request.Params.IsFile('morefiles') then

        begin

          if not DirectoryExists(eUploadFolder.Text) then

            CreateDir(eUploadFolder.Text);

          for cnt := 0 to Request.Params.ElementCount['morefiles'] - 1 do

          begin

            fname := Request.Params.Element['morefiles', cnt];

            if Request.Params.GetFile('morefiles', eUploadFolder.Text + '\' +

              ExtractFileName(fname), cnt) then

              Write('File "' + fname +

                '" was uploaded using the "more files" field at [' + IntToStr(cnt)

                + '].
')

            else

              Write('Error receiving File "' + fname + '".
');

          end;

        end

        else

          Write('Multiple files were NOT uploaded (MoreFiles parameter empty).');

        Write('');

      end;

    end;

  end;

end;


RTC组件关系图

up-c7f07133cbe4ab333686f389e16e917c348.png


自学RTC——DualServer


在窗体上放4个TRtcHttpServer,依次设置ServerPort为:80、443、8080和8090,并分别命名为HS80、HS443、HS8080和HS8090


再放3个TRtcDualDataServerLink到窗体上,分别命名为DL80and443、DL8080and8090和DLall


设置DL80and443的Server属性为HS80,Server2属性为HS443;设置DL8080and8090的Server属性为HS8080,Server2属性为HS8090


设置DLall的Link属性为DL80and443,Link2属性为DL8080and8090


再放1个TRtcDataProvider到窗体上,设置Link属性为DLall,并在OnCheckRequest事件里写上代码:


with TRtcDataServer(Sender) do

begin

  Accept;

  Write('you are on Server ' + ServerPort);

end;

在窗口OnShow事件里启动所有TRtcHttpServer,在OnClose事件里停止所有TRtcHttpServer


编译运行


在浏览器里分别访问http://localhost:80、http://localhost:443、http://localhost:8080、http://localhost:8090


注意:TRtcDualDataServerLink只能选择设置Server*或Link*!


感谢:超级无敌大懒虫原创分享

来源:https://my.oschina.net/afrusrsc?tab=newest&catalogId=3603655


推荐分享
图文皆来源于网络,内容仅做公益性分享,版权归原作者所有,如有侵权请告知删除!
 

Copyright © 2014 DelphiW.com 开发 源码 文档 技巧 All Rights Reserved
晋ICP备14006235号-8 晋公网安备 14108102000087号

执行时间: 0.042726993560791 seconds