自学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组件关系图

自学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