Projete seu próprio servidor proxy usando Delphi
Quando o autor estava escrevendo um software de cobrança pela Internet, ele envolvia a questão de como cobrar o acesso à Internet para cada estação de trabalho na rede local. De modo geral, essas estações de trabalho acessam a Internet por meio de um servidor proxy. Ao usar software de servidor proxy pronto, como o software do servidor proxy é um sistema fechado, é difícil escrever um programa para obter informações de tempo de acesso à Internet em tempo real. Portanto, considere se você pode escrever seu próprio servidor proxy para resolver o problema de acesso à Internet em grupo, por um lado, e o problema de cobrança, por outro.
Após a programação experimental, o problema foi finalmente resolvido de forma satisfatória. Escreva agora e compartilhe com seus colegas.
1. Ideias
Existe um parâmetro nas opções do sistema dos navegadores populares atualmente, nomeadamente "Conectar através de um servidor proxy".
Tente isto. Quando uma estação de trabalho na rede local especifica esse atributo e emite uma solicitação da Internet, os dados da solicitação serão enviados ao servidor proxy especificado.
OBTENHA http://home.microsoft.com/intl/cn/HTTP/1.0
Aceitar: */*
Idioma de aceitação: zh-cn
Aceitar codificação: gzip, deflate
Agente do usuário: Mozilla/4.0 (compatível; MSIE 5.0; Windows NT)
Hospedeiro: home.microsoft.com
Conexão PRoxy: Keep-Alive
A primeira linha é o URL de destino e métodos e protocolos relacionados, e a linha "Host" especifica o endereço do host de destino.
A partir disso conhecemos o processo do serviço proxy: receber a solicitação do proxy, conectar-se ao host real, receber os dados retornados pelo host e enviar os dados recebidos ao proxy.
Para este propósito, um programa simples pode ser escrito para resolver o problema de redirecionamento de comunicação de rede acima.
Ao projetar com Delphi, escolha ServerSocket como o controle de soquete para se comunicar com a estação de trabalho proxy e escolha a matriz dinâmica ClientSocket como o controle de soquete para se comunicar com o host remoto.
Uma questão importante que deve ser resolvida durante a programação é o problema do processamento de múltiplas conexões. Para agilizar o serviço de proxy e a velocidade de resposta da extremidade do proxy, as propriedades do controle de soquete devem ser definidas como não bloqueantes; session é vinculada dinamicamente ao soquete , use o valor do atributo SocketHandle do soquete para determinar a qual sessão ele pertence.
O processo de conexão de comunicação é mostrado na figura abaixo:
servidor proxy
Soquete de servidor
(1) receber
Enviado pelo agente para o host remoto
(6) (2) (5)
Navegador ClientSocket (4) Servidor Web
assumir
Enviar(3)
(1) O navegador proxy envia uma solicitação da Web e o Serversocket do servidor proxy recebe a solicitação.
(2) O programa do servidor proxy cria automaticamente um ClientSocket, define o endereço do host, porta e outros atributos e, em seguida, conecta-se ao host remoto.
(3) Após a conexão remota, o evento de envio é acionado e o pacote de solicitação da Web recebido pelo Serversocket é enviado ao host remoto.
(4) Quando o host remoto retorna os dados da página, o evento de leitura do ClientSocket é acionado para ler os dados da página.
(5) O programa do servidor proxy determina qual Socket no controle ServerSocket deve enviar as informações da página recebidas do host para o final do proxy com base nas informações de ligação.
(6) O Socket correspondente no ServerSocket envia os dados da página ao agente.
2. Programação
É muito simples projetar o processo de comunicação acima usando Delphi, principalmente relacionado a ServerSocket e ClientSocket.
Programação de drivers de software. A seguir está uma lista da interface experimental do servidor proxy e do programa de origem escrito pelo autor, incluindo uma breve descrição da função:
unidade principal;
interface
usa
Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, Formulários, Diálogos,
ExtCtrls, ScktComp, TrayIcon, Menus, StdCtrls;
tipo
session_record=registro
Usado: booleano; {se a gravação da sessão está disponível}
SS_Handle: inteiro; {identificador do soquete do servidor proxy}
CSocket: TClientSocket; {soquete usado para conectar ao controle remoto}
Procurando: booleano; {se procura o servidor}
LookupTime: inteiro; {horário do servidor de pesquisa}
Solicitação: booleano; {se há uma solicitação}
request_str: string; {bloco de dados da solicitação}
client_connected: boolean; {sinalizador on-line do cliente}
remote_connected: boolean; {sinalizador de conexão do servidor remoto}
fim;
tipo
TForm1 = classe(TForm)
ServidorSocket1: TServerSocket;
ClienteSocket1: TClientSocket;
Temporizador2: TTimer;
TrayIcon1: TTrayIcon;
PopupMenu1: TPopupMenu;
N11: TMenuItem;
N21: TMenuItem;
N1: TMenuItem;
N01: TMenuItem;
Memo1: TMemo;
Editar1: TEdit;
Rótulo1: TLabel;
Temporizador1: TTimer;
procedimento Timer2Timer(Remetente: TObject);
procedimento N11Click(Remetente: TObject);
procedimento FormCreate(Remetente: TObject);
procedimento FormClose(Sender: TObject; var Action: TCloseAction);
procedimento N21Click(Remetente: TObject);
procedimento N01Click(Remetente: TObject);
procedimento ServerSocket1ClientConnect(Sender: TObject;
Soquete: TCustomWinSocket);
procedimento ServerSocket1ClientDisconnect(Remetente: TObject;
Soquete: TCustomWinSocket);
procedimento ServerSocket1ClientError(Remetente: TObject;
Soquete: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Inteiro);
procedimento ServerSocket1ClientRead(Sender: TObject;
Soquete: TCustomWinSocket);
procedimento ClientSocket1Connect(Remetente: TObject;
Soquete: TCustomWinSocket);
procedimento ClientSocket1Disconnect(Sender: TObject;
Soquete: TCustomWinSocket);
procedimento ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Inteiro);
procedimento ClientSocket1Write(Remetente: TObject;
Soquete: TCustomWinSocket);
procedimento ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedimento ServerSocket1Listen(Sender: TObject;
Soquete: TCustomWinSocket);
procedimento AppException(Remetente: TObject; E: Exceção);
procedimento Timer1Timer(Remetente: TObject);
privado
{Declarações privadas}
público
Service_Enabled: boolean; {se o serviço proxy está habilitado}
sessão: matriz de session_record; {matriz de sessão}
sessões: inteiro; {número de sessões}
LookUpTimeOut: inteiro; {valor de tempo limite de conexão}
InvalidRequests: inteiro; {número de solicitações inválidas}
fim;
var
Formulário1: TForm1;
implementação
{$R *.DFM}
file://Temporizador de inicialização do sistema, depois que a janela de inicialização for exibida, reduza para a bandeja do sistema...
procedimento TForm1.Timer2Timer(Remetente: TObject);
começar
timer2.Enabled:=false; {desligar temporizador}
sessões:=0; {número de sessões=0}
application.OnException := AppException {Para proteger exceções que ocorrem no servidor proxy}
invalidRequests:=0; {0 erro}
LookUpTimeOut:=60000; {valor de tempo limite=1 minuto}
timer1.Enabled:=true; {ativar temporizador}
n11.Enabled:=false; {Ativar item de menu de serviço inválido}
n21.Enabled:=true; {Fechar item de menu de serviço é válido}
serverocket1.Port:=988; {porta do servidor proxy=988}
serverocket1.Active:=true; {Iniciar serviço}
form1.hide; {ocultar interface, encolher para a bandeja do sistema}
fim;
arquivo://Abrir item de menu de serviço…
procedimento TForm1.N11Click(Remetente: TObject);
começar
serverocket1.Active:=true; {Iniciar serviço}
fim;
arquivo://Parar item de menu de serviço…
procedimento TForm1.N21Click(Remetente: TObject);
começar
serverocket1.Active:=false; {parar serviço}
N11.Ativado:=Verdadeiro;
N21.Ativado:=Falso;
Service_Enabled:=false; {sinalizador desmarcado}
fim;
arquivo://Criação da janela principal…
procedimento TForm1.FormCreate(Remetente: TObject);
começar
Serviço_Enabled:=falso;
timer2.Enabled:=true; {Quando a janela for criada, abra o cronômetro}
fim;
file://Quando a janela é fechada...
procedimento TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
começar
timer1.Enabled:=false; {desligar temporizador}
se Service_Enabled então
serverocket1.Active:=false; {Fechar o serviço ao sair do programa}
fim;
arquivo: // botão de saída do programa…
procedimento TForm1.N01Click(Remetente: TObject);
começar
form1.Fechar; {Sair do programa}
fim;
file://Depois de ativar o serviço de proxy...
procedimento TForm1.ServerSocket1Listen(Sender: TObject;
Soquete: TCustomWinSocket);
começar
Service_Enabled:=true; {definir sinalizador de serviço}
N11.Ativado:=falso;
N21.Ativado:=verdadeiro;
fim;
Depois que file:// é conectado ao servidor proxy pelo proxy, uma sessão é estabelecida e vinculada ao soquete...
procedimento TForm1.ServerSocket1ClientConnect(Sender: TObject;
Soquete: TCustomWinSocket);
var
i,j: inteiro;
começar
j:=-1;
para i:=1 para sessões faça {encontre se há itens em branco}
se não for session[i-1].Used e não session[i-1].CSocket.active então
começar
j:=i-1; {Sim, atribua}
sessão[j].Usado:=true; {definido como em uso}
quebrar;
fim
outro
se não for session[i-1].Used e session[i-1].CSocket.active então
sessão[i-1].CSocket.active:=false;
se j=-1 então
começar {nenhum, adicione um}
j:=sessões;
inc(sessões);
setlength(sessão,sessões);
sessão[j].Usado:=true; {definido como em uso}
sessão[j].CSocket:=TClientSocket.Create(nil);
sessão[j].CSocket.OnConnect:=ClientSocket1Connect;
sessão[j].CSocket.OnDisconnect:=ClientSocket1Disconnect;
sessão[j].CSocket.OnError:=ClientSocket1Error;
sessão[j].CSocket.OnRead:=ClientSocket1Read;
sessão[j].CSocket.OnWrite:=ClientSocket1Write;
sessão[j].Procurando:=falso;
fim;
session[j].SS_Handle:=socket.socketHandle; {Salve o identificador e implemente a ligação}
session[j].Request:=false; {Sem solicitação}
session[j].client_connected:=true; {cliente está conectado}
session[j].remote_connected:=false; {remoto não conectado}
edit1.text:=inttostr(sessões);
fim;
Quando file:// é desconectado pelo agente...
procedimento TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
Soquete: TCustomWinSocket);
var
i,j,k: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].SS_Handle=socket.SocketHandle) e sessão[i-1].Usado então
começar
session[i-1].client_connected:=false; {cliente não está conectado}
se sessão[i-1].remote_connected então
session[i-1].CSocket.active:=false {Se a conexão remota ainda estiver conectada, desconecte-a}
outro
session[i-1].Used:=false; {Se ambos estiverem desconectados, defina o sinalizador de recurso de liberação}
quebrar;
fim;
j:=sessões;
k:=0;
for i:=1 to j do {Existem vários itens não utilizados no final da matriz da sessão de estatísticas}
começar
se sessão[ji].Usado então
quebrar;
inc(k);
fim;
se k>0 então {Corrija a matriz da sessão e libere os itens não utilizados no final}
começar
sessões:=sessões-k;
setlength(sessão,sessões);
fim;
edit1.text:=inttostr(sessões);
fim;
Quando ocorre um erro de comunicação arquivo://...
procedimento TForm1.ServerSocket1ClientError(Sender: TObject;
Soquete: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Inteiro);
var
i,j,k: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].SS_Handle=socket.SocketHandle) e sessão[i-1].Usado então
começar
session[i-1].client_connected:=false; {cliente não está conectado}
se sessão[i-1].remote_connected então
session[i-1].CSocket.active:=false {Se a conexão remota ainda estiver conectada, desconecte-a}
outro
session[i-1].Used:=false; {Se ambos estiverem desconectados, defina o sinalizador de recurso de liberação}
quebrar;
fim;
j:=sessões;
k:=0;
para i:=1 para j faça
começar
se sessão[ji].Usado então
quebrar;
inc(k);
fim;
se k>0 então
começar
sessões:=sessões-k;
setlength(sessão,sessões);
fim;
edit1.text:=inttostr(sessões);
código de erro:=0;
fim;
Quando file:// é enviado pelo proxy para solicitar a página...
procedimento TForm1.ServerSocket1ClientRead(Sender: TObject;
Soquete: TCustomWinSocket);
var
tmp,linha,host: string;
i,j,porta: inteiro;
começar
for i:=1 para sessões faça {determinar qual sessão é}
se sessão[i-1].Usado e (sessão[i-1].SS_Handle=socket.sockethandle) então
começar
session[i-1].request_str:=socket.ReceiveText; {salvar dados da solicitação}
tmp:=session[i-1].request_str; {armazenado em variável temporária}
memo1.lines.add(tmp);
j:=pos(char(13)+char(10),tmp); {marca de uma linha}
while j>0 do {digitaliza o texto da solicitação linha por linha, procurando o endereço do host}
começar
linha:=copiar(tmp,1,j-1); {pegue uma linha}
delete(tmp,1,j+1); {excluir uma linha}
j:=pos('Host',line); {sinalizador de endereço do host}
se j>0 então
começar
delete(linha,1,j+5); {exclui o caractere inválido anterior}
j:=pos(':',linha);
se j>0 então
começar
host:=copiar(linha,1,j-1);
excluir(linha,1,j);
tentar
porta:=strtoint(linha);
exceto
porta:=80;
fim;
fim
outro
começar
host:=trim(linha); {obter endereço do host}
porta:=80;
fim;
if not session[i-1].remote_connected then {Se a expedição ainda não estiver conectada}
começar
session[i-1].Request:=true; {definir sinalizador de dados de solicitação prontos}
session[i-1].CSocket.host:=host; {Defina o endereço do host remoto}
session[i-1].CSocket.port:=porta; {definir porta}
session[i-1].CSocket.active:=true; {Conectar ao host remoto}
sessão[i-1].Lookingup:=true; {definir sinalizador}
session[i-1].LookupTime:=0; {começa a contar a partir de 0}
fim
outro
{Se o controle remoto estiver conectado, envie a solicitação diretamente}
sessão[i-1].CSocket.socket.sendtext(sessão[i-1].request_str);
break; {parar o texto da solicitação de digitalização}
fim;
j:=pos(char(13)+char(10),tmp); {aponta para a próxima linha}
fim;
quebrar; {parar loop}
fim;
fim;
file://Quando a conexão com o host remoto for bem-sucedida...
procedimento TForm1.ClientSocket1Connect(Remetente: TObject;
Soquete: TCustomWinSocket);
var
eu: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].CSocket.socket.sockethandle=socket.SocketHandle) e sessão[i-1].Usado então
começar
sessão[i-1].CSocket.tag:=socket.SocketHandle;
session[i-1].remote_connected:=true; {Definir o sinalizador conectado ao host remoto}
sessão[i-1].Lookingup:=false; {limpar sinalizador}
quebrar;
fim;
fim;
file://Quando o host remoto se desconecta...
procedimento TForm1.ClientSocket1Disconnect(Remetente: TObject;
Soquete: TCustomWinSocket);
var
i,j,k: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].CSocket.tag=socket.SocketHandle) e sessão[i-1].Usado então
começar
session[i-1].remote_connected:=false; {definido como não conectado}
se não for session[i-1].client_connected então
session[i-1].Used:=false {Se o cliente estiver desconectado, defina o sinalizador de recurso de liberação}
outro
para k:=1 para serverocket1.Socket.ActiveConnections faça
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) e session[i-1].usado então
começar
serverocket1.Socket.Connections[k-1].Fechar;
quebrar;
fim;
quebrar;
fim;
j:=sessões;
k:=0;
para i:=1 para j faça
começar
se sessão[ji].Usado então
quebrar;
inc(k);
fim;
se k>0 então {corrigir matriz de sessão}
começar
sessões:=sessões-k;
setlength(sessão,sessões);
fim;
edit1.text:=inttostr(sessões);
fim;
file://Quando ocorre um erro na comunicação com o host remoto...
procedimento TForm1.ClientSocket1Error(Remetente: TObject;
Soquete: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Inteiro);
var
i,j,k: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].CSocket.tag=socket.SocketHandle) e sessão[i-1].Usado então
começar
soquete.fechar;
session[i-1].remote_connected:=false; {definido como não conectado}
se não for session[i-1].client_connected então
session[i-1].Used:=false {Se o cliente estiver desconectado, defina o sinalizador de recurso de liberação}
outro
para k:=1 para serverocket1.Socket.ActiveConnections faça
if (serversocket1.Socket.Connections[k-1].SocketHandle=session[i-1].SS_Handle) e session[i-1].usado então
começar
serverocket1.Socket.Connections[k-1].Fechar;
quebrar;
fim;
quebrar;
fim;
j:=sessões;
k:=0;
para i:=1 para j faça
começar
se sessão[ji].Usado então
quebrar;
inc(k);
fim;
código de erro:=0;
se k>0 então {corrigir matriz de sessão}
começar
sessões:=sessões-k;
setlength(sessão,sessões);
fim;
edit1.text:=inttostr(sessões);
fim;
file://Envia uma solicitação de página para o host remoto…
procedimento TForm1.ClientSocket1Write(Remetente: TObject;
Soquete: TCustomWinSocket);
var
eu: inteiro;
começar
para i:=1 para sessões faça
if (sessão[i-1].CSocket.tag=socket.SocketHandle) e sessão[i-1].Usado então
começar
se sessão[i-1].Solicitar então
começar
socket.SendText(session[i-1].request_str {Se houver uma solicitação, envie}
sessão[i-1].Request:=false; {limpar sinalizador}
fim;
quebrar;
fim;
fim;
file://Quando o host remoto envia dados da página...
procedimento TForm1.ClientSocket1Read(Remetente: TObject;
Soquete: TCustomWinSocket);
var
i,j: inteiro;
rec_bytes: inteiro; {comprimento do bloco de dados retornado}
rec_Buffer: array[0..2047] de char; {buffer de bloco de dados retornado}
começar
para i:=1 para sessões faça
if (sessão[i-1].CSocket.tag=socket.SocketHandle) e sessão[i-1].Usado então
começar
rec_bytes:=socket.ReceiveBuf(rec_buffer,2048); {Receber dados}
para j:=1 para serverocket1.Socket.ActiveConnections faça
se serverocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle então
começar
serverocket1.Socket.Connections[j-1].SendBuf(rec_buffer,rec_bytes); {Enviar dados}
quebrar;
fim;
quebrar;
fim;
fim;
file:// "Página não encontrada" e outras mensagens de erro aparecem...
procedimento TForm1.AppException(Remetente: TObject; E: Exceção);
começar
inc(solicitações inválidas);
fim;
file://Encontre o tempo do host remoto...
procedimento TForm1.Timer1Timer(Remetente: TObject);
var
i,j: inteiro;
começar
para i:=1 para sessões faça
se sessão[i-1].Usado e sessão[i-1].Procurando então {se estiver conectando}
começar
inc(sessão[i-1].LookupTime);
se sessão[i-1].LookupTime>lookuptimeout então {if timeout}
começar
sessão[i-1].Procurando:=falso;
session[i-1].CSocket.active:=false; {parar de pesquisar}
para j:=1 para serverocket1.Socket.ActiveConnections faça
se serverocket1.Socket.Connections[j-1].SocketHandle=session[i-1].SS_Handle então
começar
serverocket1.Socket.Connections[j-1].Fechar; {desconectar cliente}
quebrar;
fim;
fim;
fim;
fim;
fim.
3. Pós-escrito
Como esta ideia de design apenas adiciona uma função de redirecionamento entre o agente e o host remoto, o original
Alguns recursos, como a tecnologia de cache, são mantidos, portanto a eficiência é maior. Após o teste, ao usar um modem de 33,6K para acessar a Internet, três a dez estações de trabalho proxy podem acessar a Internet ao mesmo tempo e ainda há uma boa velocidade de resposta. Como a conexão entre a estação de trabalho proxy e a estação de trabalho do servidor proxy geralmente passa por um link de alta velocidade, o gargalo ocorre principalmente no método de acesso à Internet do servidor proxy.
Através do método acima, o autor desenvolveu com sucesso um conjunto completo de software de servidor proxy e integrou-o totalmente ao sistema de cobrança da sala de informática.
Com sucesso, é possível usar uma estação de trabalho para completar funções como proxy da Internet, faturamento da Internet e faturamento do uso da máquina. Amigos com experiência em programação podem adicionar funções adicionais de servidor proxy, como configurar sites de acesso proibido, contar tráfego de clientes, listas de acesso à Web, etc.