close

我個人把收銀機前後台網路通訊歸類為四種主要通訊命令, 建立好後這四種通訊命令機制後, 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 端程式

pos


只要放一個 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 就可以了 (原因不明 ??) 

 

 

arrow
arrow
    文章標籤
    pos pos機 收銀機
    全站熱搜

    td3beval55088 發表在 痞客邦 留言(0) 人氣()