我個人把收銀機前後台網路通訊歸類為四種主要通訊命令, 建立好後這四種通訊命令機制後, pos 前後台之間的各種通訊需求都可搞定或套用, 基本上脫離不了這四種應用範疇, 不用為了一些特殊查詢去開規格或另製電文格式, 其它業種之程式需求當然也可應用到 , 本範例主要是使用 INDY 10 的 IdTCPServer 及 IdTCPClient 兩個控件達成
1.SEND_STR 命令 : pos 向主機發出 STRING 資料
例如 : pos 送出 "I AM ALIVE" (我還活著), 告知主機 pos 連線狀況
2.GET_STR 命令 : pos 向主機要求取回 STRING 資料
例如 : pos 向後台要回主機時間字串以便同步 pos 時間,
或是簡單查詢字串 (送出會員卡號, 從主機取回會員姓名等)
3.SEND_FILE 命令 : pos 向主機送出 FILE
例如 : pos 把交易資料檔送至主機
4.GET_FILE 命令 : pos 向主機要求取回 FILE (可能有一筆或多筆檔案)
例如 : pos 從主機取得 "程式更新", "主檔配信",
甚至較複雜的查詢(如退貨交易資料內容)都可透過 GET_FILE 命令機制取回
pos
pos
這些機制可以綁在 pos 程式中, 變成 pos 程式的一部份; 也可獨立出成一 Middleware 程式
--------------------------------------------------------------------------------------------------------------------------------------
INDY 是 Borland Delphi/BCB 上有名的網路通訊元件, 現已移植到 Lazarus 上, INDY 10 跟 INDY 9 的 CODING 方式差異很大, 為了實做以上提到的四種命令機制, 研究了幾個晚上才有所成果, 把網路上找到的一些程式碼片段連結起來改成我要的機能就成了本程式, 其中包含大檔案的分割傳送, Server 收到 Client 取檔指令後如何透過原連線回送檔案到 Client(不用再開一新連線), 分享給大家
本程式內部 INDY 控件使用示意圖
CLIENT 端程式
//----------------------------------------------------------------------------
//函式使用例 : SND_STR('HELLO',0);
//----------------------------------------------------------------------------
procedure TForm1.SND_STR(command_text: String ; command_tag:integer);
begin
if (connection_busy) then exit;
connection_busy:=true;
if (IdTCPClient1.Connected) then IdTCPClient1.Disconnect;
IdTCPClient1.Host := Edit1.Text;
IdTCPClient1.ReadTimeout:=1000;
MemoLog('●SND_STR('+command_text+','+IntToStr(command_tag)+') 開始');
try
IdTCPClient1.Connect;
except
MemoLog('連結失敗');
connection_busy:=false;
MemoLog('○SND_STR() 結束');
exit;
end;
try
IdTCPClient1.IOHandler.Write(LongInt(1),true); //Command_Id => 1:SND_STR 2:GET_STR 3:SND_FILE 4:GET_FILE
IdTCPClient1.IOHandler.WriteLn(command_text); //Command_Text
IdTCPClient1.IOHandler.Write(command_tag,true); //Command_Tag
finally
IdTCPClient1.Disconnect;
connection_busy:=false;
MemoLog('○SND_STR() 結束');
end;
end;
//----------------------------------------------------------------------------
//函式使用例 : ShowMessage(GET_STR('TIME',0)); //秀出主機時間字串
//----------------------------------------------------------------------------
function TForm1.GET_STR(command_text: String ; command_tag:integer): String;
var r:String;
begin
if (connection_busy) then exit;
connection_busy:=true;
if (IdTCPClient1.Connected) then IdTCPClient1.Disconnect;
IdTCPClient1.Host := Edit1.Text;
IdTCPClient1.ReadTimeout:=1000;
MemoLog('●GET_STR('+command_text+','+IntToStr(command_tag)+') 開始');
r:='';
try
IdTCPClient1.Connect;
except
MemoLog('連結失敗');
connection_busy:=false;
MemoLog('○GET_STR() 結束');
result:=r;
exit;
end;
try
IdTCPClient1.IOHandler.Write(LongInt(2),true); //Command_Id => 1:SND_STR 2:GET_STR 3:SND_FILE 4:GET_FILE
IdTCPClient1.IOHandler.WriteLn(command_text); //Command_Text
IdTCPClient1.IOHandler.Write(command_tag,true); //Command_Tag
r:=IdTCPClient1.IOHandler.ReadLn;
MemoLog('接收到回傳字串 : "'+r+'"');
finally
IdTCPClient1.Disconnect;
connection_busy:=false;
MemoLog('○GET_STR() 結束');
end;
result:=r;
end;
//----------------------------------------------------------------------------
//函式使用例 : if (OpenDialog1.Execute) then SND_FILE(OpenDialog1.FileName,0);
//----------------------------------------------------------------------------
procedure TForm1.SND_FILE(command_text: String ; command_tag:integer);
var buff:array[0..1024] of byte;
buff2:array of Byte; //TBytes
buff3:TIdBytes; //uses idGlobal
fstream:TFileStream;
filesize:integer;
filename:String;
readcount:integer;
begin
if (connection_busy) then exit;
connection_busy:=true;
if (IdTCPClient1.Connected) then IdTCPClient1.Disconnect;
IdTCPClient1.Host := Edit1.Text;
IdTCPClient1.ReadTimeout:=1000;
MemoLog('●SND_FILE('+command_text+','+IntToStr(command_tag)+') 開始');
try
IdTCPClient1.Connect;
except
MemoLog('連結失敗');
connection_busy:=false;
MemoLog('○SND_FILE() 結束');
exit;
end;
try
fstream:=TFileStream.Create(command_text, fmShareDenyWrite); //fmOpenRead(會獨佔), fmShareDenyWrite, fmShareDenyNone
filename:=ExtractFileName(command_text);
filesize:=fstream.Size;
IdTCPClient1.IOHandler.Write(LongInt(3),true); //Command_Id => 1:SND_STR 2:GET_STR 3:SND_FILE 4:GET_FILE
IdTCPClient1.IOHandler.WriteLn(filename); //Command_Text
IdTCPClient1.IOHandler.Write(filesize,true); //Command_Tag
MemoLog('發送檔案:'+command_text);
fstream.position:=0;
While (fstream.position < filesize) do
begin
if ((filesize - fstream.position) >= 1024) then //PASCAL 不能用 SIZEOF(buff) ??
readcount := 1024
else
readcount := filesize - fstream.position;
fstream.ReadBuffer(buff, readcount);
SetLength(buff3,readcount);
Move(buff[0],buff3[0],readcount);
IdTCPClient1.Socket.Write(buff3, readcount);
end;
MemoLog('發送檔案完成 ('+IntToStr(filesize)+' bytes)');
finally
fstream.Free;
IdTCPClient1.Disconnect;
connection_busy:=false;
MemoLog('○SND_FILE() 結束');
end;
end;
//----------------------------------------------------------------------------
//函式使用例 : GET_FILE('PG_DOWNLOAD',0); //取得程式更新檔案 ('PG_DOWNLOAD'為自訂的程式更新識別字)
//----------------------------------------------------------------------------
procedure TForm1.GET_FILE(command_text: String ; command_tag:integer);
var buff:array[0..1024] of byte;
buff3:TIdBytes; //uses idGlobal
fstream:TFileStream;
filesize:integer;
filename:String;
readcount:integer;
get_file_path:String;
TmpList: TStringList;
TmpList2: TStringList;
i: integer;
begin
if (connection_busy) then exit;
connection_busy:=true;
if (IdTCPClient1.Connected) then IdTCPClient1.Disconnect;
IdTCPClient1.Host := Edit1.Text;
IdTCPClient1.ReadTimeout:=5000;
MemoLog('●GET_FILE('+command_text+','+IntToStr(command_tag)+') 開始');
try
IdTCPClient1.Connect;
except
MemoLog('連結失敗');
connection_busy:=false;
MemoLog('○GET_FILE 結束');
exit;
end;
try
IdTCPClient1.IOHandler.Write(LongInt(4),true); //Command_Id => 1:SND_STR 2:GET_STR 3:SND_FILE 4:GET_FILE
IdTCPClient1.IOHandler.WriteLn(command_text); //Command_Text
IdTCPClient1.IOHandler.Write(command_tag,true); //Command_Tag
get_file_path:='';
if (Trim(command_text)='PG_DOWNLOAD') then get_file_path:='C:\PG';
TmpList:=TStringList.Create;
TmpList2:=TStringList.Create;
//讀回檔案可能有多個
IdTCPClient1.Socket.ReadStrings(TmpList); //讀回檔名列
IdTCPClient1.Socket.ReadStrings(TmpList2); //讀回檔案 size 列
for i:=0 to TmpList.Count-1 do
begin
filename:=TmpList.Strings[i];
filesize:=StrToInt(TmpList2.Strings[i]);
//假如 Client 端已存在該檔案要先刪除
filename:=get_file_path+'\'+filename;
ForceDirectories(get_file_path);
if (FileExists(filename)) then DeleteFile(filename);
try
fstream:=TFileStream.Create(filename, fmCreate); //接收端檔名路徑
MemoLog('接收檔案:'+filename);
while (filesize > fstream.Size) do
begin
if ((filesize - fstream.Size) > 1024) then
readcount := 1024
else
readcount := filesize - fstream.Size;
//MemoLog("接收 bytes:"+IntToStr(readcount));
IdTCPClient1.Socket.ReadBytes(buff3, readcount, false);
Move(buff3[0],buff[0],readcount);
fstream.WriteBuffer(buff, readcount);
Application.ProcessMessages();
end;
finally
MemoLog('接收檔案完成 ('+IntToStr(filesize)+' bytes)');
fstream.Free;
end;
end;
finally
TmpList.Free;
TmpList2.Free;
IdTCPClient1.Disconnect;
connection_busy:=false;
MemoLog('○GET_FILE 結束');
end;
end;
--------------------------------------------------------------------------------------------------------------------------------------
SERVER 端程式
只要放一個 IdTCPServer 控件, 在其 OnExecute() 事件中處理可以所有命令需求
procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);
var command_id:integer;
command_text: String ;
command_tag:integer;
client_ip:String;
buff:array[0..1023] of byte;
buff2:array of Byte;
buff3:TIdBytes; //uses idGlobal
fstream:TFileStream;
filesize:integer;
filename:String;
readcount:integer;
i:integer;
SID:Integer;
TmpList:TStringList;
TmpList2:TStringList;
TmpList3:TStringList;
MySync: TMySync;
begin
MySync:=TMySync.Create();
Randomize();
SID:=Random(100)+Random(100); //INDY 10 沒有 THREAD ID, 需要的話可用這模擬
IncrConnectioncount;
client_ip:=AContext.Connection.Socket.Binding.PeerIP; //同 AContext.Binding.PeerIP;
try
command_id:=AContext.Connection.IOHandler.ReadLongInt(true); //Command_Id => 1:SND_STR 2:GET_STR 3:SND_FILE 4:GET_FILE
//MemoLog('command_id='+IntToStr(command_id));
case command_id of
1 : begin //收到 SND_STR 命令
//command_text:=AContext.Connection.IOHandler.ReadLn('\n',1000,-1);
command_text:=AContext.Connection.IOHandler.ReadLn;
command_tag :=AContext.Connection.IOHandler.ReadLongInt(true);
if (command_text='pos_CONNECT') then
begin
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_STR): '+command_text+'( pos_ID = '+IntToStr(command_tag)+')';
MySync.Synchronize;
end
else
begin
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_STR): '+command_text+'('+IntToStr(command_tag)+')';
MySync.Synchronize;
end;
end;
2 : begin //收到 GET_STR 命令
//command_text:=AContext.Connection.IOHandler.ReadLn('\n',1000,-1);
command_text:=AContext.Connection.IOHandler.ReadLn;
command_tag :=AContext.Connection.IOHandler.ReadLongInt(true);
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (GET_STR): '+command_text+'('+IntToStr(command_tag)+')';
MySync.Synchronize;
if (command_text='TIME') then
begin
AContext.Connection.IOHandler.WriteLn('SERVER TIME IS '+FormatDateTime('hh:nn:ss', Now));
end
end;
3 : begin //收到 SND_FILE 命令
command_text:=AContext.Connection.IOHandler.ReadLn;
command_tag :=AContext.Connection.IOHandler.ReadLongInt(true);
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_FILE): '+command_text+'('+IntToStr(command_tag)+')';
MySync.Synchronize;
filename:='C:\'+command_text;
filesize:=command_tag;
if (FileExists(filename)) then DeleteFile(filename);
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_FILE): SERVER 接收檔案 = '+filename;
MySync.Synchronize;
fstream:=TFileStream.Create(filename, fmCreate);
while (filesize>fstream.Size) do
begin
if ((filesize-fstream.Size) >= 1024) then
readcount := 1024
else
readcount := filesize - fstream.Size;
//log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_FILE): read_bytes = '+IntToStr(readcount);
//MySync.Synchronize;
//SetLength(buff3,readcount); //這裡可以不用加
AContext.Connection.Socket.ReadBytes(buff3, readcount, false);
Move(buff3[0],buff[0],readcount);
fstream.WriteBuffer(buff, readcount);
end;
fstream.Free;
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (SND_FILE): SERVER 接收檔案完成 ('+IntToStr(filesize)+' bytes)';
MySync.Synchronize;
end;
4 : begin //收到 GET_FILE 命令
command_text:=AContext.Connection.IOHandler.ReadLn;
command_tag :=AContext.Connection.IOHandler.ReadLongInt(true);
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (GET_FILE): '+command_text+'('+IntToStr(command_tag)+')';
MySync.Synchronize;
if (command_text='PG_DOWNLOAD') then
begin
TmpList:=TStringList.Create;
TmpList2:=TStringList.Create;
TmpList3:=TStringList.Create;
_GetFileList(TmpList,'C:\windows\system32\*.*');
//檔名串列
TmpList2.Clear;
for i:=0 to TmpList.Count-1 do
begin
TmpList2.Add(ExtractFileName(TmpList.Strings[i]));
end;
//檔案 Size 串列
TmpList3.Clear;
for i:=0 to TmpList.Count-1 do
begin
TmpList3.Add(IntToStr(_FileSize(TmpList.Strings[i])));
end;
AContext.Connection.IOHandler.Write(TmpList2, true);
AContext.Connection.IOHandler.Write(TmpList3, true);
//傳送檔案
for i:=0 to TmpList.Count-1 do
begin
filename:=TmpList.Strings[i];
fstream:=TFileStream.Create(filename, fmShareDenyWrite); //fmOpenRead(會獨佔), fmShareDenyWrite, fmShareDenyNone
filesize:=fstream.Size;
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (GET_FILE): SERVER 回傳檔案 = '+filename;
MySync.Synchronize;
while (fstream.position < filesize) do
begin
if ((filesize - fstream.position) >= 1024) then
readcount := 1024
else
readcount := filesize - fstream.position;
//debug 用, 打開的話傳大檔會慢很多
//log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (GET_FILE): send_bytes = '+IntToStr(readcount);
//MySync.Synchronize;
fstream.ReadBuffer(buff, readcount);
SetLength(buff3,readcount);
Move(buff[0],buff3[0],readcount);
AContext.Connection.IOHandler.Write(buff3, readcount);
end;
fstream.Free;
log_text:=client_ip+'/'+{IntToStr(AContext.Data)+}' (GET_FILE): SERVER 回傳檔案完成 ('+IntToStr(filesize)+' bytes)';
MySync.Synchronize;
end;
TmpList.Free;
pos TmpList2.Free;
end;
end;
else
//MemoLog(client_ip);
end;
finally
AContext.Connection.Disconnect();
DecrConnectioncount;
end;
MySync.Free;
end;
--------------------------------------------------------------------------------------------------------------------------------------
■ 相關參考
1. 程式下載 (含 source code) http://digitraveler.homelinux.com/down_load/LazInLine.zip
2. Indy 10.2.0.3 for Lazarus : http://www.indyproject.org/Sockets/fpc/index.en.aspx
在 Lazarus Win32 下, Indy 9 無法安裝, 所以才用 Indy 10, 安裝經驗如下 :
(1)解壓後要把 Indy10\fpc 及 Indy10\lazarus 兩個目錄內檔案合併在一起
(如全部 copy 到 Indy10\lazarus\)
(2)在 Lazarus IDE 環境下開啟 Indy10\lazarus\indylaz.lpk 檔 Comple 後 Install
(3)若 Install 過程有找不到 *.pas , 不理它, 再次 Comple 及 Install 就可以了 (原因不明 ??)