Concevez votre propre serveur proxy à l'aide de Delphi
Lorsque l'auteur écrivait un logiciel de facturation Internet, il s'agissait de savoir comment facturer l'accès Internet pour chaque poste de travail du réseau local. De manière générale, ces postes de travail accèdent à Internet via un serveur proxy. Lorsque vous utilisez un logiciel de serveur proxy prêt à l'emploi, étant donné que le logiciel de serveur proxy est un système fermé, il est difficile d'écrire un programme pour obtenir des informations de synchronisation d'accès à Internet en temps réel. Par conséquent, demandez-vous si vous pouvez écrire votre propre serveur proxy pour résoudre le problème de l'accès Internet de groupe d'une part et le problème de facturation d'autre part ?
Après une programmation expérimentale, le problème a finalement été résolu de manière satisfaisante. Écrivez-le maintenant et partagez-le avec vos pairs.
1. Idées
Il existe un paramètre dans les options système des navigateurs actuellement populaires, à savoir « Se connecter via un serveur proxy ». Après les tests de programmation,
Essayez ceci. Lorsqu'un poste de travail du réseau local spécifie cet attribut et émet ensuite une requête Internet, les données de la requête seront envoyées au serveur proxy spécifié. Voici un exemple de paquet de requête :
OBTENIR http://home.microsoft.com/intl/cn/ HTTP/1.0
Accepter: */*
Langue d'acceptation : zh-cn
Accepter l'encodage : gzip, dégonfler
Agent utilisateur : Mozilla/4.0 (compatible ; MSIE 5.0 ; Windows NT)
Hébergeur : home.microsoft.com
Connexion PRoxy : Keep-Alive
La première ligne correspond à l'URL cible et aux méthodes et protocoles associés, et la ligne « Hôte » spécifie l'adresse de l'hôte cible.
De là, nous connaissons le processus du service proxy : réception de la demande du proxy, connexion à l'hôte réel, réception des données renvoyées par l'hôte et envoi des données reçues au proxy.
À cette fin, un programme simple peut être écrit pour résoudre le problème de redirection de communication réseau ci-dessus.
Lors de la conception avec Delphi, choisissez ServerSocket comme contrôle de socket pour communiquer avec le poste de travail proxy, et choisissez le tableau dynamique ClientSocket comme contrôle de socket pour communiquer avec l'hôte distant.
Un problème important qui doit être résolu lors de la programmation est le problème du traitement des connexions multiples. Afin d'accélérer le service proxy et la vitesse de réponse de l'extrémité proxy, les propriétés du contrôle de socket doivent être définies pour ne pas bloquer chaque communication ; session est liée dynamiquement au socket, utilisez la valeur de l'attribut SocketHandle du socket pour déterminer à quelle session il appartient.
Le processus de connexion de communication est illustré dans la figure ci-dessous :
serveur proxy
Prise serveur
(1) recevoir
Envoyé par l'agent à l'hôte distant
(6) (2) (5)
Navigateur ClientSocket (4) Serveur Web
reprendre
Envoyer(3)
(1) Le navigateur proxy envoie une requête Web et le Serversocket du serveur proxy reçoit la requête.
(2) Le programme du serveur proxy crée automatiquement un ClientSocket, définit l'adresse de l'hôte, le port et d'autres attributs, puis se connecte à l'hôte distant.
(3) Après la connexion à distance, l'événement d'envoi est déclenché et le paquet de requête Web reçu par Serversocket est envoyé à l'hôte distant.
(4) Lorsque l'hôte distant renvoie les données de la page, l'événement de lecture de ClientSocket est déclenché pour lire les données de la page.
(5) Le programme du serveur proxy détermine quel Socket dans le contrôle ServerSocket doit envoyer les informations de page reçues de l'hôte à l'extrémité mandatée en fonction des informations de liaison.
(6) Le Socket correspondant dans ServerSocket envoie les données de page à l'agent.
2. Programmation
Il est très simple de concevoir le processus de communication ci-dessus à l'aide de Delphi, principalement lié à ServerSocket et ClientSocket.
Programmation du pilote logiciel. Ce qui suit est une liste de l'interface du serveur proxy expérimental et du programme source écrits par l'auteur, y compris une brève description des fonctions :
unité principale ;
interface
utilise
Windows, messages, SysUtils, classes, graphiques, contrôles, formulaires, boîtes de dialogue,
ExtCtrls, ScktComp, TrayIcon, Menus, StdCtrls ;
taper
session_record=enregistrement
Utilisé : booléen ; {si l'enregistrement de la session est disponible}
SS_Handle : entier ; {descripteur de socket du serveur proxy}
CSocket : TClientSocket ; {socket utilisé pour se connecter à la télécommande}
Recherche : booléen ; {que ce soit pour rechercher le serveur}
LookupTime : entier ; {heure du serveur de recherche}
Requête : booléen ; {s'il y a une requête}
request_str : chaîne ; {requête de bloc de données}
client_connected : booléen ; {indicateur client en ligne}
remote_connected : booléen ; {indicateur de connexion au serveur distant}
fin;
taper
TForm1 = classe(TForm)
ServerSocket1 : TServerSocket ;
ClientSocket1 : TClientSocket ;
Minuterie2 : TIminuterie ;
TrayIcon1 : TTrayIcon ;
Menu contextuel1 : TPopupMenu ;
N11 : TMenuItem ;
N21 : TMenuItem ;
N1 : TMenuItem ;
N01 : TMenuItem ;
Mémo1 : TMémo ;
Edit1 : TEdit ;
Étiquette1 : TLabel ;
Minuterie1 : TIminuterie ;
procédure Timer2Timer(Expéditeur : TObject);
procédure N11Click(Expéditeur : TObject) ;
procédure FormCreate(Expéditeur : TObject);
procédure FormClose (Expéditeur : TObject ; var Action : TCloseAction);
procédure N21Click(Expéditeur : TObject) ;
procédure N01Click(Expéditeur : TObject) ;
procédure ServerSocket1ClientConnect(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ServerSocket1ClientDisconnect(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ServerSocket1ClientError(Expéditeur : TObject ;
Socket : TCustomWinSocket ; ErrorEvent : TErrorEvent ;
varErrorCode : entier );
procédure ServerSocket1ClientRead(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ClientSocket1Connect(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ClientSocket1Disconnect(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ClientSocket1Error(Expéditeur : TObject ; Socket : TCustomWinSocket ;
ErrorEvent : TErrorEvent ; var ErrorCode : Integer );
procédure ClientSocket1Write(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure ClientSocket1Read (Expéditeur : TObject ; Socket : TCustomWinSocket) ;
procédure ServerSocket1Listen(Expéditeur : TObject ;
Socket : TCustomWinSocket );
procédure AppException (Expéditeur : TObject ; E : Exception) ;
procédure Timer1Timer(Expéditeur : TObject);
privé
{Déclarations privées}
publique
Service_Enabled : booléen ; {si le service proxy est activé}
session : tableau de session_record ; {session array}
sessions : entier ; {nombre de sessions}
LookUpTimeOut : entier ; {valeur du délai d'expiration de la connexion}
InvalidRequests : entier ; {nombre de requêtes invalides}
fin;
var
Formulaire1 : TForm1 ;
mise en œuvre
{$R *.DFM}
file://Minuterie de démarrage du système, une fois la fenêtre de démarrage affichée, réduisez-la dans la barre d'état système...
procédure TForm1.Timer2Timer(Expéditeur : TObject);
commencer
timer2.Enabled :=false ; {éteindre la minuterie}
sessions :=0 ; {nombre de sessions=0}
application.OnException := AppException; {Pour protéger les exceptions qui se produisent sur le serveur proxy}
invalidRequests :=0 ; {0 erreur}
LookUpTimeOut :=60 000 ; {valeur du délai d'attente=1 minute}
timer1.Enabled :=true ; {allumer la minuterie}
n11.Enabled :=false ; {Activer l'élément de menu de service invalide}
n21.Enabled :=true ; {L'élément de menu de service fermé est valide}
serveursocket1.Port :=988 ; {port du serveur proxy=988}
serveursocket1.Active :=true ; {Démarrer le service}
form1.hide ; {masquer l'interface, réduire à la barre d'état système}
fin;
fichier://Ouvrir l'élément de menu de service…
procédure TForm1.N11Click(Expéditeur : TObject);
commencer
serveursocket1.Active :=true ; {Démarrer le service}
fin;
fichier://Arrêter l'élément de menu du service…
procédure TForm1.N21Click(Expéditeur : TObject);
commencer
serveursocket1.Active :=false ; {arrêter le service}
N11.Enabled :=Vrai ;
N21.Enabled :=Faux ;
Service_Enabled :=false ; {drapeau effacé}
fin;
file://Création de la fenêtre principale…
procédure TForm1.FormCreate(Expéditeur : TObject);
commencer
Service_Enabled :=false ;
timer2.Enabled:=true; {Lorsque la fenêtre est créée, ouvrez le minuteur}
fin;
fichier://Quand la fenêtre est fermée...
procédure TForm1.FormClose(Expéditeur : TObject ; var Action : TCloseAction) ;
commencer
timer1.Enabled :=false ; {éteindre la minuterie}
si Service_Enabled alors
serversocket1.Active:=false; {Fermer le service en quittant le programme}
fin;
fichier://bouton de sortie du programme…
procédure TForm1.N01Click(Expéditeur : TObject);
commencer
form1.Close ; {Quitter le programme}
fin;
file://Après avoir activé le service proxy...
procédure TForm1.ServerSocket1Listen(Sender: TObject;
Socket : TCustomWinSocket );
commencer
Service_Enabled :=true ; {définir l'indicateur de service}
N11.Enabled :=false ;
N21.Enabled :=true ;
fin;
Une fois que file:// est connecté au serveur proxy par le proxy, une session est établie et liée au socket...
procédure TForm1.ServerSocket1ClientConnect(Expéditeur : TObject ;
Socket : TCustomWinSocket );
var
je,j : entier ;
commencer
j:=-1;
pour i:=1 aux sessions, faites {trouver s'il y a des éléments vides}
sinon session[i-1].Used et non session[i-1].CSocket.active alors
commencer
j:=i-1 ; {Oui, attribuez-le}
session[j].Used:=true ; {défini comme en cours d'utilisation}
casser;
fin
autre
sinon session[i-1].Used et session[i-1].CSocket.active alors
session[i-1].CSocket.active:=false;
si j=-1 alors
commencer {aucun, ajouter un}
j:=séances ;
inc(séances);
setlength(session,sessions);
session[j].Used:=true ; {défini comme en cours d'utilisation}
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;
fin;
session[j].SS_Handle:=socket.socketHandle; {Enregistrez le handle et implémentez la liaison}
session[j].Request:=false ; {Aucune demande}
session[j].client_connected:=true; {le client est connecté}
session[j].remote_connected:=false; {télécommande non connectée}
edit1.text:=inttostr(sessions);
fin;
Lorsque file:// est déconnecté par l'agent...
procédure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
Socket : TCustomWinSocket );
var
i,j,k : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].SS_Handle=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
session[i-1].client_connected:=false; {le client n'est pas connecté}
si session[i-1].remote_connected alors
session[i-1].CSocket.active:=false {Si la connexion à distance est toujours connectée, déconnectez-la}
autre
session[i-1].Used:=false ; {Si les deux sont déconnectés, définissez l'indicateur de ressource de version}
casser;
fin;
j:=séances ;
k:=0;
for i:=1 to j do {Il y a plusieurs éléments inutilisés à la fin du tableau de session de statistiques}
commencer
si session[ji].Utilisé alors
casser;
inc(k);
fin;
si k>0 alors {Corrigez le tableau de session et libérez les éléments inutilisés à la fin}
commencer
sessions :=sessions-k ;
setlength(session,sessions);
fin;
edit1.text:=inttostr(sessions);
fin;
Lorsqu'une erreur de communication file:// se produit...
procédure TForm1.ServerSocket1ClientError(Expéditeur : TObject;
Socket : TCustomWinSocket ; ErrorEvent : TErrorEvent ;
varErrorCode : entier );
var
i,j,k : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].SS_Handle=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
session[i-1].client_connected:=false; {le client n'est pas connecté}
si session[i-1].remote_connected alors
session[i-1].CSocket.active:=false {Si la connexion à distance est toujours connectée, déconnectez-la}
autre
session[i-1].Used:=false ; {Si les deux sont déconnectés, définissez l'indicateur de ressource de version}
casser;
fin;
j:=séances ;
k:=0;
pour i:=1 à j faire
commencer
si session[ji].Utilisé alors
casser;
inc(k);
fin;
si k>0 alors
commencer
sessions :=sessions-k ;
setlength(session,sessions);
fin;
edit1.text:=inttostr(sessions);
code d'erreur :=0 ;
fin;
Lorsque file:// est envoyé par le proxy pour demander la page...
procédure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket : TCustomWinSocket );
var
tmp, ligne, hôte : chaîne ;
je,j,port : entier ;
commencer
pour i:=1 aux sessions, faites {déterminez de quelle session il s'agit}
si session[i-1].Used et (session[i-1].SS_Handle=socket.sockethandle) alors
commencer
session[i-1].request_str:=socket.ReceiveText ; {enregistrer les données de la demande}
tmp:=session[i-1].request_str; {stocké dans une variable temporaire}
memo1.lines.add(tmp);
j:=pos(char(13)+char(10),tmp); {une marque de ligne}
while j>0 do {scanne le texte de la requête ligne par ligne, à la recherche de l'adresse de l'hôte}
commencer
line:=copy(tmp,1,j-1); {prendre une ligne}
delete(tmp,1,j+1); {supprimer une ligne}
j:=pos('Host',line); {indicateur d'adresse hôte}
si j>0 alors
commencer
delete(line,1,j+5); {supprimer le caractère invalide précédent}
j:=pos(':',ligne);
si j>0 alors
commencer
hôte:=copie(ligne,1,j-1);
supprimer(ligne,1,j);
essayer
port:=strtoint(ligne);
sauf
port :=80 ;
fin;
fin
autre
commencer
host:=trim(line); {obtenir l'adresse de l'hôte}
port :=80 ;
fin;
if not session[i-1].remote_connected then {Si l'expédition n'est pas encore connectée}
commencer
session[i-1].Request:=true ; {définir l'indicateur de données de demande prêtes}
session[i-1].CSocket.host:=host; {Définir l'adresse de l'hôte distant}
session[i-1].CSocket.port:=port; {définir le port}
session[i-1].CSocket.active:=true ; {Connecter à l'hôte distant}
session[i-1].Lookingup :=true ; {set flag}
session[i-1].LookupTime:=0 ; {commence à compter à partir de 0}
fin
autre
{Si la télécommande est connectée, envoyez la demande directement}
session[i-1].CSocket.socket.sendtext(session[i-1].request_str);
break ; {arrêter d'analyser le texte de la demande}
fin;
j:=pos(char(13)+char(10),tmp); {pointe vers la ligne suivante}
fin;
pause ; {arrêter la boucle}
fin;
fin;
file://Lorsque la connexion à l'hôte distant réussit...
procédure TForm1.ClientSocket1Connect(Expéditeur : TObject;
Socket : TCustomWinSocket );
var
je : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].CSocket.socket.sockethandle=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
session[i-1].CSocket.tag:=socket.SocketHandle;
session[i-1].remote_connected:=true; {Définir l'indicateur de connexion de l'hôte distant}
session[i-1].Lookingup:=false; {effacer le drapeau}
casser;
fin;
fin;
file://Lorsque l'hôte distant se déconnecte...
procédure TForm1.ClientSocket1Disconnect(Expéditeur : TObject;
Socket : TCustomWinSocket );
var
i,j,k : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].CSocket.tag=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
session[i-1].remote_connected:=false; {défini sur non connecté}
sinon session[i-1].client_connected alors
session[i-1].Used:=false {Si le client est déconnecté, définissez l'indicateur de ressource de version}
autre
pour k:=1 àserversocket1.Socket.ActiveConnections faire
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) et session[i-1].used then
commencer
serveursocket1.Socket.Connections[k-1].Close ;
casser;
fin;
casser;
fin;
j:=séances ;
k:=0;
pour i:=1 à j faire
commencer
si session[ji].Utilisé alors
casser;
inc(k);
fin;
si k>0 alors {fixer le tableau de session}
commencer
sessions :=sessions-k ;
setlength(session,sessions);
fin;
edit1.text:=inttostr(sessions);
fin;
file://Lorsqu'une erreur se produit lors de la communication avec l'hôte distant...
procédure TForm1.ClientSocket1Error(Expéditeur : TObject ;
Socket : TCustomWinSocket ; ErrorEvent : TErrorEvent ;
varErrorCode : entier );
var
i,j,k : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].CSocket.tag=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
socket.close;
session[i-1].remote_connected:=false; {défini sur non connecté}
sinon session[i-1].client_connected alors
session[i-1].Used:=false {Si le client est déconnecté, définissez l'indicateur de ressource de version}
autre
pour k:=1 àserversocket1.Socket.ActiveConnections faire
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) et session[i-1].used then
commencer
serveursocket1.Socket.Connections[k-1].Close ;
casser;
fin;
casser;
fin;
j:=séances ;
k:=0;
pour i:=1 à j faire
commencer
si session[ji].Utilisé alors
casser;
inc(k);
fin;
code d'erreur :=0 ;
si k>0 alors {fixer le tableau de session}
commencer
sessions :=sessions-k ;
setlength(session,sessions);
fin;
edit1.text:=inttostr(sessions);
fin;
file://Envoie une demande de page à l'hôte distant…
procédure TForm1.ClientSocket1Write(Expéditeur : TObject;
Socket : TCustomWinSocket );
var
je : entier ;
commencer
pour i:=1 aux sessions faire
if (session[i-1].CSocket.tag=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
si session[i-1].Demande alors
commencer
socket.SendText(session[i-1].request_str); {S'il y a une demande, envoyez}
session[i-1].Request:=false; {effacer l'indicateur}
fin;
casser;
fin;
fin;
file://Lorsque l'hôte distant envoie des données de page...
procédure TForm1.ClientSocket1Read(Expéditeur : TObject;
Socket : TCustomWinSocket );
var
je,j : entier ;
rec_bytes : entier ; {longueur du bloc de données renvoyé}
rec_Buffer : tableau[0..2047] de caractères ; {tampon de bloc de données renvoyé}
commencer
pour i:=1 aux sessions faire
if (session[i-1].CSocket.tag=socket.SocketHandle) et session[i-1].Utilisé alors
commencer
rec_bytes:=socket.ReceiveBuf(rec_buffer,2048); {Recevoir des données}
pour j:=1 àserversocket1.Socket.ActiveConnections faire
si serveursocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle alors
commencer
serveursocket1.Socket.Connections[j-1].SendBuf(rec_buffer,rec_bytes); {Envoyer des données}
casser;
fin;
casser;
fin;
fin;
file:// "Page non trouvée" et d'autres messages d'erreur apparaissent...
procédure TForm1.AppException(Expéditeur : TObject ; E : Exception) ;
commencer
inc (demandes invalides);
fin;
fichier://Trouver le timing de l'hôte distant...
procédure TForm1.Timer1Timer(Expéditeur : TObject);
var
je,j : entier ;
commencer
pour i:=1 aux sessions faire
si session[i-1].Utilisé et session[i-1].Recherche alors {si connexion}
commencer
inc(session[i-1].LookupTime);
si session[i-1].LookupTime>lookuptimeout alors {if timeout}
commencer
session[i-1].Lookingup:=false;
session[i-1].CSocket.active:=false; {arrêter la recherche}
pour j:=1 àserversocket1.Socket.ActiveConnections faire
si serveursocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle alors
commencer
serveursocket1.Socket.Connections[j-1].Close ; {déconnecter le client}
casser;
fin;
fin;
fin;
fin;
fin.
3. Post-scriptum
Puisque cette idée de conception ajoute uniquement une fonction de redirection entre l'agent et l'hôte distant, l'original
Certaines fonctionnalités telles que la technologie de mise en cache sont conservées, ce qui rend l'efficacité plus élevée. Après les tests, lors de l'utilisation d'un modem 33,6K pour accéder à Internet, trois à dix postes de travail proxy peuvent accéder à Internet en même temps, et la vitesse de réponse est toujours bonne. Étant donné que la connexion entre le poste de travail proxy et le poste de travail du serveur proxy passe généralement par une liaison à haut débit, le goulot d'étranglement se produit principalement dans la méthode d'accès Internet du serveur proxy.
Grâce à la méthode ci-dessus, l'auteur a développé avec succès un ensemble complet de logiciels de serveur proxy et l'a entièrement intégré au système de facturation de la salle informatique.
Avec succès, il est possible d'utiliser un seul poste de travail pour exécuter des fonctions telles que le proxy Internet, la facturation Internet et la facturation de l'utilisation de la machine. Les amis ayant une expérience en programmation peuvent ajouter des fonctions de serveur proxy supplémentaires, telles que la définition de sites à accès interdit, le comptage du trafic client, les listes d'accès Web, etc.