用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;
&nbs