<form id="hz9zz"></form>
  • <form id="hz9zz"></form>

      <nobr id="hz9zz"></nobr>

      <form id="hz9zz"></form>

    1. 明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

      用Delphi設計自己的代理服務器

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

      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訪問列表等等。


      日韩精品一区二区三区高清