Diseña tu propio servidor proxy usando Delphi
Cuando el autor estaba escribiendo un software de facturación de Internet, se planteó la cuestión de cómo facturar el acceso a Internet para cada estación de trabajo en la red de área local. En términos generales, estas estaciones de trabajo acceden a Internet a través de un servidor proxy. Cuando se utiliza software de servidor proxy ya preparado, dado que el software del servidor proxy es un sistema cerrado, es difícil escribir un programa para obtener información sobre el tiempo de acceso a Internet en tiempo real. Por lo tanto, considere si puede escribir su propio servidor proxy para resolver el problema del acceso grupal a Internet por un lado y el problema de facturación por el otro.
Después de la programación experimental, el problema finalmente se resolvió satisfactoriamente. Escríbelo ahora y compártelo con tus compañeros.
1.Ideas
Hay un parámetro en las opciones del sistema de los navegadores más populares actualmente, a saber, "Conectarse a través de un servidor proxy".
Inténtelo, cuando una estación de trabajo en la red local especifica este atributo y luego emite una solicitud de Internet, los datos de la solicitud se enviarán al servidor proxy especificado. El siguiente es un ejemplo de un paquete de solicitud:
OBTENER http://home.microsoft.com/intl/cn/ HTTP/1.0
Aceptar: */*
Aceptar-Idioma: zh-cn
Aceptar codificación: gzip, deflate
Agente de usuario: Mozilla/4.0 (compatible; MSIE 5.0; Windows NT)
Anfitrión: home.microsoft.com
Conexión PRoxy: Mantener vivo
La primera línea es la URL de destino y los métodos y protocolos relacionados, y la línea "Host" especifica la dirección del host de destino.
De esto conocemos el proceso del servicio proxy: recibir la solicitud del proxy, conectarse al host real, recibir los datos devueltos por el host y enviar los datos recibidos al proxy.
Para ello, se puede escribir un programa sencillo para completar el problema de redirección de comunicación de red anterior.
Al diseñar con Delphi, elija ServerSocket como control de socket para comunicarse con la estación de trabajo proxy y elija la matriz dinámica ClientSocket como control de socket para comunicarse con el host remoto.
Un problema importante que debe resolverse durante la programación es el problema del procesamiento de múltiples conexiones. Para acelerar el servicio proxy y la velocidad de respuesta del agente, las propiedades del control del socket deben configurarse para que no bloqueen cada sesión de comunicación; está vinculado dinámicamente al socket, utilice el valor del atributo SocketHandle del socket para determinar a qué sesión pertenece.
El proceso de conexión de comunicación se muestra en la siguiente figura:
servidor proxy
zócalo del servidor
(1) recibir
Enviado por el agente al host remoto
(6) (2) (5)
Navegador ClientSocket (4) Servidor web
tomar el control
Enviar(3)
(1) El navegador proxy envía una solicitud web y el Serversocket del servidor proxy recibe la solicitud.
(2) El programa del servidor proxy crea automáticamente un ClientSocket, establece la dirección del host, el puerto y otros atributos y luego se conecta al host remoto.
(3) Después de la conexión remota, se activa el evento de envío y el paquete de solicitud web recibido por Serversocket se envía al host remoto.
(4) Cuando el host remoto devuelve datos de la página, se activa el evento de lectura de ClientSocket para leer los datos de la página.
(5) El programa del servidor proxy determina qué Socket en el control ServerSocket debe enviar la información de la página recibida del host al extremo proxy en función de la información vinculante.
(6) El socket correspondiente en ServerSocket envía los datos de la página al agente.
2. Programación
Es muy sencillo diseñar el proceso de comunicación anterior utilizando Delphi, principalmente relacionado con ServerSocket y ClientSocket.
Programación de controladores de software. La siguiente es una lista de la interfaz del servidor proxy experimental y el programa fuente escrito por el autor, incluida una breve descripción de la función:
unidad principal;
interfaz
usos
Windows, Mensajes, SysUtils, Clases, Gráficos, Controles, Formularios, Cuadros de diálogo,
ExtCtrls, ScktComp, TrayIcon, Menús, StdCtrls;
tipo
session_record=registro
Usado: booleano; {si la grabación de sesión está disponible}
SS_Handle: entero; {identificador de socket del servidor proxy}
CSocket: TClientSocket; {zócalo utilizado para conectarse al control remoto}
Buscando: booleano; {si busca el servidor}
LookupTime: entero; {hora del servidor de búsqueda}
Solicitud: booleana; {si hay una solicitud}
request_str: cadena; {solicitar bloque de datos}
client_connected: booleano; {indicador en línea del cliente}
remoto_conectado: booleano; {indicador de conexión del servidor remoto}
fin;
tipo
TForm1 = clase(TForm)
ServerSocket1: TServerSocket;
ClientSocket1: TClientSocket;
Temporizador2: Temporizador T;
BandejaIcon1: TTrayIcon;
Menú emergente1: TPopupMenu;
N11: TMenúArtículo;
N21: TMenúArtículo;
N1: TMenuItem;
N01: TMenuItem;
Memo1: TMemo;
Editar1: TEditar;
Etiqueta1: TLabel;
Temporizador1: Temporizador T;
procedimiento Timer2Timer(Remitente: TObject);
procedimiento N11Click(Remitente: TObject);
procedimiento FormCreate(Remitente: TObject);
procedimiento FormClose(Remitente: TObject; var Acción: TCloseAction);
procedimiento N21Click(Remitente: TObject);
procedimiento N01Click(Remitente: TObject);
procedimiento ServerSocket1ClientConnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ServerSocket1ClientDisconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ServerSocket1ClientError(Remitente: TObject;
Conector: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Entero);
procedimiento ServerSocket1ClientRead(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ClientSocket1Connect(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ClientSocket1Disconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ClientSocket1Error(Remitente: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: entero);
procedimiento ClientSocket1Write(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento ClientSocket1Read(Remitente: TObject; Socket: TCustomWinSocket);
procedimiento ServerSocket1Listen(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento AppException(Remitente: TObject; E: Excepción);
procedimiento Timer1Timer(Remitente: TObject);
privado
{Declaraciones privadas}
público
Service_Enabled: booleano; {si el servicio proxy está habilitado}
sesión: matriz de session_record; {matriz de sesiones}
sesiones: entero; {número de sesiones}
LookUpTimeOut: entero; {valor de tiempo de espera de conexión}
InvalidRequests: entero; {número de solicitudes no válidas}
fin;
var
Formulario1: TForm1;
implementación
{$R *.DFM}
file://Temporizador de inicio del sistema, después de que se muestre la ventana de inicio, reduzca a la Bandeja del sistema...
procedimiento TForm1.Timer2Timer(Remitente: TObject);
comenzar
timer2.Enabled:=false; {apagar el temporizador}
sesiones:=0; {número de sesiones=0}
application.OnException := AppException; {Para proteger las excepciones que ocurren en el servidor proxy}
Solicitudes inválidas:=0; {0 error}
LookUpTimeOut:=60000; {valor de tiempo de espera=1 minuto}
timer1.Enabled:=true; {activar el temporizador}
n11.Enabled:=false; {Habilitar elemento del menú de servicio no válido}
n21.Enabled:=true; {Cerrar el elemento del menú de servicio es válido}
serversocket1.Port:=988; {puerto del servidor proxy=988}
serversocket1.Active:=true; {Iniciar servicio}
form1.hide; {ocultar interfaz, reducir a la bandeja del sistema}
fin;
file://Abrir elemento del menú de servicio…
procedimiento TForm1.N11Click(Remitente: TObject);
comenzar
serversocket1.Active:=true; {Iniciar servicio}
fin;
file://Detener elemento del menú de servicio…
procedimiento TForm1.N21Click(Remitente: TObject);
comenzar
serversocket1.Active:=false; {detener servicio}
N11.Habilitado:=Verdadero;
N21.Habilitado:=Falso;
Service_Enabled:=false; {bandera borrada}
fin;
archivo://Creación de la ventana principal…
procedimiento TForm1.FormCreate(Remitente: TObject);
comenzar
Servicio_Enabled:=falso;
timer2.Enabled:=true; {Cuando se crea la ventana, abre el temporizador}
fin;
file://Cuando la ventana está cerrada...
procedimiento TForm1.FormClose(Remitente: TObject; var Acción: TCloseAction);
comenzar
timer1.Enabled:=false; {apagar el temporizador}
si Service_Enabled entonces
serversocket1.Active:=false; {Cerrar el servicio al salir del programa}
fin;
archivo://Botón Salir del programa…
procedimiento TForm1.N01Click(Remitente: TObject);
comenzar
formulario1.Cerrar; {Salir del programa}
fin;
file://Después de activar el servicio proxy...
procedimiento TForm1.ServerSocket1Listen(Remitente: TObject;
Zócalo: TCustomWinSocket);
comenzar
Service_Enabled:=true; {establecer indicador de servicio}
N11.Habilitado:=falso;
N21.Habilitado:=verdadero;
fin;
Después de que el proxy conecta file:// al servidor proxy, se establece una sesión y se vincula al socket...
procedimiento TForm1.ServerSocket1ClientConnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
i,j: número entero;
comenzar
j:=-1;
para i:=1 para sesiones {buscar si hay elementos en blanco}
si no es sesión [i-1]. Usado y no sesión [i-1]. CSocket.active entonces
comenzar
j:=i-1; {Sí, asígnalo}
sesión[j].Usado:=true; {establecido como en uso}
romper;
fin
demás
si no es sesión [i-1]. Usado y sesión [i-1]. CSocket.active entonces
sesión[i-1].CSocket.active:=false;
si j=-1 entonces
comenzar {ninguno, agregar uno}
j:=sesiones;
inc(sesiones);
setlength(sesión,sesiones);
sesión[j].Usado:=true; {establecido como en uso}
sesión[j].CSocket:=TClientSocket.Create(nil);
sesión[j].CSocket.OnConnect:=ClientSocket1Connect;
sesión[j].CSocket.OnDisconnect:=ClientSocket1Disconnect;
sesión[j].CSocket.OnError:=ClientSocket1Error;
sesión[j].CSocket.OnRead:=ClientSocket1Read;
sesión[j].CSocket.OnWrite:=ClientSocket1Write;
sesión[j].Buscando:=false;
fin;
session[j].SS_Handle:=socket.socketHandle; {Guardar el identificador e implementar el enlace}
sesión[j].Solicitud:=false; {Sin solicitud}
sesión[j].client_connected:=true; {el cliente está conectado}
sesión[j].remote_connected:=false; {remoto no conectado}
edit1.text:=inttostr(sesiones);
fin;
Cuando el agente desconecta file://...
procedimiento TForm1.ServerSocket1ClientDisconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
i,j,k: número entero;
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].SS_Handle=socket.SocketHandle) y sesión[i-1].Se usa entonces
comenzar
sesión[i-1].client_connected:=false; {el cliente no está conectado}
si sesión[i-1].remote_connected entonces
session[i-1].CSocket.active:=false {Si la conexión remota aún está conectada, desconéctela}
demás
session[i-1].Used:=false; {Si ambos están desconectados, establezca el indicador de liberación de recursos}
romper;
fin;
j:=sesiones;
k:=0;
for i:=1 to j do {Hay varios elementos no utilizados al final de la matriz de la sesión de estadísticas}
comenzar
si sesión [ji]. Usado entonces
romper;
inc(k);
fin;
si k>0 entonces {Modificar la matriz de sesión y liberar los elementos no utilizados al final}
comenzar
sesiones:=sesiones-k;
setlength(sesión,sesiones);
fin;
edit1.text:=inttostr(sesiones);
fin;
Cuando se produce un error de comunicación file://...
procedimiento TForm1.ServerSocket1ClientError(Remitente: TObject;
Conector: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: entero);
var
i,j,k: número entero;
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].SS_Handle=socket.SocketHandle) y sesión[i-1].Se usa entonces
comenzar
sesión[i-1].client_connected:=false; {el cliente no está conectado}
si sesión[i-1].remote_connected entonces
session[i-1].CSocket.active:=false {Si la conexión remota aún está conectada, desconéctela}
demás
session[i-1].Used:=false; {Si ambos están desconectados, establezca el indicador de liberación de recursos}
romper;
fin;
j:=sesiones;
k:=0;
para i:=1 a j hacer
comenzar
si sesión [ji]. Usado entonces
romper;
inc(k);
fin;
si k>0 entonces
comenzar
sesiones:=sesiones-k;
setlength(sesión,sesiones);
fin;
edit1.text:=inttostr(sesiones);
código de error:=0;
fin;
Cuando el proxy envía file:// para solicitar la página...
procedimiento TForm1.ServerSocket1ClientRead(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
tmp,línea,host: cadena;
i,j,puerto: entero;
comenzar
para i:=1 a sesiones hacer {determinar qué sesión es}
si sesión[i-1].Usado y (sesión[i-1].SS_Handle=socket.sockethandle) entonces
comenzar
sesión[i-1].request_str:=socket.ReceiveText; {guardar datos de solicitud}
tmp:=sesión[i-1].request_str; {almacenado en variable temporal}
memo1.lines.add(tmp);
j:=pos(char(13)+char(10),tmp); {marca de una línea}
mientras j>0 haz {escanear el texto de la solicitud línea por línea, buscando la dirección del host}
comenzar
línea:=copiar(tmp,1,j-1); {tomar una línea}
eliminar (tmp,1,j+1); {eliminar una fila}
j:=pos('Host',line); {indicador de dirección de host}
si j>0 entonces
comenzar
eliminar(línea,1,j+5); {eliminar el carácter no válido anterior}
j:=pos(':',línea);
si j>0 entonces
comenzar
anfitrión:=copiar(línea,1,j-1);
eliminar(línea,1,j);
intentar
puerto:=strtoint(línea);
excepto
puerto:=80;
fin;
fin
demás
comenzar
host:=trim(line); {obtener dirección de host}
puerto:=80;
fin;
si no es sesión[i-1].remote_connected entonces {Si la expedición aún no está conectada}
comenzar
sesión[i-1].Request:=true; {establecer indicador de datos de solicitud listos}
session[i-1].CSocket.host:=host; {Establecer la dirección del host remoto}
sesión[i-1].CSocket.port:=puerto; {establecer puerto}
sesión[i-1].CSocket.active:=true; {Conectar al host remoto}
sesión[i-1].Buscando:=true; {establecer bandera}
sesión[i-1].LookupTime:=0; {comienza a contar desde 0}
fin
demás
{Si el control remoto está conectado, envíe la solicitud directamente}
sesión[i-1].CSocket.socket.sendtext(sesión[i-1].request_str);
break; {detener el escaneo del texto de solicitud}
fin;
j:=pos(char(13)+char(10),tmp); {señala la siguiente línea}
fin;
romper; {detener bucle}
fin;
fin;
file://Cuando la conexión al host remoto es exitosa...
procedimiento TForm1.ClientSocket1Connect(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
yo: número entero;
comenzar
para i:=1 a sesiones hacer
si (sesión[i-1].CSocket.socket.sockethandle=socket.SocketHandle) y sesión[i-1].Se usa entonces
comenzar
sesión[i-1].CSocket.tag:=socket.SocketHandle;
session[i-1].remote_connected:=true; {Establecer el indicador de conexión del host remoto}
sesión[i-1].Buscando:=false; {borrar bandera}
romper;
fin;
fin;
file://Cuando el host remoto se desconecta...
procedimiento TForm1.ClientSocket1Disconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
i,j,k: número entero;
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].CSocket.tag=socket.SocketHandle) y sesión[i-1].Usado entonces
comenzar
sesión[i-1].remote_connected:=false; {establecido como no conectado}
si no es sesión [i-1]. cliente_conectado entonces
sesión[i-1].Used:=false {Si el cliente está desconectado, establezca el indicador de liberación de recurso}
demás
para k:=1 a serversocket1.Socket.ActiveConnections hacer
si (servosocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) y session[i-1].used entonces
comenzar
serversocket1.Socket.Conexiones[k-1].Cerrar;
romper;
fin;
romper;
fin;
j:=sesiones;
k:=0;
para i:=1 a j hacer
comenzar
si sesión [ji]. Usado entonces
romper;
inc(k);
fin;
si k>0 entonces {arreglar matriz de sesión}
comenzar
sesiones:=sesiones-k;
setlength(sesión,sesiones);
fin;
edit1.text:=inttostr(sesiones);
fin;
file://Cuando se produce un error al comunicarse con el host remoto...
procedimiento TForm1.ClientSocket1Error(Remitente: TObject;
Conector: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Entero);
var
i,j,k: número entero;
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].CSocket.tag=socket.SocketHandle) y sesión[i-1].Usado entonces
comenzar
enchufe.cerrar;
sesión[i-1].remote_connected:=false; {establecido como no conectado}
si no es sesión [i-1]. cliente_conectado entonces
sesión[i-1].Used:=false {Si el cliente está desconectado, establezca el indicador de liberación de recurso}
demás
para k:=1 a serversocket1.Socket.ActiveConnections hacer
si (servosocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) y session[i-1].used entonces
comenzar
serversocket1.Socket.Conexiones[k-1].Cerrar;
romper;
fin;
romper;
fin;
j:=sesiones;
k:=0;
para i:=1 a j hacer
comenzar
si sesión [ji]. Usado entonces
romper;
inc(k);
fin;
código de error:=0;
si k>0 entonces {arreglar matriz de sesión}
comenzar
sesiones:=sesiones-k;
setlength(sesión,sesiones);
fin;
edit1.text:=inttostr(sesiones);
fin;
file://Envía una solicitud de página al host remoto...
procedimiento TForm1.ClientSocket1Write(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
yo: número entero;
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].CSocket.tag=socket.SocketHandle) y sesión[i-1].Usado entonces
comenzar
si sesión [i-1]. Solicitar entonces
comenzar
socket.SendText(session[i-1].request_str); {Si hay una solicitud, envíe}
sesión[i-1].Solicitud:=false; {borrar bandera}
fin;
romper;
fin;
fin;
file://Cuando el host remoto envía datos de la página...
procedimiento TForm1.ClientSocket1Read(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
i,j: número entero;
rec_bytes: entero; {longitud del bloque de datos devuelto}
rec_Buffer: matriz [0..2047] de char; {búfer de bloque de datos devuelto}
comenzar
para i:=1 a sesiones hacer
if (sesión[i-1].CSocket.tag=socket.SocketHandle) y sesión[i-1].Usado entonces
comenzar
rec_bytes:=socket.ReceiveBuf(rec_buffer,2048); {recibir datos}
para j:=1 a serversocket1.Socket.ActiveConnections hacer
si serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle entonces
comenzar
serversocket1.Socket.Connections[j-1].SendBuf(rec_buffer,rec_bytes);
romper;
fin;
romper;
fin;
fin;
Archivo:// "Página no encontrada" y aparecen otros mensajes de error...
procedimiento TForm1.AppException(Remitente: TObject; E: Excepción);
comenzar
inc(solicitudes no válidas);
fin;
file://Buscar temporización del host remoto...
procedimiento TForm1.Timer1Timer(Remitente: TObject);
var
i,j: número entero;
comenzar
para i:=1 a sesiones hacer
si sesión[i-1].Usado y sesión[i-1].Buscando entonces {si conectando}
comenzar
inc(sesión[i-1].LookupTime);
si sesión[i-1].LookupTime>lookuptimeout entonces {if timeout}
comenzar
sesión[i-1].Buscando:=false;
sesión[i-1].CSocket.active:=false; {dejar de buscar}
para j:=1 a serversocket1.Socket.ActiveConnections hacer
si serversocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle entonces
comenzar
serversocket1.Socket.Connections[j-1].Cerrar; {desconectar cliente}
romper;
fin;
fin;
fin;
fin;
fin.
3. Posdata
Dado que esta idea de diseño solo agrega una función de redirección entre el extremo del proxy y el host remoto, el extremo del proxy original
Se conservan algunas características, como la tecnología de almacenamiento en caché, por lo que la eficiencia es alta. Después de las pruebas, cuando se utiliza un módem de 33,6 K para acceder a Internet, de tres a diez estaciones de trabajo proxy pueden acceder a Internet al mismo tiempo y todavía hay una buena velocidad de respuesta. Dado que la conexión entre la estación de trabajo proxy y la estación de trabajo del servidor proxy generalmente pasa a través de un enlace de alta velocidad, el cuello de botella se produce principalmente en el método de acceso a Internet del servidor proxy.
Mediante el método anterior, el autor desarrolló con éxito un conjunto completo de software de servidor proxy y lo integró completamente con el sistema de facturación de la sala de computadoras.
Con éxito, es posible utilizar una estación de trabajo para completar funciones como proxy de Internet, facturación de Internet y facturación del uso de la máquina. Los amigos con experiencia en programación pueden agregar funciones adicionales del servidor proxy, como configurar sitios de acceso prohibido, contar el tráfico de clientes, listas de acceso web, etc.
Blog del autor: http://blog.csdn.net/BExpress/