Falando sobre a aplicação do “flow” na programação Delphi
Chen Jingtao
O que é um fluxo? Stream, simplesmente, é uma ferramenta abstrata de processamento de dados baseada em orientação a objetos. No fluxo, são definidas algumas operações básicas para processamento de dados, como leitura de dados, gravação de dados, etc. O programador executa todas as operações no fluxo sem se preocupar com a direção real do fluxo dos dados na outra extremidade do fluxo. Os fluxos podem não apenas processar arquivos, mas também memória dinâmica, dados de rede e outras formas de dados. Se você for muito proficiente em operações de fluxo e usar a conveniência dos fluxos em seu programa, a eficiência da escrita de programas será bastante melhorada.
Abaixo, o autor usa quatro exemplos: criptografador de arquivo EXE, cartão eletrônico, OICQ caseiro e transmissão de tela em rede para ilustrar o uso de "stream" na programação Delphi. Algumas das técnicas nesses exemplos já foram segredos de muitos softwares e não eram abertas ao público. Agora todos podem citar o código diretamente e gratuitamente.
“Edifícios altos surgem do solo.” Antes de analisar os exemplos, vamos primeiro entender os conceitos e funções básicas do fluxo. Somente depois de compreender essas coisas básicas podemos prosseguir para a próxima etapa. Certifique-se de compreender esses métodos básicos cuidadosamente. Claro, se você já estiver familiarizado com eles, poderá pular esta etapa.
1. Conceitos básicos e declarações de funções de streams em Delphi
No Delphi, a classe base de todos os objetos stream é a classe TStream, que define as propriedades e métodos comuns de todos os streams.
As propriedades definidas na classe TStream são apresentadas da seguinte forma:
1. Tamanho: Esta propriedade retorna o tamanho dos dados do fluxo em bytes.
2. Posição: Este atributo controla a posição do ponteiro de acesso no fluxo.
Existem quatro métodos virtuais definidos no Tstream:
1. Leitura: Este método lê dados do fluxo. O protótipo da função é:
Função Leitura(var Buffer;Contagem:Inteiro longo):Inteiro longo;virtual;abstrato;
O parâmetro Buffer é o buffer colocado durante a leitura dos dados. Count é o número de bytes de dados a serem lidos. O valor de retorno deste método é o número real de bytes lidos, que pode ser menor ou igual ao valor especificado em Count. .
2. Gravação: Este método grava dados no fluxo. O protótipo da função é:
Função Write(var Buffer;Contagem:Inteiro longo):Inteiro longo;virtual;abstrato;
O parâmetro Buffer é o buffer dos dados a serem gravados no fluxo, Count é o comprimento dos dados em bytes e o valor de retorno deste método é o número de bytes realmente gravados no fluxo.
3. Busca: Este método implementa o movimento do ponteiro de leitura no fluxo. O protótipo da função é:
Busca de Função (Deslocamento:Inteiro Longo;Origem:Word):Inteiro Longo;virtual;abstrato;
O parâmetro Offset é o número de bytes de offset, e o parâmetro Origin aponta o real significado do Offset. Seus valores possíveis são os seguintes:
soFromBeginning:Offset é a posição do ponteiro desde o início dos dados após o movimento. Neste momento, o deslocamento deve ser maior ou igual a zero.
soFromCurrent:Offset é a posição relativa do ponteiro após o movimento e o ponteiro atual.
soFromEnd:Offset é a posição do ponteiro no final dos dados após o movimento. Neste momento, o deslocamento deve ser menor ou igual a zero. O valor de retorno deste método é a posição do ponteiro após o movimento.
4. Setsize: Este método altera o tamanho dos dados. O protótipo da função é:
Função Setsize(NewSize:Longint);virtual;
Além disso, vários métodos estáticos também são definidos na classe TStream:
1. ReadBuffer: A função deste método é ler dados da posição atual no fluxo. O protótipo da função é:
Procedimento ReadBuffer(var Buffer;Contagem:Inteiro longo);
A definição dos parâmetros é a mesma da leitura acima. Nota: Quando o número de bytes de dados lidos não for igual ao número de bytes que precisam ser lidos, uma exceção EReadError será gerada.
2. WriteBuffer: A função deste método é gravar dados no fluxo na posição atual. O protótipo da função é:
Procedimento WriteBuffer(var Buffer;Contagem:Inteiro longo);
A definição dos parâmetros é a mesma do Write acima. Nota: Quando o número de bytes de dados gravados não for igual ao número de bytes que precisam ser gravados, uma exceção EWriteError será gerada.
3. CopyFrom: Este método é usado para copiar fluxos de dados de outros fluxos. O protótipo da função é:
Função CopyFrom(Fonte:TStream;Count:Longint):Longint;
O parâmetro Source é o fluxo que fornece os dados e Count é o número de bytes de dados copiados. Quando Count é maior que 0, CopyFrom copia Count bytes de dados da posição atual do parâmetro Source; quando Count é igual a 0, CopyFrom define a propriedade Position do parâmetro Source como 0 e depois copia todos os dados do Source; ;
TStream possui outras classes derivadas, das quais a mais comumente usada é a classe TFileStream. Para usar a classe TFileStream para acessar arquivos, você deve primeiro criar uma instância. A declaração é a seguinte:
construtor Create(const Nome do arquivo:string;Modo:Word);
Nome do arquivo é o nome do arquivo (incluindo o caminho), e o parâmetro Modo é a forma de abrir o arquivo, que inclui o modo de abertura e modo de compartilhamento do arquivo. Seus possíveis valores e significados são os seguintes:
Modo aberto:
fmCreate: Crie um arquivo com o nome de arquivo especificado ou abra o arquivo se ele já existir.
fmOpenRead: abre o arquivo especificado no modo somente leitura
fmOpenWrite: abre o arquivo especificado no modo somente gravação
fmOpenReadWrite: abre o arquivo especificado para gravação
Modo de compartilhamento:
fmShareCompat: o modo de compartilhamento é compatível com FCBs
fmShareExclusive: Não permita que outros programas abram o arquivo de forma alguma
fmShareDenyWrite: Não permite que outros programas abram o arquivo para gravação
fmShareDenyRead: Não permite que outros programas abram o arquivo no modo de leitura
fmShareDenyNone: Outros programas podem abrir o arquivo de qualquer forma
TStream também possui uma classe derivada, TMemoryStream, que é usada com muita frequência em aplicações reais. É chamado de fluxo de memória, o que significa criar um objeto de fluxo na memória. Seus métodos e funções básicos são os mesmos acima.
Bem, com a base acima estabelecida, podemos iniciar nossa jornada de programação.
-------------------------------------------------- --------------------------
2. Aplicação prática um: usando streams para criar criptografadores de arquivos EXE, pacotes, arquivos autoextraíveis e programas de instalação
Vamos primeiro falar sobre como criar um criptografador de arquivo EXE.
O princípio do criptografador de arquivo EXE: Crie dois arquivos, um é usado para adicionar recursos ao outro arquivo EXE, que é chamado de programa complementar. Outro arquivo EXE adicionado é chamado de arquivo de cabeçalho. A função deste programa é ler os arquivos adicionados a ele. A estrutura do arquivo EXE no Windows é relativamente complexa e alguns programas também possuem somas de verificação. Quando descobrem que foram alterados, pensam que estão infectados por um vírus e se recusam a executar. Portanto, adicionamos o arquivo ao nosso próprio programa para que a estrutura original do arquivo não seja alterada. Vamos primeiro escrever uma função add. A função desta função é adicionar um arquivo como um fluxo ao final de outro arquivo. A função é a seguinte:
Função Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Destino,Fonte:TFileStream;
MeuFileSize:inteiro;
começar
tentar
Fonte:=TFileStream.Create(SourceFile,fmOpenRead ou fmShareExclusive);
Destino:=TFileStream.Create(TargetFile,fmOpenWrite ou fmShareExclusive);
tentar
Target.Seek(0,soFromEnd);//Adiciona recursos ao final
Target.CopyFrom(Fonte,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calcula o tamanho do recurso e escreve-o no final do processo auxiliar
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finalmente
Alvo.Grátis;
Fonte.Grátis;
fim;
exceto
Resultado:=Falso;
Saída;
fim;
Resultado:=Verdadeiro;
fim;
Com a base acima, devemos compreender facilmente esta função. O parâmetro SourceFile é o arquivo a ser adicionado e o parâmetro TargetFile é o arquivo de destino a ser adicionado. Por exemplo, para adicionar a.exe a b.exe: Cjt_AddtoFile('a.exe',b.exe'); Se a adição for bem-sucedida, retornará True, caso contrário, retornará False.
Com base na função acima, podemos escrever a função de leitura oposta:
Função Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
var
Fonte:TFileStream;
Alvo:TMemoryStream;
MeuFileSize:inteiro;
começar
tentar
Destino:=TMemoryStream.Create;
Fonte:=TFileStream.Create(SourceFile,fmOpenRead ou fmShareDenyNone);
tentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Lê o tamanho do recurso
Source.Seek(-MyFileSize,soFromEnd);//Localiza o local do recurso
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//Remover recursos
Target.SaveToFile(TargetFile);//Salvar no arquivo
finalmente
Alvo.Grátis;
Fonte.Grátis;
fim;
exceto
Resultado:=falso;
Saída;
fim;
Resultado:=verdadeiro;
fim;
O parâmetro SourceFile é o nome do arquivo ao qual o arquivo foi adicionado e o parâmetro TargetFile é o nome do arquivo de destino salvo após o arquivo ser retirado. Por exemplo, Cjt_LoadFromFile('b.exe','a.txt'); retira o arquivo em b.exe e o salva como a.txt. Se a extração for bem-sucedida, retorna True, caso contrário, retorna False.
Abra o Delphi, crie um novo projeto e coloque um controle Edit Edit1 e dois Buttons na janela: Button1 e Button2. A propriedade Caption do botão está definida como "OK" e "Cancelar", respectivamente. Escreva o código no evento Click do Button1:
var S: string;
começar
S:=ChangeFileExt(application.ExeName,'.Cjt');
se Edit1.Text='790617' então
começar
Cjt_LoadFromFile(Application.ExeName,S);
{Retire o arquivo e salve-o no caminho atual e nomeie-o como "arquivo original.Cjt"}
Winexec(pchar(S),SW_Show);{Execute "arquivo original.Cjt"}
Application.Terminate;{Sair do programa}
fim
outro
Application.MessageBox('A senha está incorreta, digite novamente!', 'A senha está incorreta', MB_ICONERROR+MB_OK);
Compile este programa e renomeie o arquivo EXE para head.exe. Crie um novo arquivo de texto head.rc com o conteúdo: head exefile head.exe, copie-os para o diretório BIN do Delphi, execute o comando Dos Brcc32.exe head.rc e um arquivo head.res será gerado. file é Mantenha os arquivos de recursos que queremos primeiro.
Nosso arquivo de cabeçalho foi criado, vamos criar o programa add-in.
Crie um novo projeto e coloque os seguintes controles: um Edit, um Opendialog e as propriedades Caption dos dois Button1s são definidas como "Select File" e "Encryption" respectivamente. Adicione uma frase no programa fonte: {$R head.res} e copie o arquivo head.res para o diretório atual do programa. Desta forma, o head.exe agora é compilado junto com o programa.
Escreva o código no evento Cilck do Button1:
se OpenDialog1.Execute então Edit1.Text:=OpenDialog1.FileName;
Escreva o código no evento Cilck do Button2:
var S:String;
começar
S:=ExtractFilePath(Edit1.Text);
se ExtractRes('exefile','head',S+'head.exe') então
se Cjt_AddtoFile(Edit1.Text,S+'head.exe') então
se DeleteFile (Edit1.Text) então
se RenameFile(S+'head.exe',Edit1.Text) então
Application.MessageBox('Criptografia de arquivo bem-sucedida!','Mensagem',MB_ICONINFORMATION+MB_OK)
outro
começar
se FileExists(S+'head.exe') then DeleteFile(S+'head.exe');
Application.MessageBox('Falha na criptografia do arquivo!','Mensagem',MB_ICONINFORMATION+MB_OK)
fim;
fim;
Entre eles, ExtractRes é uma função customizada, que é usada para extrair head.exe do arquivo de recurso.
Função ExtractRes(ResType, ResName, ResNewName: String):boolean;
var
Res: TResourceStream;
começar
tentar
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
tentar
Res.SavetoFile(ResNovoNome);
Resultado:=verdadeiro;
finalmente
Res.Livre;
fim;
exceto
Resultado:=falso;
fim;
fim;
Nota: Nossa função acima simplesmente anexa um arquivo ao final de outro arquivo. Em aplicações reais, pode ser alterado para adicionar vários arquivos, desde que o endereço de deslocamento seja definido de acordo com o tamanho e número reais. Por exemplo, um empacotador de arquivos adiciona dois ou mais programas a um arquivo de cabeçalho. Os princípios dos programas e instaladores autoextraíveis são os mesmos, mas com mais compactação. Por exemplo, podemos referenciar uma unidade LAH, compactar o fluxo e depois adicioná-lo, para que o arquivo fique menor. Basta descompactá-lo antes de lê-lo. Além disso, o exemplo do criptografador EXE do artigo ainda apresenta muitas imperfeições. Por exemplo, a senha é fixada em "790617", e após retirar o EXE e executá-lo, ele deve ser excluído após terminar a execução, etc. Os leitores podem modificá-lo por conta própria.
-------------------------------------------------- -------------------
3. Aplicação Prática 2: Usando streams para criar cartões eletrônicos executáveis
Freqüentemente vemos alguns softwares de produção de cartões eletrônicos que permitem que você mesmo selecione as imagens e, em seguida, gera um arquivo executável EXE para você. Ao abrir o cartão comemorativo, a imagem será exibida enquanto a música é reproduzida. Agora que aprendemos as operações de fluxo, também podemos fazer uma.
No processo de adição de imagens, podemos usar diretamente o Cjt_AddtoFile anterior, e o que precisamos fazer agora é como ler e exibir as imagens. Podemos usar o Cjt_LoadFromFile anterior para ler a imagem primeiro, salvá-la como um arquivo e depois carregá-la. No entanto, existe uma maneira mais simples, que é ler diretamente o fluxo do arquivo e exibi-lo com a poderosa ferramenta de fluxo. , tudo se torna simples.
As imagens mais populares hoje em dia são os formatos BMP e JPG. Escreveremos agora funções de leitura e exibição para esses dois tipos de imagens.
Função Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
var
Fonte:TFileStream;
MeuFileSize:inteiro;
começar
Fonte:=TFileStream.Create(SourceFile,fmOpenRead ou fmShareDenyNone);
tentar
tentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Lê recursos
Source.Seek(-MyFileSize,soFromEnd);//Localiza a posição inicial do recurso
ImgBmp.Picture.Bitmap.LoadFromStream(Fonte);
finalmente
Fonte.Grátis;
fim;
exceto
Resultado:=Falso;
Saída;
fim;
Resultado:=Verdadeiro;
fim;
A descrição acima é uma função para leitura de imagens BMP e a seguir é uma função para leitura de imagens JPG. Como a unidade JPG é usada, uma frase: usa jpeg deve ser adicionada ao programa.
Função Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
var
Fonte:TFileStream;
MeuFileSize:inteiro;
Meujpg: TJpegImage;
começar
tentar
Meujpg:= TJpegImage.Create;
Fonte:=TFileStream.Create(SourceFile,fmOpenRead ou fmShareDenyNone);
tentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(Fonte);
JpgImg.Picture.Bitmap.Assign(Meujpg);
finalmente
Fonte.Grátis;
Meujpg.free;
fim;
exceto
Resultado:=falso;
Saída;
fim;
Resultado:=verdadeiro;
fim;
Com estas duas funções podemos fazer um programa de leitura. Vamos pegar fotos BMP como exemplo:
Execute o Delphi, crie um novo projeto e coloque um controle de exibição de imagem Image1. Basta escrever a seguinte frase no evento Create da janela:
Cjt_BmpLoad(Image1,Application.ExeName);
Este é o arquivo de cabeçalho e, em seguida, usamos o método anterior para gerar um arquivo de recurso head.res.
Agora podemos começar a fazer nosso programa complementar. Todo o código é o seguinte:
unidade Unidade1;
interface
usa
Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, Formulários, Diálogos,
ExtCtrls, StdCtrls, ExtDlgs;
tipo
TForm1 = classe(TForm)
Editar1: TEdit;
Botão1: Botão T;
Botão2: Botão T;
OpenPictureDialog1:TOpenPictureDialog;
procedimento FormCreate(Remetente: TObject);
procedimento Button1Click(Remetente: TObject);
procedimento Button2Click(Remetente: TObject);
privado
Função ExtractRes(ResType, ResName, ResNewName: String):boolean;
Função Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{Declarações privadas}
público
{Declarações públicas}
fim;
var
Formulário1: TForm1;
implementação
{$R *.DFM}
Função TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res: TResourceStream;
começar
tentar
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
tentar
Res.SavetoFile(ResNovoNome);
Resultado:=verdadeiro;
finalmente
Res.Livre;
fim;
exceto
Resultado:=falso;
fim;
fim;
Função TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Destino,Fonte:TFileStream;
MeuFileSize:inteiro;
começar
tentar
Fonte:=TFileStream.Create(SourceFile,fmOpenRead ou fmShareExclusive);
Destino:=TFileStream.Create(TargetFile,fmOpenWrite ou fmShareExclusive);
tentar
Target.Seek(0,soFromEnd);//Adiciona recursos ao final
Target.CopyFrom(Fonte,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calcula o tamanho do recurso e escreve-o no final do processo auxiliar
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finalmente
Alvo.Grátis;
Fonte.Grátis;
fim;
exceto
Resultado:=Falso;
Saída;
fim;
Resultado:=Verdadeiro;
fim;
procedimento TForm1.FormCreate(Remetente: TObject);
começar
Legenda:='Programa de demonstração Bmp2Exe Autor: Chen Jingtao';
Editar1.Texto:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);
Button1.Caption:='Selecionar imagem BMP';
Button2.Caption:='Gerar EXE';
fim;
procedimento TForm1.Button1Click(Remetente: TObject);
começar
se OpenPictureDialog1.Execute então
Edit1.Text:=OpenPictureDialog1.NomeArquivo;
fim;
procedimento TForm1.Button2Click(Remetente: TObject);
var
HeadTemp:Corda;
começar
se não existir arquivo (Edit1.Text), então
começar
Application.MessageBox('Arquivo de imagem BMP não existe, selecione novamente!','Mensagem',MB_ICONINFORMATION+MB_OK)
Saída;
fim;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
se ExtractRes('exefile','head',HeadTemp) então
se Cjt_AddtoFile(Edit1.Text,HeadTemp) então
Application.MessageBox('Arquivo EXE gerado com sucesso!','Mensagem',MB_ICONINFORMATION+MB_OK)
outro
começar
se FileExists(HeadTemp) então DeleteFile(HeadTemp);
Application.MessageBox('Falha na geração do arquivo EXE!','Mensagem',MB_ICONINFORMATION+MB_OK)
fim;
fim;
fim.
Que tal? É incrível :) Deixe a interface do programa mais bonita e adicione algumas funções, e você descobrirá que não é muito inferior aos softwares que exigem registro.
-------------------------------------------------- --------------------------
Aplicação prática três: Use streams para fazer seu próprio OICQ
OICQ é um software de comunicação online em tempo real desenvolvido pela Shenzhen Tencent Company e possui uma grande base de usuários na China. No entanto, o OICQ deve estar conectado à Internet e logado no servidor da Tencent antes de poder ser usado. Assim, podemos escrever um e usá-lo na rede local.
OICQ utiliza o protocolo UDP, que é um protocolo sem conexão, ou seja, as partes comunicantes podem enviar informações sem estabelecer conexão, portanto a eficiência é relativamente alta. O controle NMUDP do FastNEt que vem com o próprio Delphi é um controle de datagrama de usuário do protocolo UDP. Porém, deve-se ressaltar que se você utilizar este controle, deverá sair do programa antes de desligar o computador, pois o controle TNMXXX possui um BUG. O ThreadTimer usado pelo PowerSocket, base de todos os controles nm, usa uma janela oculta (classe TmrWindowClass) para lidar com falhas.
O que deu errado:
Psock::TThreadTimer::WndProc(var msg:TMessage)
se msg.message=WM_TIMER então
Ele mesmo cuida disso
msg.resultado:=0
outro
msg.result:=DefWindowProc(0,....)
fim
O problema é que ao chamar DefWindowProc, o parâmetro HWND transmitido é na verdade uma constante 0, então DefWindowProc realmente não pode funcionar. A chamada para qualquer mensagem de entrada retorna 0, incluindo WM_QUERYENDsession, então o Windows não pode sair. Devido à chamada anormal de DefWindowProc, na verdade, exceto WM_TIMER, outras mensagens processadas por DefWindowProc são inválidas.
A solução está em PSock.pas
Dentro de TThreadTimer.Wndproc
Resultado:= DefWindowProc(0, Msg, WPARAM, LPARAM);
Alterar para:
Resultado: = DefWindowProc (FWindowHandle, Msg, WPARAM, LPARAM);
As primeiras versões de baixo nível do OICQ também apresentavam esse problema. Se o OICQ não fosse desligado, a tela piscaria por um momento e depois retornaria quando o computador fosse desligado.
Ok, sem mais delongas, vamos escrever nosso OICQ. Este é na verdade um exemplo que vem com o Delphi :)
Crie um novo projeto, arraste um controle NMUDP do painel FASTNET para a janela e coloque três EDITs chamados Editip, EditPort, EditMyTxt, três botões BtSend, BtClear, BtSave, um MEMOMemoReceive, um SaveDialog e uma barra de status. Quando o usuário clica em BtSend, um objeto de fluxo de memória é criado, as informações de texto a serem enviadas são gravadas no fluxo de memória e, em seguida, o NMUDP envia o fluxo. Quando o NMUDP recebe dados, seu evento DataReceived é acionado. Aqui convertemos o fluxo recebido em informações de caracteres e depois os exibimos.
Nota: Lembre-se de liberar (Free) todos os objetos de fluxo após serem criados e usados. Na verdade, seu destruidor deve ser Destroy. Porém, se a criação do fluxo falhar, usar Destroy irá gerar uma exceção. verificará primeiro se o fluxo não for estabelecido com sucesso, ele será liberado somente se for estabelecido, por isso é mais seguro usar o Gratuito.
Neste programa utilizamos o controle NMUDP, que possui diversas propriedades importantes. RemoteHost representa o IP ou nome do computador remoto e LocalPort é a porta local, que monitora principalmente se há entrada de dados. O RemotePort é uma porta remota e os dados são enviados através desta porta durante o envio de dados. Entendendo isso já podemos entender nosso programa.
Todo o código é o seguinte:
unidade Unidade1;
interface
usa
Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, Formulários, Diálogos,StdCtrls, ComCtrls,NMUDP;
tipo
TForm1 = classe(TForm)
NMUDP1: TNMUDP;
EditarIP: TEdit;
EditPort: TEdit;
EditarMeuTexto: TEdit;
MemoReceber: TMemo;
BtEnviar: TButton;
BtClear: Botão T;
BtSalvar: TButton;
StatusBar1: TStatusBar;
SaveDialog1: TSaveDialog;
procedimento BtSendClick(Remetente: TObject);
procedimento NMUDP1DataReceived(Sender: TComponent; NumberBytes: Integer;
FromIP: String; Porta: Inteiro);
procedimento NMUDP1InvalidHost(var manipulado: Boolean);
procedimento NMUDP1DataSend(Remetente: TObject);
procedimento FormCreate(Remetente: TObject);
procedimento BtClearClick(Remetente: TObject);
procedimento BtSaveClick(Remetente: TObject);
procedimento EditMyTxtKeyPress(Sender: TObject; var Key: Char);
privado
{Declarações privadas}
público
{Declarações públicas}
fim;
var
Formulário1: TForm1;
implementação
{$R *.DFM}
procedimento TForm1.BtSendClick(Remetente: TObject);
var
MeuStream: TMemoryStream;
MeuEnviarTexto: String;
Importar,icode:inteiro;
Começar
Val(EditPort.Text,Iport,icode);
se icode<>0 então
começar
Application.MessageBox('A porta deve ser um número, digite novamente!','Mensagem',MB_ICONINFORMATION+MB_OK);
Saída;
fim;
NMUDP1.RemoteHost := EditIP.Text; {host remoto}
NMUDP1.LocalPort:=Iportar; {porta local}
NMUDP1.RemotePort := Porta; {porta remota}
MeuEnvioTxt := EditMeuTxt.Text;
MyStream := TMemoryStream.Create; {Criar fluxo}
tentar
MyStream.Write(MySendTxt[1], Length(EditMyTxt.Text));{Escrever dados}
NMUDP1.SendStream(MyStream); {Enviar fluxo}
finalmente
MyStream.Free; {transmissão de lançamento}
fim;
fim;
procedimento TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Inteiro; FromIP: String;
var
MeuStream: TMemoryStream;
MeuReciveTxt: String;
começar
MyStream := TMemoryStream.Create; {Criar fluxo}
tentar
NMUDP1.ReadStream(MyStream);{Receber stream}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes é o número de bytes recebidos}
MyStream.Read(MyReciveTxt[1],NumberBytes);{ler dados}
MemoReceive.Lines.Add('Informações recebidas do host '+FromIP+':'+MyReciveTxt);
finalmente
MyStream.Free; {transmissão de lançamento}
fim;
fim;
procedimento TForm1.NMUDP1InvalidHost(var manipulado: Boolean);
começar
Application.MessageBox('O endereço IP da outra parte está incorreto, digite novamente!','Mensagem',MB_ICONINFORMATION+MB_OK);
fim;
procedimento TForm1.NMUDP1DataSend(Remetente: TObject);
começar
StatusBar1.SimpleText:='Mensagem enviada com sucesso!';
fim;
procedimento TForm1.FormCreate(Remetente: TObject);
começar
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='Enviar';
BtClear.Caption:='Limpar histórico de bate-papo';
BtSave.Caption:='Salvar histórico de bate-papo';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.Clear;
EditMyTxt.Text:='Insira as informações aqui e clique em Enviar.';
StatusBar1.SimplePanel:=true;
fim;
procedimento TForm1.BtClearClick(Remetente: TObject);
começar
MemoReceive.Clear;
fim;
procedimento TForm1.BtSaveClick(Remetente: TObject);
começar
se SaveDialog1.Execute então MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
fim;
procedimento TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char);
começar
se Key=#13 então BtSend.Click;
fim;
fim.
O programa acima certamente está longe do OICQ, porque o OICQ usa o método de comunicação Socket5. Quando fica on-line, ele primeiro recupera as informações do amigo e o status on-line do servidor. Quando o envio expira, ele primeiro salva as informações no servidor, espera que a outra parte fique on-line na próxima vez, depois as envia e exclui as. backup do servidor. Você pode melhorar este programa com base nos conceitos aprendidos anteriormente. Por exemplo, adicionar um controle NMUDP para gerenciar o status online. As informações enviadas são primeiro convertidas em código ASCII para operação AND e adicionam informações de cabeçalho. receber as informações Esteja o cabeçalho da informação correto ou não, a informação será descriptografada e exibida apenas se estiver correta, melhorando assim a segurança e a confidencialidade.
Além disso, outra grande vantagem do protocolo UDP é que ele pode transmitir, o que significa que qualquer pessoa no mesmo segmento de rede pode receber informações sem especificar um endereço IP específico. Os segmentos de rede são geralmente divididos em três categorias: A, B e C.
1~126.XXX.XXX.XXX (rede Classe A): O endereço de transmissão é XXX.255.255.255
128~191.XXX.XXX.XXX (rede Classe B): O endereço de transmissão é XXX.XXX.255.255
192~254.XXX.XXX.XXX (rede categoria C): O endereço de transmissão é XXX.XXX.XXX.255
Por exemplo, se três computadores forem 192.168.0.1, 192.168.0.10 e 192.168.0.18, ao enviar informações, você só precisará especificar o endereço IP 192.168.0.255 para realizar a transmissão. Abaixo está uma função que converte IP em IP de transmissão. Use-a para melhorar seu próprio OICQ^-^.
Função Trun_ip(S:string):string;
var s1,s2,s3,ss,sss,Cabeça: string;
n,m:inteiro;
começar
sss:=S;
n:=pos('.',s);
s1:=cópia(s,1,n);
m:=comprimento(s1);
excluir(s,1,m);
Cabeçalho:=cópia(s1,1,(comprimento(s1)-1));
n:=pos('.',s);
s2:=cópia(s,1,n);
m:=comprimento(s2);
excluir(s,1,m);
n:=pos('.',s);
s3:=cópia(s,1,n);
m:=comprimento(s3);
excluir(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255'; //1~126.255.255.255 (rede Classe A)
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255 (rede Classe B)
if strtoint(Head) em [192..254] then ss:=s1+s2+s3+'255';
Resultado:=ss;
fim;
-------------------------------------------------- --------------------------
5. Aplicação Prática 4: Usando streams para transmitir imagens de tela pela rede
Você já deve ter visto muitos programas de gerenciamento de rede. Uma das funções desses programas é monitorar a tela de um computador remoto. Na verdade, isso também é conseguido usando operações de fluxo. Abaixo damos um exemplo. Este exemplo está dividido em dois programas, um é o servidor e o outro é o cliente. Depois que o programa é compilado, ele pode ser usado diretamente em uma única máquina, em uma rede local ou na Internet. Comentários correspondentes foram fornecidos no programa. Faremos uma análise detalhada posteriormente.
Crie um novo projeto e arraste um controle ServerSocket para a janela do painel Internet. Este controle é utilizado principalmente para monitorar o cliente e estabelecer conexões e comunicações com o cliente. Após definir a porta de escuta, chame o método Open ou Active:=True para começar a trabalhar. Nota: Ao contrário do NMUDP anterior, uma vez que o Socket começa a escutar, sua porta não pode ser alterada. Se desejar alterá-la, você deve primeiro chamar Close ou definir Active como False, caso contrário ocorrerá uma exceção. Além disso, se a porta já estiver aberta, ela não poderá mais ser usada. Portanto, você não pode executar o programa novamente antes de sair, caso contrário ocorrerá uma exceção, ou seja, uma janela de erro aparecerá. Em aplicações práticas, erros podem ser evitados determinando se o programa foi executado e saindo se já estiver em execução.
Quando os dados são passados do cliente, o evento ServerSocket1ClientRead será acionado e podemos processar os dados recebidos aqui. Neste programa, recebe principalmente as informações de caráter enviadas pelo cliente e realiza as operações correspondentes conforme acordo prévio.
Todo o código do programa é o seguinte:
unit Unit1;{programa servidor}
interface
usa
Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, Formulários, Diálogos, JPEG, ExtCtrls, ScktComp;
tipo
TForm1 = classe(TForm)
ServidorSocket1: TServerSocket;
procedimento ServerSocket1ClientRead(Sender: TObject;Socket: TCustomWinSocket);
procedimento FormCreate(Remetente: TObject);
procedimento FormClose(Sender: TObject; var Action: TCloseAction);
privado
procedimento Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
{Função de captura de tela personalizada, DrawCur indica se deve capturar a imagem do mouse}
{Declarações privadas}
público
{Declarações públicas}
fim;
var
Formulário1: TForm1;
MyStream: TMemorystream;{objeto de fluxo de memória}
implementação
{$R *.DFM}
procedimento TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
var
Cursorx, Cursor: inteiro;
dc:hdc;
Mycan: Tcanvas;
R: TRect;
DrawPos: PontoT;
MeuCursor: TIcon;
hld: hwnd;
Tópico: dword;
mp: ponto t;
pIconInfo: TIconInfo;
começar
Mybmp := Tbitmap.Create; {Criar BMPMAP}
Mycan := TCanvas.Create; {captura de tela}
dc := GetWindowDC(0);
tentar
Mycan.Handle := dc;
R := Rect(0, 0, tela.Largura, tela.Altura);
Mybmp.Width := R.Direito;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
finalmente
liberaçãoDC(0, DC);
fim;
Mycan.Handle := 0;
Mycan.Free;
se DrawCur então {desenhe a imagem do mouse}
começar
GetCursorPos(DrawPos);
MeuCursor := TIcon.Create;
getcursorpos(mp);
hld := WindowFromPoint(mp);
Threadld := GetWindowThreadProcessId(hld, nil);
AttachThreadInput(GetCurrentThreadId, Threadld, True);
MeuCursor.Handle := Getcursor();
AttachThreadInput(GetCurrentThreadId, threadld, False);
GetIconInfo(Mycursor.Handle, pIconInfo);
cursorx := DrawPos.x - round(pIconInfo.xHotspot);
cursory := DrawPos.y - round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, cursory, MyCursor);
DeleteObject(pIconInfo.hbmColor);{GetIconInfo cria dois objetos bitmap quando usados. Esses dois objetos precisam ser liberados manualmente}.
DeleteObject(pIconInfo.hbmMask); {Caso contrário, após chamá-lo, ele criará um bitmap e várias chamadas gerarão várias até que os recursos se esgotem}
Mycursor.ReleaseHandle; {Liberar memória da matriz}
MyCursor.Free; {soltar o ponteiro do mouse}
fim;
fim;
procedimento TForm1.FormCreate(Remetente: TObject);
começar
ServerSocket1.Port := 3000;
ServerSocket1.Open; {Socket começa a escutar}
fim;
procedimento TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
começar
se ServerSocket1.Active então ServerSocket1.Close {Fechar Socket};
fim;
procedimento TForm1.ServerSocket1ClientRead(Sender: TObject;
Soquete: TCustomWinSocket);
var
S, S1: corda;
MeuBmp: TBitmap;
Meujpg: TJpegimage;
começar
S := Socket.ReceiveText;
if S = 'cap' then {O cliente emite um comando de captura de tela}
começar
tentar
MyStream := TMemorystream.Create;{Criar fluxo de memória}
MeuBmp := TBitmap.Create;
Meujpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True significa capturar a imagem do mouse}
Myjpg.Assign(MyBmp); {Converta imagens BMP para formato JPG para fácil transmissão na Internet}
Myjpg.CompressionQuality := 10; {configuração de porcentagem de compactação do arquivo JPG, quanto maior o número, mais nítida será a imagem, mas maiores serão os dados}
Myjpg.SaveToStream(MyStream); {Escrever imagem JPG para transmitir}
Meujpg.free;
MyStream.Position := 0;{Nota: esta frase deve ser adicionada}
s1 := inttostr(MyStream.size);{tamanho do stream}
Socket.sendtext(s1); {tamanho do fluxo de envio}
finalmente
MeuBmp.free;
fim;
fim;
if s = 'ready' then {O cliente está pronto para receber imagens}
começar
MeuStream.Position := 0;
Socket.SendStream(MyStream); {Enviar o stream}
fim;
fim;
fim.
O acima é o servidor, vamos escrever o programa cliente abaixo. Crie um novo projeto e adicione o controle Socket ClientSocket, o controle de exibição de imagem Image, um Panel, um Edit, dois Buttons e um controle de barra de status StatusBar1. Nota: Coloque Edit1 e dois botões acima do Panel1. As propriedades de ClientSocket são semelhantes às de ServerSocket, mas há uma propriedade Address adicional, que indica o endereço IP do servidor a ser conectado. Após preencher o endereço IP, clique em “Conectar” para estabelecer uma conexão com o programa do servidor. Se for bem-sucedido, a comunicação poderá ser iniciada. Clicar em “Captura de tela” enviará personagens para o servidor. Como o programa usa unidades de imagem JPEG, Jpeg deve ser adicionado a Usos.
Todo o código é o seguinte:
unidade Unidade2{cliente};
interface
usa
Windows, Mensagens, SysUtils, Classes, Gráficos, Controles, Formulários, Diálogos, StdCtrls, ScktComp, ExtCtrls, JPEG, ComCtrls;
tipo
TForm1 = classe(TForm)
ClienteSocket1: TClientSocket;
Imagem1: TImage;
StatusBar1: TStatusBar;
Painel1: TPanel;
Editar1: TEdit;
Botão1: Botão T;
Botão2: Botão T;
procedimento Button1Click(Remetente: TObject);
procedimento ClientSocket1Connect(Remetente: TObject;
Soquete: TCustomWinSocket);
procedimento Button2Click(Remetente: TObject);
procedimento ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: Inteiro);
procedimento ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
procedimento FormCreate(Remetente: TObject);
procedimento FormClose(Sender: TObject; var Action: TCloseAction);
procedimento ClientSocket1Disconnect(Sender: TObject;
Soquete: TCustomWinSocket);
privado
{Declarações privadas}
público
{Declarações públicas}
fim;
var
Formulário1: TForm1;
MeuTamanho: Inteiro Longo;
MyStream: TMemorystream;{objeto de fluxo de memória}
implementação
{$R *.DFM}
procedimento TForm1.FormCreate(Remetente: TObject);
começar
{-------- O seguinte é definir as propriedades de aparência do controle da janela ------------- }
{Nota: Coloque Button1, Button2 e Edit1 acima do Panel1}
Edit1.Text := '127.0.0.1';
Button1.Caption := 'Conectar ao host';
Button2.Caption := 'Captura de tela';
Button2.Enabled := falso;
Painel1.Align := alTop;
Imagem1.Align := alClient;
Imagem1.Stretch := Verdadeiro;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := Verdadeiro;
{---------------------------------------------------------------- ---------}
MyStream := TMemorystream.Create; {Crie um objeto de fluxo de memória}
MeuTamanho := 0; {inicialização}
fim;
procedimento TForm1.Button1Click(Remetente: TObject);
começar
se não for ClientSocket1.Active então
começar
ClientSocket1.Address := Edit1.Text; {endereço IP remoto}
ClientSocket1.Port := 3000; {porta do soquete}
ClientSocket1.Open; {Estabelecer conexão}
fim;
fim;
procedimento TForm1.Button2Click(Remetente: TObject);
começar
Clientsocket1.Socket.SendText('cap'); {Envie um comando para notificar o servidor para capturar a imagem da tela}
Button2.Enabled := Falso;
fim;
procedimento TForm1.ClientSocket1Connect(Remetente: TObject;
Soquete: TCustomWinSocket);
começar
StatusBar1.SimpleText := 'Com host' + ClientSocket1.Address + 'Conexão estabelecida com sucesso!';
Button2.Enabled := Verdadeiro;
fim;
procedimento TForm1.ClientSocket1Error(Remetente: TObject;
Soquete: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Inteiro);
começar
Código de erro: = 0; {Não abrir janela de erro}
StatusBar1.SimpleText := 'Não foi possível conectar com o host' + ClientSocket1.Address + 'Estabelecer conexão!';
fim;
procedimento TForm1.ClientSocket1Disconnect(Remetente: TObject;
Soquete: TCustomWinSocket);
começar
StatusBar1.SimpleText := 'Com host' + ClientSocket1.Address + 'Desconectar!';
Button2.Enabled := Falso;
fim;
procedimento TForm1.ClientSocket1Read(Remetente: TObject;
Soquete: TCustomWinSocket);
var
MyBuffer: array[0..10000] de byte; {Definir buffer de recebimento}
MyReceviceLength: inteiro;
S: corda;
MeuBmp: TBitmap;
MeuJpg: TJpegimage;
começar
StatusBar1.SimpleText := 'Recebendo dados...';
se MySize = 0 então {MySize é o número de bytes enviados pelo servidor. Se for 0, significa que a recepção da imagem ainda não foi iniciada}.
começar
S := Socket.ReceiveText;
MySize := Strtoint(S); {Define o número de bytes a serem recebidos}
Clientsocket1.Socket.SendText('ready'); {Envie um comando para notificar o servidor para iniciar o envio de imagens}
fim
outro
começar {A seguir está a parte de recebimento de dados de imagem}
MyReceviceLength := socket.ReceiveLength; {ler comprimento do pacote}
StatusBar1.SimpleText := 'Recebendo dados, o tamanho dos dados é:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {Receba o pacote de dados e leia-o no buffer}
MyStream.Write(MyBuffer, MyReceviceLength); {Gravar dados no fluxo}
if MyStream.Size >= MySize then {Se o comprimento do fluxo for maior que o número de bytes a serem recebidos, a recepção será concluída}
começar
MeuStream.Position := 0;
MeuBmp := tbitmap.Create;
MeuJpg := tjpegimage.Create;
tentar
MyJpg.LoadFromStream(MyStream); {Lê os dados do fluxo no objeto de imagem JPG}
MyBmp.Assign(MyJpg); {Converter JPG em BMP}
StatusBar1.SimpleText := 'Exibindo imagem';
Image1.Picture.Bitmap.Assign(MyBmp); {Atribuído ao elemento image1}
finalmente {A seguir está o trabalho de limpeza}
MeuBmp.free;
MeuJpg.free;
Button2.Enabled := verdadeiro;
{Socket.SendText('cap');Adicione esta frase para capturar a tela continuamente}
MeuStream.Clear;
MeuTamanho := 0;
fim;
fim;
fim;
fim;
procedimento TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
começar
MyStream.Free; {Libere o objeto de fluxo de memória}
se ClientSocket1.Active então ClientSocket1.Close {Fechar conexão de soquete};
fim;
fim.
Princípio do programa: execute o servidor para começar a ouvir, depois execute o cliente, insira o endereço IP do servidor para estabelecer uma conexão e, em seguida, envie um caractere para notificar o servidor para capturar a tela. O servidor chama a função personalizada Cjt_GetScreen para capturar a tela e salvá-la como BMP, converter o BMP em JPG, gravar o JPG no fluxo de memória e, em seguida, enviar o fluxo ao cliente. Após receber o stream, o cliente realiza a operação inversa, convertendo o stream para JPG e depois para BMP e posteriormente exibindo-o.
Nota: Devido às limitações do Socket, dados muito grandes não podem ser enviados de uma vez, mas só podem ser enviados várias vezes. Portanto, no programa, após converter a captura de tela em um fluxo, o servidor primeiro envia o tamanho do fluxo para notificar o cliente sobre o tamanho do fluxo. O cliente usa esse número para determinar se o fluxo foi recebido. for, ele será convertido e exibido.
Este programa e o OICQ anterior, feito por ele mesmo, usam o objeto de fluxo de memória TMemoryStream. Na verdade, esse objeto de fluxo é o mais comumente usado em programação. Ele pode melhorar os recursos de leitura e gravação de E/S e, se você quiser operar vários tipos diferentes de fluxos ao mesmo tempo e trocar dados entre si, use-o como. um "intermediário" É o melhor. Por exemplo, se você compactar ou descompactar um fluxo, primeiro crie um objeto TMemoryStream, copie outros dados nele e execute as operações correspondentes. Por funcionar diretamente na memória, a eficiência é muito alta. Às vezes você nem notará nenhum atraso.
Áreas para melhoria no programa: Claro, você pode adicionar uma unidade de compressão para compactar antes de enviar. Nota: Há também um truque aqui, que é compactar o BMP diretamente em vez de convertê-lo para JPG e depois compactá-lo. Experimentos mostraram que o tamanho de uma imagem no programa acima é de cerca de 40-50 KB. Se for processada usando o algoritmo de compressão LAH, terá apenas 8-12 KB, o que torna a transmissão mais rápida. Se quiser ir mais rápido, você pode usar este método: primeiro capture a primeira imagem e envie, depois comece a partir da segunda imagem e envie apenas imagens em áreas diferentes da anterior. Existe um programa externo chamado Administrador Remoto que usa esse método. Os dados testados são os seguintes: 100-500 quadros por segundo na rede local e 5-10 quadros por segundo na Internet quando a velocidade da rede é extremamente baixa. Essas digressões querem apenas ilustrar uma verdade: ao pensar em um problema, especialmente ao escrever um programa, especialmente um programa que parece muito complicado, não se envolve demais nele. . O programa está morto, o talento está vivo. Obviamente, estes só podem confiar no acúmulo de experiência. Mas a formação de bons hábitos desde o início pagará dividendos ao longo de sua vida!