用Delphi設(shè)計(jì)自己的代理服務(wù)器
發(fā)表時(shí)間:2023-07-15 來源:明輝站整理相關(guān)軟件相關(guān)文章人氣:
[摘要]筆者在編寫一個(gè)上網(wǎng)計(jì)費(fèi)軟件時(shí),涉及到如何對(duì)局域網(wǎng)中各工作站上網(wǎng)計(jì)費(fèi)問題。一般來講,這些工作站通過代理服務(wù)器上網(wǎng),而采用現(xiàn)成的代理服務(wù)器軟件時(shí),由于代理服務(wù)器軟件是封閉的系統(tǒng),很難編寫程序獲取實(shí)時(shí)的上...
筆者在編寫一個(gè)上網(wǎng)計(jì)費(fèi)軟件時(shí),涉及到如何對(duì)局域網(wǎng)中各工作站上網(wǎng)計(jì)費(fèi)問題。一般來講,這些工作站通過代理服務(wù)器上網(wǎng),而采用現(xiàn)成的代理服務(wù)器軟件時(shí),由于代理服務(wù)器軟件是封閉的系統(tǒng),很難編寫程序獲取實(shí)時(shí)的上網(wǎng)計(jì)時(shí)信息。因此,考慮是否能編寫自己的代理服務(wù)器,一方面解決群體上網(wǎng),另一方面又解決上網(wǎng)的計(jì)費(fèi)問題呢?
經(jīng)過實(shí)驗(yàn)性編程,終于圓滿地解決了該問題,F(xiàn)寫出來,與各位同行分享。
1、 思路
當(dāng)前流行的瀏覽器的系統(tǒng)選項(xiàng)中有一個(gè)參數(shù),即“通過代理服務(wù)器連接”,經(jīng)過編程測(cè)
試,當(dāng)局域網(wǎng)中一臺(tái)工作站指定了該屬性,再發(fā)出Internet請(qǐng)求時(shí),請(qǐng)求數(shù)據(jù)將發(fā)送到所指定的代理服務(wù)器上,以下為請(qǐng)求數(shù)據(jù)包示例:
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
其中第一行為目標(biāo)URL及相關(guān)方法、協(xié)議,“Host”行指定了目標(biāo)主機(jī)的地址。
由此知道了代理服務(wù)的過程:接收被代理端的請(qǐng)求、連接真正的主機(jī)、接收主機(jī)返回的數(shù)據(jù)、將接收數(shù)據(jù)發(fā)送到被代理端。
為此可編寫一個(gè)簡(jiǎn)單的程序,完成上述網(wǎng)絡(luò)通信重定向問題。
用Delphi設(shè)計(jì)時(shí),選用ServerSocket作為與被代理工作站通信的套接字控件,選用ClientSocket動(dòng)態(tài)數(shù)組作為與遠(yuǎn)程主機(jī)通信的套接字控件。
編程時(shí)應(yīng)解決的一個(gè)重要問題是多重連接處理問題,為了加快代理服務(wù)的速度和被代理端的響應(yīng)速度,套接字控件的屬性應(yīng)設(shè)為非阻塞型;各通信會(huì)話與套接字動(dòng)態(tài)綁定,用套接字的SocketHandle屬性值確定屬于哪一個(gè)會(huì)話。
通信的銜接過程如下圖所示:
代理服務(wù)器
Serversocket
(1) 接 收
被代理端 發(fā) 送 遠(yuǎn)程主機(jī)
(6) (2) (5)
Browser ClientSocket (4) Web Server
接 收
發(fā) 送 (3)
(1)、被代理端瀏覽器發(fā)出Web請(qǐng)求,代理服務(wù)器的Serversocket接收到請(qǐng)求。
(2)、代理服務(wù)器程序自動(dòng)創(chuàng)建一個(gè)ClientSocket,并設(shè)置主機(jī)地址、端口等屬性,然后連接遠(yuǎn)程主機(jī)。
(3)、遠(yuǎn)程連通后激發(fā)發(fā)送事件,將Serversocket接收到的Web請(qǐng)求數(shù)據(jù)包發(fā)送到遠(yuǎn)程主機(jī)。
(4)、當(dāng)遠(yuǎn)程主機(jī)返回頁(yè)面數(shù)據(jù)時(shí),激發(fā)ClientSocket的讀事件,讀取頁(yè)面數(shù)據(jù)。
(5)、代理服務(wù)器程序根據(jù)綁定信息確定屬于ServerSocket控件中的哪一個(gè)Socket應(yīng)該將從主機(jī)接收的頁(yè)面信息發(fā)送到被代理端。
(6)、ServerSocket中的對(duì)應(yīng)Socket將頁(yè)面數(shù)據(jù)發(fā)送到被代理端。
2、 程序編寫
使用Delphi設(shè)計(jì)以上通信過程非常簡(jiǎn)單,主要是ServerSocket、ClientSocket的相關(guān)事
件驅(qū)動(dòng)程序的程序編寫。下面給出作者編寫的實(shí)驗(yàn)用代理服務(wù)器界面與源程序清單,內(nèi)含簡(jiǎn)要功能說明:
unit main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, ScktComp, TrayIcon, Menus, StdCtrls;
type
session_record=record
Used: boolean; {會(huì)話記錄是否可用}
SS_Handle: integer; {代理服務(wù)器套接字句柄}
CSocket: TClientSocket; {用于連接遠(yuǎn)程的套接字}
Lookingup: boolean; {是否正在查找服務(wù)器}
LookupTime: integer; {查找服務(wù)器時(shí)間}
Request: boolean; {是否有請(qǐng)求}
request_str: string; {請(qǐng)求數(shù)據(jù)塊}
client_connected: boolean; {客戶機(jī)聯(lián)機(jī)標(biāo)志}
remote_connected: boolean; {遠(yuǎn)程服務(wù)器連接標(biāo)志}
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; {代理服務(wù)是否開啟}
session: array of session_record; {會(huì)話數(shù)組}
sessions: integer; {會(huì)話數(shù)}
LookUpTimeOut: integer; {連接超時(shí)值}
InvalidRequests: integer; {無效請(qǐng)求數(shù)}
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
file://系統(tǒng)啟動(dòng)定時(shí)器,啟動(dòng)窗顯示完成后,縮小到System Tray…
procedure TForm1.Timer2Timer(Sender: TObject);
begin
timer2.Enabled:=false; {關(guān)閉定時(shí)器}
sessions:=0; {會(huì)話數(shù)=0}
Application.OnException := AppException; {為了屏蔽代理服務(wù)器出現(xiàn)的異常}
invalidRequests:=0; {0錯(cuò)誤}
LookUpTimeOut:=60000; {超時(shí)值=1分鐘}
timer1.Enabled:=true; {打開定時(shí)器}
n11.Enabled:=false; {開啟服務(wù)菜單項(xiàng)失效}
n21.Enabled:=true; {關(guān)閉服務(wù)菜單項(xiàng)有效}
serversocket1.Port:=988; {代理服務(wù)器端口=988}
serversocket1.Active:=true; {開啟服務(wù)}
form1.hide; {隱藏界面,縮小到System Tray上}
end;
file://開啟服務(wù)菜單項(xiàng)…
procedure TForm1.N11Click(Sender: TObject);
begin
serversocket1.Active:=true; {開啟服務(wù)}
end;
file://停止服務(wù)菜單項(xiàng)…
procedure TForm1.N21Click(Sender: TObject);
begin
serversocket1.Active:=false; {停止服務(wù)}
N11.Enabled:=True;
N21.Enabled:=False;
Service_Enabled:=false; {標(biāo)志清零}
end;
file://主窗口建立…
procedure TForm1.FormCreate(Sender: TObject);
begin
Service_Enabled:=false;
timer2.Enabled:=true; {窗口建立時(shí),打開定時(shí)器}
end;
file://窗口關(guān)閉時(shí)…
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
timer1.Enabled:=false; {關(guān)閉定時(shí)器}
if Service_Enabled then
serversocket1.Active:=false; {退出程序時(shí)關(guān)閉服務(wù)}
end;
file://退出程序按鈕…
procedure TForm1.N01Click(Sender: TObject);
begin
form1.Close; {退出程序}
end;
file://開啟代理服務(wù)后…
procedure TForm1.ServerSocket1Listen(Sender: TObject;
Socket: TCustomWinSocket);
begin
Service_Enabled:=true; {置正在服務(wù)標(biāo)志}
N11.Enabled:=false;
N21.Enabled:=true;
end;
file://被代理端連接到代理服務(wù)器后,建立一個(gè)會(huì)話,并與套接字綁定…
procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j: integer;
begin
j:=-1;
for i:=1 to sessions do {查找是否有空白項(xiàng)}
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 {無,新增一個(gè)}
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; {保存句柄,實(shí)現(xiàn)綁定}
session[j].Request:=false; {無請(qǐng)求}
session[j].client_connected:=true; {客戶機(jī)已連接}
session[j].remote_connected:=false; {遠(yuǎn)程未連接}
edit1.text:=inttostr(sessions);
end;
file://被代理端斷開時(shí)…
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; {客戶機(jī)未連接}
if session[i-1].remote_connected then
session[i-1].CSocket.active:=false {假如遠(yuǎn)程尚連接,斷開它}
else
session[i-1].Used:=false; {假如兩者都斷開,則置釋放資源標(biāo)志}
break;
end;
j:=sessions;
k:=0;
for i:=1 to j do {統(tǒng)計(jì)會(huì)話數(shù)組尾部有幾個(gè)未用項(xiàng)}
begin
if session[j-i].Used then
break;
inc(k);
end;
if k>0 then {修正會(huì)話數(shù)組,釋放尾部未用項(xiàng)}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
file://通信錯(cuò)誤出現(xiàn)時(shí)…
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; {客戶機(jī)未連接}
if session[i-1].remote_connected then
session[i-1].CSocket.active:=false {假如遠(yuǎn)程尚連接,斷開它}
else
session[i-1].Used:=false; {假如兩者都斷開,則置釋放資源標(biāo)志}
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://被代理端發(fā)送來頁(yè)面請(qǐng)求時(shí)…
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
tmp,line,host: string;
i,j,port: integer;
begin
for i:=1 to sessions do {判斷是哪一個(gè)會(huì)話}
if session[i-1].Used and (session[i-1].SS_Handle=socket.sockethandle) then
begin
session[i-1].request_str:=socket.ReceiveText; {保存請(qǐng)求數(shù)據(jù)}
tmp:=session[i-1].request_str; {存放到臨時(shí)變量}
memo1.lines.add(tmp);
j:=pos(char(13)+char(10),tmp); {一行標(biāo)志}
while j>0 do {逐行掃描請(qǐng)求文本,查找主機(jī)地址}
begin
line:=copy(tmp,1,j-1); {取一行}
delete(tmp,1,j+1); {刪除一行}
j:=pos('Host',line); {主機(jī)地址標(biāo)志}
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); {獲取主機(jī)地址}
port:=80;
end;
if not session[i-1].remote_connected then {假如遠(yuǎn)征尚未連接}
begin
session[i-1].Request:=true; {置請(qǐng)求數(shù)據(jù)就緒標(biāo)志}
session[i-1].CSocket.host:=host; {設(shè)置遠(yuǎn)程主機(jī)地址}
session[i-1].CSocket.port:=port; {設(shè)置端口}
session[i-1].CSocket.active:=true; {連接遠(yuǎn)程主機(jī)}
session[i-1].Lookingup:=true; {置標(biāo)志}
session[i-1].LookupTime:=0; {從0開始計(jì)時(shí)}
end
else
{假如遠(yuǎn)程已連接,直接發(fā)送請(qǐng)求}
session[i-1].CSocket.socket.sendtext(session[i-1].request_str);
break; {停止掃描請(qǐng)求文本}
end;
j:=pos(char(13)+char(10),tmp); {指向下一行}
end;
break; {停止循環(huán)}
end;
end;
file://當(dāng)連接遠(yuǎn)程主機(jī)成功時(shí)…
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; {置遠(yuǎn)程主機(jī)已連通標(biāo)志}
session[i-1].Lookingup:=false; {清標(biāo)志}
break;
end;
end;
file://當(dāng)遠(yuǎn)程主機(jī)斷開時(shí)…
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 {假如客戶機(jī)已斷開,則置釋放資源標(biāo)志}
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 {修正會(huì)話數(shù)組}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
file://當(dāng)與遠(yuǎn)程主機(jī)通信發(fā)生錯(cuò)誤時(shí)…
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 {假如客戶機(jī)已斷開,則置釋放資源標(biāo)志}
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 {修正會(huì)話數(shù)組}
begin
sessions:=sessions-k;
setlength(session,sessions);
end;
edit1.text:=inttostr(sessions);
end;
file://向遠(yuǎn)程主機(jī)發(fā)送頁(yè)面請(qǐng)求…
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); {假如有請(qǐng)求,發(fā)送}
session[i-1].Request:=false; {清標(biāo)志}
end;
break;
end;
end;
file://遠(yuǎn)程主機(jī)發(fā)來頁(yè)面數(shù)據(jù)時(shí)…
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
i,j: integer;
rec_bytes: integer; {傳回的數(shù)據(jù)塊長(zhǎng)度}
rec_Buffer: array[0..2047] of char; {傳回的數(shù)據(jù)塊緩沖區(qū)}
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); {接收數(shù)據(jù)}
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); {發(fā)送數(shù)據(jù)}
break;
end;
break;
end;
end;
file://“頁(yè)面找不到”等錯(cuò)誤信息出現(xiàn)時(shí)…
procedure TForm1.AppException(Sender: TObject; E: Exception);
begin
inc(invalidrequests);
end;
file://查找遠(yuǎn)程主機(jī)定時(shí)…
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 {假如超時(shí)}
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; {斷開客戶機(jī)}
break;
end;
end;
end;
end;
end.
3、 后記
由于這種設(shè)計(jì)思路僅僅在被代理端和遠(yuǎn)程主機(jī)之間增加了一個(gè)重定向功能,被代理端原
有的緩存技術(shù)等特點(diǎn)均保留,因此效率較高。經(jīng)過測(cè)試,利用1個(gè)33.6K的Modem上網(wǎng)時(shí),三到十個(gè)被代理工作站同時(shí)上網(wǎng),仍有較好的響應(yīng)速度。由于被代理工作站和代理服務(wù)器工作站之間的連接一般通過高速鏈路,因此瓶頸主要出現(xiàn)在代理服務(wù)器的上網(wǎng)方式上。
通過上述方法,作者成功開發(fā)了一套完善的代理服務(wù)器軟件并與機(jī)房計(jì)費(fèi)系統(tǒng)完全集
成,實(shí)現(xiàn)了利用一臺(tái)工作站完成上網(wǎng)代理、上網(wǎng)計(jì)費(fèi)、用機(jī)計(jì)費(fèi)等功能。 有編程經(jīng)驗(yàn)的朋友完全可以另行增加代理服務(wù)器功能,如設(shè)定禁止訪問站點(diǎn)、統(tǒng)計(jì)客戶流量、Web訪問列表等等。