程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 用Delphi設計代理服務器

用Delphi設計代理服務器

編輯:Delphi
     筆者在編寫一個上網計費軟件時,涉及到如何對局域網中各工作站上網計費問題。一般來講,這些工作站通過代理服務器上網,而采用現成的代理服務器軟件時,由於代理服務器軟件是封閉的系統,很難編寫程序獲取實時的上網計時信息。因此,考慮是否能編寫自己的代理服務器,一方面解決群體上網,另一方面又解決上網的計費問題呢?
      經過實驗性編程,終於圓滿地解決了該問題。現寫出來,與各位同行分享。

  1、 思路
  當前流行的浏覽器的系統選項中有一個參數,即“通過代理服務器連接”,經過編程測
  試,當局域網中一台工作站指定了該屬性,再發出Internet請求時,請求數據將發送到所指定的代理服務器上,以下為請求數據包示例:
                   GET http://home.microsoft.com/intl/cn/ HTTP/1.0
                   Accept: */*
                   Accept-Language: zh-cn
                   Accept-Encoding: gzip, deflate
                   User-Agent: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
                   Host: home.microsoft.com
                   Proxy-Connection: Keep-Alive
  其中第一行為目標URL及相關方法、協議,“Host”行指定了目標主機的地址。
  由此知道了代理服務的過程:接收被代理端的請求、連接真正的主機、接收主機返回的數據、將接收數據發送到被代理端。
  為此可編寫一個簡單的程序,完成上述網絡通信重定向問題。
  用Delphi設計時,選用ServerSocket作為與被代理工作站通信的套接字控件,選用ClIEntSocket動態數組作為與遠程主機通信的套接字控件。
  編程時應解決的一個重要問題是多重連接處理問題,為了加快代理服務的速度和被代理端的響應速度,套接字控件的屬性應設為非阻塞型;各通信會話與套接字動態綁定,用套接字的SocketHandle屬性值確定屬於哪一個會話。
  通信的銜接過程如下圖所示:

                                    代理服務器
                                   
                                    Serversocket
                          (1)          接  收
           被代理端                   發  送                        遠程主機
                          (6)        (2)      (5)
           Browser                  ClIEntSocket       (4)            Web Server
                                      接  收
                                      發  送          (3)

  
  (1)、被代理端浏覽器發出Web請求,代理服務器的Serversocket接收到請求。
  (2)、代理服務器程序自動創建一個ClIEntSocket,並設置主機地址、端口等屬性,然後連接遠程主機。
  (3)、遠程連通後激發發送事件,將Serversocket接收到的Web請求數據包發送到遠程主機。
  (4)、當遠程主機返回頁面數據時,激發ClIEntSocket的讀事件,讀取頁面數據。
  (5)、代理服務器程序根據綁定信息確定屬於ServerSocket控件中的哪一個Socket應該將從主機接收的頁面信息發送到被代理端。
  (6)、ServerSocket中的對應Socket將頁面數據發送到被代理端。

  2、 程序編寫
  使用Delphi設計以上通信過程非常簡單,主要是ServerSocket、ClIEntSocket的相關事
  件驅動程序的程序編寫。下面給出作者編寫的實驗用代理服務器界面與源程序清單,內含簡要功能說明:

  unit main;

  interface

  uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
    ExtCtrls, ScktComp, TrayIcon, Menus, StdCtrls;

  type
     session_record=record
        Used: boolean;                       {會話記錄是否可用}
        SS_Handle: integer;                  {代理服務器套接字句柄}
        CSocket: TClIEntSocket;              {用於連接遠程的套接字}
        Lookingup: boolean;                  {是否正在查找服務器}
        LookupTime: integer;                 {查找服務器時間}
        Request: boolean;                    {是否有請求}
        request_str: string;                 {請求數據塊}
        clIEnt_connected: boolean;           {客戶機聯機標志}
        remote_connected: boolean;           {遠程服務器連接標志}
  end;

  type
    TForm1 = class(TForm)
      ServerSocket1: TServerSocket;
      ClientSocket1: TClIEntSocket;
      Timer2: TTimer;
      TrayIcon1: TTrayIcon;
      PopupMenu1: TPopupMenu;
      N11: TMenuItem;
      N21: TMenuItem;
      N1: TMenuItem;
      N01: TMenuItem;
      Memo1: TMemo;
      Edit1: TEdit;
      Label1: TLabel;
      Timer1: TTimer;
      procedure Timer2Timer(Sender: TObject);
      procedure N11Click(Sender: TObject);
      procedure FormCreate(Sender: TObject);
      procedure FormClose(Sender: TObject; var Action: TCloseAction);
      procedure N21Click(Sender: TObject);
      procedure N01Click(Sender: TObject);
      procedure ServerSocket1ClIEntConnect(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ServerSocket1ClIEntDisconnect(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ServerSocket1ClIEntError(Sender: TObject;
        Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
        var ErrorCode: Integer);
      procedure ServerSocket1ClIEntRead(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ClIEntSocket1Connect(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ClIEntSocket1Disconnect(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ClIEntSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
        ErrorEvent: TErrorEvent; var ErrorCode: Integer);
      procedure ClIEntSocket1Write(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure ClIEntSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
      procedure ServerSocket1Listen(Sender: TObject;
        Socket: TCustomWinSocket);
      procedure AppException(Sender: TObject; E: Exception);
      procedure Timer1Timer(Sender: TObject);
    private
      { Private declarations }
    public
      Service_Enabled: boolean;           {代理服務是否開啟}
      session: array of session_record;      {會話數組}
      sessions: integer;                  {會話數}
      LookUpTimeOut: integer;           {連接超時值}
      InvalidRequests: integer;            {無效請求數}
    end;

  var
    Form1: TForm1;

  implementation

  {$R *.DFM}

  file://系統啟動定時器,啟動窗顯示完成後,縮小到System Tray…
  procedure TForm1.Timer2Timer(Sender: TObject);
  begin
     timer2.Enabled:=false;     {關閉定時器}
     sessions:=0;               {會話數=0}
     Application.OnException := AppException;     {為了屏蔽代理服務器出現的異常}
     invalidRequests:=0;           {0錯誤}
     LookUpTimeOut:=60000;      {超時值=1分鐘}
     timer1.Enabled:=true;         {打開定時器}
     n11.Enabled:=false;           {開啟服務菜單項失效}
     n21.Enabled:=true;           {關閉服務菜單項有效}
     serversocket1.Port:=988;      {代理服務器端口=988}
     serversocket1.Active:=true;    {開啟服務}
     form1.hide;                 {隱藏界面,縮小到System Tray上}
  end;

  file://開啟服務菜單項…
  procedure TForm1.N11Click(Sender: TObject);
  begin
     serversocket1.Active:=true;    {開啟服務}
  end;

  
  file://停止服務菜單項…
  procedure TForm1.N21Click(Sender: TObject);
  begin
     serversocket1.Active:=false;      {停止服務}
     N11.Enabled:=True;
     N21.Enabled:=False;
     Service_Enabled:=false;           {標志清零}
  end;

  
  file://主窗口建立…
  procedure TForm1.FormCreate(Sender: TObject);
  begin
     Service_Enabled:=false;
     timer2.Enabled:=true;        {窗口建立時,打開定時器}
  end;

  file://窗口關閉時…
  procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
  begin
     timer1.Enabled:=false;          {關閉定時器}
     if Service_Enabled then
        serversocket1.Active:=false;   {退出程序時關閉服務}
  end;

  file://退出程序按鈕…
  procedure TForm1.N01Click(Sender: TObject);
  begin
     form1.Close;                     {退出程序}
  end;

  file://開啟代理服務後…
  procedure TForm1.ServerSocket1Listen(Sender: TObject;
    Socket: TCustomWinSocket);
  begin
     Service_Enabled:=true;            {置正在服務標志}
     N11.Enabled:=false;
     N21.Enabled:=true;
  end;

  file://被代理端連接到代理服務器後,建立一個會話,並與套接字綁定…
  procedure TForm1.ServerSocket1ClIEntConnect(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i,j: integer;
  begin
     j:=-1;
     for i:=1 to sessions do               {查找是否有空白項}
        if not session[i-1].Used and not session[i-1].CSocket.active then
           begin
              j:=i-1;                      {有,分配它}
              session[j].Used:=true;       {置為在用}
              break;
           end
        else
           if not session[i-1].Used and session[i-1].CSocket.active then
                 session[i-1].CSocket.active:=false;
     if j=-1 then
        begin                              {無,新增一個}
           j:=sessions;
           inc(sessions);
           setlength(session,sessions);
           session[j].Used:=true;                        {置為在用}
           session[j].CSocket:=TClIEntSocket.Create(nil);
           session[j].CSocket.OnConnect:=ClIEntSocket1Connect;
           session[j].CSocket.OnDisconnect:=ClIEntSocket1Disconnect;
           session[j].CSocket.OnError:=ClIEntSocket1Error;
           session[j].CSocket.OnRead:=ClIEntSocket1Read;
           session[j].CSocket.OnWrite:=ClIEntSocket1Write;
           session[j].Lookingup:=false;
        end;
     session[j].SS_Handle:=socket.socketHandle;    {保存句柄,實現綁定}
     session[j].Request:=false;                    {無請求}
     session[j].clIEnt_connected:=true;            {客戶機已連接}
     session[j].remote_connected:=false;           {遠程未連接}
     edit1.text:=inttostr(sessions);
  end;

  file://被代理端斷開時…
  procedure TForm1.ServerSocket1ClIEntDisconnect(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i,j,k: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].SS_Handle=socket.SocketHandle) and session[i-1].Used then
           begin
              session[i-1].clIEnt_connected:=false;   {客戶機未連接}
              if session[i-1].remote_connected then
                 session[i-1].CSocket.active:=false   {假如遠程尚連接,斷開它}
              else
                 session[i-1].Used:=false;           {假如兩者都斷開,則置釋放資源標志}
              break;
           end;
     j:=sessions;
     k:=0;
     for i:=1 to j do                        {統計會話數組尾部有幾個未用項}
        begin
           if session[j-i].Used then
              break;
           inc(k);
        end;
     if k>0 then                          {修正會話數組,釋放尾部未用項}
        begin
           sessions:=sessions-k;
           setlength(session,sessions);
        end;
     edit1.text:=inttostr(sessions);
  end;

  file://通信錯誤出現時…
  procedure TForm1.ServerSocket1ClIEntError(Sender: TObject;
    Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
    var ErrorCode: Integer);
  var
  i,j,k: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].SS_Handle=socket.SocketHandle) and session[i-1].Used then
           begin
              session[i-1].clIEnt_connected:=false;   {客戶機未連接}
              if session[i-1].remote_connected then
                 session[i-1].CSocket.active:=false   {假如遠程尚連接,斷開它}
              else
                 session[i-1].Used:=false;           {假如兩者都斷開,則置釋放資源標志}
              break;
           end;
     j:=sessions;
     k:=0;
     for i:=1 to j do
        begin
           if session[j-i].Used then
              break;
           inc(k);
        end;
     if k>0 then
        begin
           sessions:=sessions-k;
           setlength(session,sessions);
        end;
     edit1.text:=inttostr(sessions);
     errorcode:=0;
  end;

  file://被代理端發送來頁面請求時…
  procedure TForm1.ServerSocket1ClIEntRead(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  tmp,line,host: string;
  i,j,port: integer;
  begin
     for i:=1 to sessions do                 {判斷是哪一個會話}
        if session[i-1].Used and (session[i-1].SS_Handle=socket.sockethandle) then
            begin
               session[i-1].request_str:=socket.ReceiveText;  {保存請求數據}
               tmp:=session[i-1].request_str;                 {存放到臨時變量}
               memo1.lines.add(tmp);
               j:=pos(char(13)+char(10),tmp);                 {一行標志}
               while j>0 do                       {逐行掃描請求文本,查找主機地址}
                  begin
                     line:=copy(tmp,1,j-1);                  {取一行}
                     delete(tmp,1,j+1);                      {刪除一行}
                     j:=pos('Host',line);                    {主機地址標志}
                     if j>0 then
                        begin
                           delete(line,1,j+5);               {刪除前面的無效字符}
                           j:=pos(':',line);
                           if j>0 then
                              begin
                                 host:=copy(line,1,j-1);
                                 delete(line,1,j);
                                 try
                                    port:=strtoint(line);
                                 except
                                    port:=80;
                                 end;
                              end
                           else
                              begin
                                 host:=trim(line);                 {獲取主機地址}
                                 port:=80;
                              end;
                           if not session[i-1].remote_connected then  {假如遠征尚未連接}
                              begin
                                 session[i-1].Request:=true;      {置請求數據就緒標志}
                                 session[i-1].CSocket.host:=host;  {設置遠程主機地址}
                                 session[i-1].CSocket.port:=port;     {設置端口}
                                 session[i-1].CSocket.active:=true;   {連接遠程主機}
                                 session[i-1].Lookingup:=true;        {置標志}
                                 session[i-1].LookupTime:=0;          {從0開始計時}
                              end
                           else
                              {假如遠程已連接,直接發送請求}
                              session[i-1].CSocket.socket.sendtext(session[i-1].request_str);                                   
                           break;                        {停止掃描請求文本}
                        end;
                     j:=pos(char(13)+char(10),tmp);           {指向下一行}
                  end;
               break;                    {停止循環}
            end;
  end;

  file://當連接遠程主機成功時…
  procedure TForm1.ClIEntSocket1Connect(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].CSocket.socket.sockethandle=socket.SocketHandle) and session[i-1].Used then
           begin
              session[i-1].CSocket.tag:=socket.SocketHandle;
              session[i-1].remote_connected:=true;   {置遠程主機已連通標志}
              session[i-1].Lookingup:=false;         {清標志}
              break;
           end;
  end;

  
  file://當遠程主機斷開時…
  procedure TForm1.ClIEntSocket1Disconnect(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i,j,k: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
           begin
              session[i-1].remote_connected:=false;       {置為未連接}
              if not session[i-1].clIEnt_connected then
                 session[i-1].Used:=false       {假如客戶機已斷開,則置釋放資源標志}
              else
                 for k:=1 to serversocket1.Socket.ActiveConnections do
                    if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) and session[i-1].used then
                       begin
                          serversocket1.Socket.Connections[k-1].Close;
                          break;
                       end;
              break;
           end;
     j:=sessions;
     k:=0;
     for i:=1 to j do
        begin
           if session[j-i].Used then
              break;
           inc(k);
        end;
     if k>0 then                        {修正會話數組}
        begin
           sessions:=sessions-k;
           setlength(session,sessions);
        end;
     edit1.text:=inttostr(sessions);
  end;

  file://當與遠程主機通信發生錯誤時…
  procedure TForm1.ClIEntSocket1Error(Sender: TObject;
    Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
    var ErrorCode: Integer);
  var
  i,j,k: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
           begin
              socket.close;
              session[i-1].remote_connected:=false;       {置為未連接}
              if not session[i-1].clIEnt_connected then
                 session[i-1].Used:=false        {假如客戶機已斷開,則置釋放資源標志}
              else
                 for k:=1 to serversocket1.Socket.ActiveConnections do
                    if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) and session[i-1].used then
                       begin
                          serversocket1.Socket.Connections[k-1].Close;
                          break;
                       end;
              break;
           end;
     j:=sessions;
     k:=0;
     for i:=1 to j do
        begin
           if session[j-i].Used then
              break;
           inc(k);
        end;
     errorcode:=0;
     if k>0 then                        {修正會話數組}
        begin
           sessions:=sessions-k;
           setlength(session,sessions);
        end;
     edit1.text:=inttostr(sessions);
  end;

  file://向遠程主機發送頁面請求…
  procedure TForm1.ClIEntSocket1Write(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i: integer;
  begin
     for i:=1 to sessions do
        if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
           begin
              if session[i-1].Request then
                 begin
                    socket.SendText(session[i-1].request_str);   {假如有請求,發送}
                    session[i-1].Request:=false;                 {清標志}
                 end;
              break;
           end;
  end;

  file://遠程主機發來頁面數據時…
  procedure TForm1.ClIEntSocket1Read(Sender: TObject;
    Socket: TCustomWinSocket);
  var
  i,j: integer;
  rec_bytes: integer;                  {傳回的數據塊長度}
  rec_Buffer: array[0..2047] of char;  {傳回的數據塊緩沖區}
  begin
     for i:=1 to sessions do
        if (session[i-1].CSocket.tag=socket.SocketHandle) and session[i-1].Used then
           begin
              rec_bytes:=socket.ReceiveBuf(rec_buffer,2048);    {接收數據}
              for j:=1 to serversocket1.Socket.ActiveConnections do
                 if serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle then
                    begin
                       serversocket1.Socket.Connections[j-1].SendBuf(rec_buffer,rec_bytes);  {發送數據}
                       break;
                    end;
              break;
           end;
  end;

  file://“頁面找不到”等錯誤信息出現時…
  procedure TForm1.AppException(Sender: TObject; E: Exception);
  begin
    inc(invalidrequests);
  end;

  file://查找遠程主機定時…
  procedure TForm1.Timer1Timer(Sender: TObject);
  var
  i,j: integer;
  begin
     for i:=1 to sessions do
        if session[i-1].Used and session[i-1].Lookingup then    {假如正在連接}
           begin
              inc(session[i-1].LookupTime);
              if session[i-1].LookupTime>lookuptimeout then     {假如超時}
                 begin
                    session[i-1].Lookingup:=false;
                    session[i-1].CSocket.active:=false;         {停止查找}
                    for j:=1 to serversocket1.Socket.ActiveConnections do
                       if serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle then
                          begin
                             serversocket1.Socket.Connections[j-1].Close;  {斷開客戶機}
                             break;
                          end;
                 end;
           end;
  end;
  end.

  3、 後記
  由於這種設計思路僅僅在被代理端和遠程主機之間增加了一個重定向功能,被代理端原
  有的緩存技術等特點均保留,因此效率較高。經過測試,利用1個33.6K的Modem上網時,三到十個被代理工作站同時上網,仍有較好的響應速度。由於被代理工作站和代理服務器工作站之間的連接一般通過高速鏈路,因此瓶頸主要出現在代理服務器的上網方式上。
  通過上述方法,作者成功開發了一套完善的代理服務器軟件並與機房計費系統完全集
  成,實現了利用一台工作站完成上網代理、上網計費、用機計費等功能。 有編程經驗的朋友完全可以另行增加代理服務器功能,如設定禁止訪問站點、統計客戶流量、Web訪問列表等等。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved