unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, IdContext, IdBaseComponent, IdComponent, IdCustomTCPServer,
IdTCPServer, StdCtrls, StrUtils, IdScheduler, IdSchedulerOfThread,
IdSchedulerOfThreadPool;
type
TForm1 = class(TForm)
Memo1: TMemo;
Label1: TLabel;
Label2: TLabel;
TCPServer: TIdTCPServer;
IdSchedulerOfThreadPool1: TIdSchedulerOfThreadPool;
procedure TCPServerExecute(AContext: TIdContext);
procedure FormShow(Sender: TObject);
private
function GetSign(Str: string; Encode: boolean): string;
procedure Split(Delimiter: string; Input: string;
Strings: TStringList);
function InStr(Start: integer; MainStr, SubStr: string): integer;
public
{Public declarations}
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
var
Fs: array of TFileStream;
Ms: array of TMemoryStream;
ClientIP, FileName: string;
SomeBytes: array of byte;
Ix: integer;
procedure TForm1.TCPServerExecute(AContext: TIdContext);
Var
CMd, Tok: string;
RecSign, SenSign, SignStr, FileSign, IpSign: string;
FilePart: string;
Det, AChar: string;
AByte: byte;
IPos, JPos, KPos, i, jj: integer;
TsL: TStringList;
begin
Det := chr(0);
SignStr := 'sign:';
CMd := AContext.Connection.IOHandler.ReadLn();
ClientIP := AContext.Connection.Socket.Binding.PeerIP;
if copy(LowerCase(CMd), 1, 4) = 'file' then
begin
Tok := copy(CMd, 5, 3);
//
//{ ******* Create new files **********}
//
if Tok = (Det + ':' + Det) then
begin
Ix := Ix + 1;
SetLength(Fs, Ix);
SetLength(Ms, Ix);
FileName := copy(CMd, 8, length(CMd));
//Create new FileStream and MemoryStream
Fs[Ix - 1] := TFileStream.Create(FileName, fmCreate);
Ms[Ix - 1] := TMemoryStream.Create;
//generate a new sign for current transfer
SenSign := GetSign(FileName + '*' + ClientIP, true);
AContext.Connection.IOHandler.WriteLn(SignStr + SenSign);
end;
//
//{ ******* Save the any part of any file **********}
//
if Tok = (Det + '#' + Det) then
begin
IPos := PosEx(SignStr, CMd, 1) + length(SignStr);
JPos := PosEx(Det + '=' + Det, CMd, IPos + 1);
//Get file sign
RecSign := copy(CMd, IPos, JPos - IPos);
RecSign := GetSign(RecSign, false);
KPos := pos('*', RecSign);
//Split the RecSign to find the FileSign and IpSign
FileSign := copy(RecSign, 1, KPos - 1);
IpSign := copy(RecSign, KPos + 1, length(RecSign));
if (IpSign = ClientIP) then
begin
try
TsL := TStringList.Create;
for i := low(Fs) to high(Fs) do
begin
if Fs[i].FileName = FileSign then
begin
//Get any part of file
FilePart := copy(CMd, JPos + 3, length(CMd));
Split('`', FilePart, TsL);
//Write the file as bytes
for jj := 0 to TsL.Count - 1 do
begin
AChar := TsL[jj];
AByte := strtoint(AChar);
Ms[i].Write(AByte, 1);
end;
end;
end;
finally
TsL.Free;
end;
end;
end;
end;
//
//{ ******* Write the compeleted files **********}
//
if Tok = (Det + '!' + Det) then
begin
IPos := PosEx(SignStr, CMd, 1) + length(SignStr);
JPos := PosEx(Det + '=' + Det, CMd, IPos + 1);
//Get file sign
RecSign := copy(CMd, IPos, JPos - IPos);
RecSign := GetSign(RecSign, false);
KPos := pos('*', RecSign);
//Split the RecSign to find the FileSign and IpSign
FileSign := copy(RecSign, 1, KPos - 1);
IpSign := copy(RecSign, KPos + 1, length(RecSign));
if (IpSign = ClientIP) then
begin
for i := low(Fs) to high(Fs) do
begin
if Fs[i].FileName = FileSign then
begin
Fs[i].Free;
Ms[i].SaveToFile(FileSign);
Ms[i].Free;
Ix := Ix - 1;
end;
end;
end;
end;
end;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
TCPServer.Active := true;
end;
function TForm1.GetSign(Str: string; Encode: boolean): string;
var
i, j: integer;
Ch, OutP: string;
begin
if Encode = true then
begin
j := 5
end
else
begin
j := -5
end;
for i := 1 to length(Str) do
begin
Ch := copy(Str, i, 1);
OutP := OutP + chr(ord(Ch[1]) + j);
end;
GetSign := OutP;
end;
//------------------
procedure TForm1.Split(Delimiter: string; Input: string;
Strings: TStringList);
var
i, x: integer;
Item: string;
begin
i := 1;
while i <= length(Input) do
begin
x := InStr(i, Input, Delimiter) - 1;
if x <> 0 then
begin
Item := copy(Input, i, x);
Strings.Add(Item);
i := i + (length(Delimiter) + length(Item));
end
else
begin
i := i + 1;
end;
end;
end;
//--------------
function TForm1.InStr(Start: integer; MainStr, SubStr: string): integer;
var
StrTmp: string;
begin
StrTmp := copy(MainStr, Start, length(MainStr));
InStr := pos(SubStr, StrTmp);
end;
end.
کلاینت :
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms,
Dialogs, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
StdCtrls;
type
TForm1 = class(TForm)
TCPClient: TIdTCPClient;
Button1: TButton;
Edit1: TEdit;
procedure TCPClientConnected(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{Private declarations}
public
{Public declarations}
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var
i, j, S: integer;
Cmd, D2Send: string;
File1: TMemoryStream;
B: array [1 .. 512] of byte;
begin
//Begin Transmit signal
TCPClient.IOHandler.WriteLn('file' + chr(0) + ':' + chr(0) + Edit1.text);
//Get a new unique sign for current transfer
Cmd := TCPClient.IOHandler.ReadLn();
if copy(Cmd, 1, 5) = 'sign:' then
begin
//Send a File
File1 := TMemoryStream.Create;
File1.LoadFromFile(Edit1.text);
S := File1.Size;
while i <= S do
begin
File1.Position := i;
File1.Read(B, 512);
for j := low(B) to high(B) do
begin
D2Send := D2Send + '`' + inttostr(B[j]);
end;
sleep(1);
//Send Parts
TCPClient.IOHandler.WriteLn('file' + (chr(0) + '#' + chr(0))
+ 'sign:' + copy(Cmd, 6, length(Cmd)) + chr(0) + '=' + chr(0)
+ D2Send + '`');
D2Send := '';
i := i + 512;
end;
end;
//End Transmit signal
TCPClient.IOHandler.WriteLn('file' + (chr(0) + '!' + chr(0))
+ 'sign:' + copy(Cmd, 6, length(Cmd)) + chr(0) + '=' + chr(0) + Cmd);
end;
procedure TForm1.FormShow(Sender: TObject);
begin
TCPClient.Connect;
end;
procedure TForm1.TCPClientConnected(Sender: TObject);
begin
Button1.Enabled := true;
end;
end.
توضیحات :
برای ارسال فایل از طریق شبکه و با استفاده از پروتکل TCP ، لازم هست تا ابتدا فایل به قطعات کوچک تر تقسیم بندی شده و سپس ارسال بشه . پیاده سازی این روش تا زمانی که ارسال فایل تنها بین یک سرور و یک مشتری صورت میگیره ساده هست . اما در روش دریافت فایل به صورت چند کاربره ، کار سرور کمی پیچیده میشه ضمن اینکه با توجه به ماهیت برنامه نویسی سوکت ، هیچ بسته ای بدون مشخصات و اصل و نسب برای ما قابل قبول نیست . برای همین قطعات فایل رو قبل از ارسال نشانه گذاری میکنیم تا در سمت سرور و در حالی که ممکنه هزاران قطعه فایل در حال دریافت شدن باشه ، بدونیم کدوم قطعه مربوط به کدوم فایل و کدوم فرستنده هست . من برای حل این مشکل از یک روش نشانه/امضا گذاری استفاده کردم که با استفاده از اون همه کلاینت ها موظف اند قبل از ارسال فایل ، یکبار اطلاعات مربوط به فایل(نام فایل) رو به سرور ارسال کرده و پس از دریافت "نشانه" ای از جانب سرور ، قطعات فایل رو با پیشوند گذاری و هویت دادن توسط اون نشانه ارسال کنند .
اما در سمت سرور چه اتفاقی میفته !؟ پیغام اولیه برای درخواست نشانه از سرور رو A و پارت های نشانه گذاری شده فایل رو Bx در نظر بگیرید .
- زمانی که پیغام A به سرور رسید، سرور کاربر جدیدی که قصد ارسال فایل داره رو در لیست(آرایه) قرار داده و نشانه مربوطه رو براش ارسال میکنه .
- کلاینت پس از دریافت نشانه شروع به ارسال Bx میکنه (هر پارت نشانه گذاری و ارسال میشه)
- سرور پیغام هایی که حاوی نشانه هستند رو در قسمتی دیگر پردازش میکنه . اطلاعاتی که از نشانه استخراج میشند میتونند شامل هر سرنخ مشخص کننده ای از هویت کلاینت ها باشند . (که در کد بالا ما از IP کلاینت و نام فایل ارسالی به عنوان نشانه استفاده میکنیم)
- با توجه به آرایه ای که در هر بار درخواست کاربران برای ارسال فایل ابعادش بروزرسانی میشه ، در قسمت دریافت Bx تشخیص میدیم که کدوم پارت رو در کدوم فایل (Fs , Ms[]) ذخیره سازی کنیم .
- و نهایتاً بعد از اتمام عملیات دریافت فایل ، ضمن Free کردن Object های مربوط به اون Transfer ، ابعاد آرایه Global رو هم به نسبت کاهش میدیم .
و اما اینجا مشکلی به وجود میاد که در ادامه عرض خواهم کرد ...
ویرایش 1 : برطرف کردن مشکل تابع Split .
ویرایش 2 : اضافه کردن توضیحات .