Hablando de la aplicación del "flujo" en la programación Delphi
Chen Jingtao
¿Qué es una corriente? Stream, en pocas palabras, es una herramienta de procesamiento de datos abstracta basada en orientación a objetos. En la secuencia, se definen algunas operaciones básicas para procesar datos, como leer datos, escribir datos, etc. El programador realiza todas las operaciones en la secuencia sin preocuparse por la dirección real del flujo de los datos en el otro extremo de la secuencia. Los flujos no solo pueden procesar archivos, sino también memoria dinámica, datos de red y otras formas de datos. Si es muy competente en las operaciones de transmisión y utiliza la conveniencia de las transmisiones en su programa, la eficiencia de la escritura de programas mejorará enormemente.
A continuación, el autor utiliza cuatro ejemplos: cifrado de archivos EXE, tarjeta de felicitación electrónica, OICQ de fabricación propia y transmisión de pantalla de red para ilustrar el uso de "flujo" en la programación de Delphi. Algunas de las técnicas de estos ejemplos alguna vez fueron secretos de muchos programas y no estaban abiertas al público. Ahora todos pueden citar directamente el código de forma gratuita.
"Los edificios altos se elevan desde el suelo". Antes de analizar los ejemplos, primero comprendamos los conceptos y funciones básicos del flujo. Sólo después de comprender estos aspectos básicos podremos pasar al siguiente paso. Asegúrese de comprender estos métodos básicos cuidadosamente. Por supuesto, si ya estás familiarizado con ellos, puedes saltarte este paso.
1. Conceptos básicos y declaraciones de funciones de flujos en Delphi.
En Delphi, la clase base de todos los objetos de flujo es la clase TStream, que define las propiedades y métodos comunes de todos los flujos.
Las propiedades definidas en la clase TStream se presentan de la siguiente manera:
1. Tamaño: esta propiedad devuelve el tamaño de los datos en la secuencia en bytes.
2. Posición: este atributo controla la posición del puntero de acceso en el flujo.
Hay cuatro métodos virtuales definidos en Tstream:
1. Leer: este método lee datos de la transmisión. El prototipo de función es:
Función Leer(var Buffer;Count:Longint):Longint;virtual;abstracto;
El parámetro Buffer es el búfer que se coloca cuando se leen los datos. Count es el número de bytes de datos que se leerán. El valor de retorno de este método es el número real de bytes leídos, que puede ser menor o igual al valor especificado en. Contar.
2. Escribir: este método escribe datos en la secuencia. El prototipo de función es:
Función Escritura(var Buffer;Count:Longint):Longint;virtual;abstracto;
El parámetro Buffer es el búfer de los datos que se escribirán en la secuencia, Count es la longitud de los datos en bytes y el valor de retorno de este método es el número de bytes realmente escritos en la secuencia.
3. Buscar: este método implementa el movimiento del puntero de lectura en la secuencia. El prototipo de función es:
Búsqueda de función (Desplazamiento: Entero largo; Origen: Palabra): Entero largo; virtual; abstracto;
El parámetro Desplazamiento es el número de bytes de desplazamiento y el parámetro Origen señala el significado real de Desplazamiento. Sus valores posibles son los siguientes:
soFromBeginning: Offset es la posición del puntero desde el principio de los datos después del movimiento. En este momento, el desplazamiento debe ser mayor o igual a cero.
soFromCurrent: Offset es la posición relativa del puntero después del movimiento y el puntero actual.
soFromEnd:Offset es la posición del puntero desde el final de los datos después del movimiento. En este momento, el desplazamiento debe ser menor o igual a cero. El valor de retorno de este método es la posición del puntero después del movimiento.
4. Setsize: este método cambia el tamaño de los datos. El prototipo de función es:
Función Setsize(NewSize:Longint);virtual;
Además, también se definen varios métodos estáticos en la clase TStream:
1. ReadBuffer: la función de este método es leer datos desde la posición actual en la secuencia. El prototipo de función es:
Procedimiento ReadBuffer(var Buffer;Count:Longint);
La definición de parámetros es la misma que en la lectura anterior. Nota: Cuando la cantidad de bytes de datos leídos no es la misma que la cantidad de bytes que deben leerse, se generará una excepción EReadError.
2. WriteBuffer: la función de este método es escribir datos en la secuencia en la posición actual. El prototipo de función es:
Procedimiento WriteBuffer(var Buffer;Count:Longint);
La definición de parámetros es la misma que en Escritura anterior. Nota: Cuando la cantidad de bytes de datos escritos no es la misma que la cantidad de bytes que deben escribirse, se generará una excepción EWriteError.
3. CopyFrom: este método se utiliza para copiar flujos de datos de otros flujos. El prototipo de función es:
Función CopyFrom(Fuente:TStream;Count:Longint):Longint;
El parámetro Fuente es el flujo que proporciona datos y Recuento es el número de bytes de datos copiados. Cuando Count es mayor que 0, CopyFrom copia Count bytes de datos de la posición actual del parámetro Source; cuando Count es igual a 0, CopyFrom establece la propiedad Position del parámetro Source en 0 y luego copia todos los datos de Source; ;
TStream tiene otras clases derivadas, la más utilizada es la clase TFileStream. Para utilizar la clase TFileStream para acceder a archivos, primero debe crear una instancia. El comunicado es el siguiente:
constructor Crear(const Nombre de archivo:cadena;Modo:Palabra);
Nombre de archivo es el nombre del archivo (incluida la ruta) y el parámetro Modo es la forma de abrir el archivo, que incluye el modo de apertura del archivo y el modo de compartir. Sus posibles valores y significados son los siguientes:
Modo abierto:
fmCreate: crea un archivo con el nombre de archivo especificado o abre el archivo si ya existe.
fmOpenRead: abre el archivo especificado en modo de solo lectura
fmOpenWrite: abre el archivo especificado en modo de solo escritura
fmOpenReadWrite: abre el archivo especificado para escribir
Modo compartir:
fmShareCompat: el modo Compartir es compatible con FCB
fmShareExclusive: no permita que otros programas abran el archivo de ninguna manera
fmShareDenyWrite: no permita que otros programas abran el archivo para escribir
fmShareDenyRead: No permita que otros programas abran el archivo en modo lectura
fmShareDenyNone: Otros programas pueden abrir el archivo de cualquier forma
TStream también tiene una clase derivada, TMemoryStream, que se usa con mucha frecuencia en aplicaciones reales. Se llama flujo de memoria, lo que significa crear un objeto de flujo en la memoria. Sus métodos y funciones básicos son los mismos que los anteriores.
Bueno, con la base anterior establecida, podemos comenzar nuestro viaje de programación.
-------------------------------------------------- --------------------------
2. Aplicación práctica uno: uso de secuencias para crear cifradores de archivos EXE, paquetes, archivos autoextraíbles y programas de instalación
Primero hablemos de cómo crear un cifrado de archivos EXE.
El principio del cifrador de archivos EXE: cree dos archivos, uno se usa para agregar recursos al otro archivo EXE, que se denomina programa complementario. Otro archivo EXE que se agrega se llama archivo de encabezado. La función de este programa es leer los archivos agregados a él mismo. La estructura del archivo EXE en Windows es relativamente compleja y algunos programas también tienen sumas de verificación. Cuando descubren que han sido modificados, pensarán que están infectados por un virus y se negarán a ejecutarse. Entonces agregamos el archivo a nuestro propio programa para que no se cambie la estructura del archivo original. Primero escribamos una función de agregar. La función de esta función es agregar un archivo como una secuencia al final de otro archivo. La función es la siguiente:
Función Cjt_AddtoFile(SourceFile,TargetFile:string):Booleano;
var
Destino, Fuente: TFileStream;
TamañoMiArchivo:entero;
comenzar
intentar
Fuente:=TFileStream.Create(SourceFile,fmOpenRead o fmShareExclusive);
Destino:=TFileStream.Create(TargetFile,fmOpenWrite o fmShareExclusive);
intentar
Target.Seek(0,soFromEnd);//Agregar recursos al final
Destino.CopyFrom(Fuente,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calcule el tamaño del recurso y escríbalo al final del proceso auxiliar
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finalmente
Objetivo.Gratis;
Fuente.Gratis;
fin;
excepto
Resultado:=Falso;
Salida;
fin;
Resultado:=Verdadero;
fin;
Con la base anterior, deberíamos entender fácilmente esta función. El parámetro SourceFile es el archivo que se agregará y el parámetro TargetFile es el archivo de destino que se agregará. Por ejemplo, para agregar a.exe a b.exe: Cjt_AddtoFile('a.exe',b.exe'); Si la adición se realiza correctamente, devolverá Verdadero; de lo contrario, devolverá Falso.
Basado en la función anterior podemos escribir la función de lectura opuesta:
Función Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
var
Fuente: TFileStream;
Objetivo:TMemoryStream;
TamañoMiArchivo:entero;
comenzar
intentar
Objetivo:=TMemoryStream.Create;
Fuente:=TFileStream.Create(SourceFile,fmOpenRead o fmShareDenyNone);
intentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Leer el tamaño del recurso
Source.Seek(-MyFileSize,soFromEnd);//Ubique la ubicación del recurso
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//Eliminar recursos
Target.SaveToFile(TargetFile);//Guardar en archivo
finalmente
Objetivo.Gratis;
Fuente.Gratis;
fin;
excepto
Resultado:=falso;
Salida;
fin;
Resultado:=verdadero;
fin;
El parámetro SourceFile es el nombre del archivo al que se agregó el archivo y el parámetro TargetFile es el nombre del archivo de destino guardado después de extraer el archivo. Por ejemplo, Cjt_LoadFromFile('b.exe','a.txt'); saca el archivo en b.exe y lo guarda como a.txt. Si la extracción es exitosa, devuelve Verdadero; de lo contrario, devuelve Falso.
Abra Delphi, cree un nuevo proyecto y coloque un control de edición Edit1 y dos botones en la ventana: Botón1 y Botón2. La propiedad Título del botón está configurada en "Aceptar" y "Cancelar" respectivamente. Escribe código en el evento Click del Botón1:
var S: cadena;
comenzar
S:=ChangeFileExt(application.ExeName,'.Cjt');
si Edit1.Text='790617' entonces
comenzar
Cjt_LoadFromFile(Application.ExeName,S);
{Saque el archivo y guárdelo en la ruta actual y asígnele el nombre "archivo original.Cjt"}
Winexec(pchar(S),SW_Show);{Ejecutar "archivo original.Cjt"}
Aplicación.Terminar;{Salir del programa}
fin
demás
Application.MessageBox('¡La contraseña es incorrecta, vuelva a ingresarla!', 'La contraseña es incorrecta', MB_ICONERROR+MB_OK);
Compile este programa y cambie el nombre del archivo EXE a head.exe. Cree un nuevo archivo de texto head.rc con el contenido: head exefile head.exe, luego cópielo en el directorio BIN de Delphi, ejecute el comando DOS Brcc32.exe head.rc y se generará un archivo head.res. El archivo es Conservar primero los archivos de recursos que queremos.
Nuestro archivo de encabezado ha sido creado, creemos el programa complementario.
Cree un nuevo proyecto y coloque los siguientes controles: un Editar, un Opendialog y las propiedades de Título de los dos Button1 están configuradas en "Seleccionar archivo" y "Cifrado" respectivamente. Agregue una oración en el programa fuente: {$R head.res} y copie el archivo head.res al directorio actual del programa. De esta manera, el head.exe que acaba de compilarse se compila junto con el programa.
Escribe el código en el evento Cilck del Botón1:
si OpenDialog1.Execute entonces Edit1.Text:=OpenDialog1.FileName;
Escribe el código en el evento Cilck del Botón2:
var S: Cadena;
comenzar
S:=ExtractFilePath(Edit1.Texto);
si ExtractRes('exefile','head',S+'head.exe') entonces
si Cjt_AddtoFile(Edit1.Text,S+'head.exe') entonces
si EliminarArchivo(Editar1.Texto) entonces
si RenameFile(S+'head.exe',Edit1.Text) entonces
Application.MessageBox('¡Cifrado de archivos exitoso!','Mensaje',MB_ICONINFORMATION+MB_OK)
demás
comenzar
si FileExists(S+'head.exe') entonces DeleteFile(S+'head.exe');
Application.MessageBox('¡Error en el cifrado de archivos!','Mensaje',MB_ICONINFORMATION+MB_OK)
fin;
fin;
Entre ellos, ExtractRes es una función personalizada que se utiliza para extraer head.exe del archivo de recursos.
Función ExtractRes(ResType, ResName, ResNewName: Cadena):boolean;
var
Res: TResourceStream;
comenzar
intentar
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
intentar
Res.SavetoFile(ResNewName);
Resultado:=verdadero;
finalmente
Res.Libre;
fin;
excepto
Resultado:=falso;
fin;
fin;
Nota: Nuestra función anterior simplemente agrega un archivo al final de otro archivo. En aplicaciones reales, se puede cambiar para agregar varios archivos, siempre que la dirección de desplazamiento se defina de acuerdo con el tamaño y el número reales. Por ejemplo, un paquete de archivos agrega dos o más programas a un archivo de encabezado. Los principios de los instaladores y programas autoextraíbles son los mismos, pero con más compresión. Por ejemplo, podemos hacer referencia a una unidad LAH, comprimir la secuencia y luego agregarla, para que el archivo se vuelva más pequeño. Simplemente descomprímalo antes de leerlo. Además, el ejemplo del cifrado EXE en el artículo todavía tiene muchas imperfecciones. Por ejemplo, la contraseña está fijada en "790617", y después de sacar el EXE y ejecutarlo, debe eliminarse una vez que haya terminado de ejecutarse, etc. Los lectores pueden modificarlo ellos mismos.
-------------------------------------------------- -------------------
3. Aplicación práctica 2: uso de secuencias para crear tarjetas de felicitación electrónicas ejecutables
A menudo vemos algún software de producción de tarjetas de felicitación electrónicas que le permite seleccionar imágenes usted mismo y luego generará un archivo ejecutable EXE para usted. Al abrir la tarjeta de felicitación, se mostrará la imagen mientras se reproduce música. Ahora que hemos aprendido las operaciones de transmisión, también podemos crear una.
En el proceso de agregar imágenes, podemos usar directamente el Cjt_AddtoFile anterior, y lo que debemos hacer ahora es cómo leer y mostrar las imágenes. Podemos usar el Cjt_LoadFromFile anterior para leer la imagen primero, guardarla como un archivo y luego cargarla. Sin embargo, hay una forma más sencilla, que es leer directamente la secuencia del archivo y mostrarla con la poderosa herramienta de secuencia. , todo se vuelve simple.
Las imágenes más populares hoy en día son el formato BMP y el formato JPG. Ahora escribiremos funciones de lectura y visualización para estos dos tipos de imágenes.
Función Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
var
Fuente: TFileStream;
TamañoMiArchivo:entero;
comenzar
Fuente:=TFileStream.Create(SourceFile,fmOpenRead o fmShareDenyNone);
intentar
intentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Leer recursos
Source.Seek(-MyFileSize,soFromEnd);//Ubique la posición inicial del recurso
ImgBmp.Picture.Bitmap.LoadFromStream(Fuente);
finalmente
Fuente.Gratis;
fin;
excepto
Resultado:=Falso;
Salida;
fin;
Resultado:=Verdadero;
fin;
Lo anterior es una función para leer imágenes BMP y lo siguiente es una función para leer imágenes JPG. Debido a que se usa la unidad JPG, se debe agregar una oración: usa jpeg al programa.
Función Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Booleano;
var
Fuente: TFileStream;
TamañoMiArchivo:entero;
Mijpg: TJpegImage;
comenzar
intentar
Mijpg:= TJpegImage.Create;
Fuente:=TFileStream.Create(SourceFile,fmOpenRead o fmShareDenyNone);
intentar
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Fuente.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(Fuente);
JpgImg.Picture.Bitmap.Assign(Mijpg);
finalmente
Fuente.Gratis;
Mijpg.gratis;
fin;
excepto
Resultado:=falso;
Salida;
fin;
Resultado:=verdadero;
fin;
Con estas dos funciones podemos hacer un programa de lectura. Tomemos fotografías BMP como ejemplo:
Ejecute Delphi, cree un nuevo proyecto y coloque un control de visualización de imágenes Imagen1. Simplemente escriba la siguiente oración en el evento Crear de la ventana:
Cjt_BmpLoad(Imagen1,Aplicación.ExeName);
Este es el archivo de encabezado y luego usamos el método anterior para generar un archivo de recursos head.res.
Ahora podemos comenzar a crear nuestro programa complementario. El código completo es el siguiente:
unidad Unidad1;
interfaz
usos
Windows, Mensajes, SysUtils, Clases, Gráficos, Controles, Formularios, Cuadros de diálogo,
ExtCtrls, StdCtrls, ExtDlgs;
tipo
TForm1 = clase(TForm)
Editar1: TEditar;
Botón1: TBotón;
Botón2: TBotón;
OpenPictureDialog1: TOpenPictureDialog;
procedimiento FormCreate(Remitente: TObject);
procedimiento Button1Click(Remitente: TObject);
procedimiento Button2Click(Remitente: TObject);
privado
Función ExtractRes(ResType, ResName, ResNewName: Cadena):boolean;
Función Cjt_AddtoFile(SourceFile,TargetFile:string):Booleano;
{Declaraciones privadas}
público
{Declaraciones públicas}
fin;
var
Formulario1: TForm1;
implementación
{$R *.DFM}
Función TForm1.ExtractRes(ResType, ResName, ResNewName: Cadena):boolean;
var
Res: TResourceStream;
comenzar
intentar
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
intentar
Res.SavetoFile(ResNewName);
Resultado:=verdadero;
finalmente
Res.Libre;
fin;
excepto
Resultado:=falso;
fin;
fin;
Función TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Destino, Fuente: TFileStream;
TamañoMiArchivo:entero;
comenzar
intentar
Fuente:=TFileStream.Create(SourceFile,fmOpenRead o fmShareExclusive);
Destino:=TFileStream.Create(TargetFile,fmOpenWrite o fmShareExclusive);
intentar
Target.Seek(0,soFromEnd);//Agregar recursos al final
Destino.CopyFrom(Fuente,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Calcule el tamaño del recurso y escríbalo al final del proceso auxiliar
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finalmente
Objetivo.Gratis;
Fuente.Gratis;
fin;
excepto
Resultado:=Falso;
Salida;
fin;
Resultado:=Verdadero;
fin;
procedimiento TForm1.FormCreate(Remitente: TObject);
comenzar
Título:='Programa de demostración Bmp2Exe Autor: Chen Jingtao';
Editar1.Texto:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter: = GraphicFilter (TBitmap);
Button1.Caption:='Seleccionar imagen BMP';
Button2.Caption:='Generar EXE';
fin;
procedimiento TForm1.Button1Click (Remitente: TObject);
comenzar
si OpenPictureDialog1.Execute entonces
Edit1.Text:=OpenPictureDialog1.FileName;
fin;
procedimiento TForm1.Button2Click (Remitente: TObject);
var
Temperatura del cabezal: cadena;
comenzar
si no existe el archivo (Edit1.Text), entonces
comenzar
Application.MessageBox('¡El archivo de imagen BMP no existe, selecciónelo nuevamente!','Mensaje',MB_ICONINFORMATION+MB_OK)
Salida;
fin;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
si ExtractRes('exefile','head',HeadTemp) entonces
si Cjt_AddtoFile(Edit1.Text,HeadTemp) entonces
Application.MessageBox('¡El archivo EXE se generó correctamente!','Mensaje',MB_ICONINFORMATION+MB_OK)
demás
comenzar
si FileExists(HeadTemp) entonces DeleteFile(HeadTemp);
Application.MessageBox('¡Falló la generación del archivo EXE!','Mensaje',MB_ICONINFORMATION+MB_OK)
fin;
fin;
fin.
¿Qué tal? Es asombroso :) Haga que la interfaz del programa sea más hermosa y agregue algunas funciones, y encontrará que no es muy inferior a los programas que requieren registro.
-------------------------------------------------- --------------------------
Aplicación práctica tres: utilice transmisiones para crear su propio OICQ
OICQ es un software de comunicación en línea en tiempo real desarrollado por Shenzhen Tencent Company y tiene una gran base de usuarios en China. Sin embargo, OICQ debe estar conectado a Internet e iniciado sesión en el servidor de Tencent antes de poder usarse. Entonces podemos escribir uno nosotros mismos y usarlo en la red local.
OICQ utiliza el protocolo UDP, que es un protocolo sin conexión, es decir, las partes que se comunican pueden enviar información sin establecer una conexión, por lo que la eficiencia es relativamente alta. El control NMUDP de FastNEt que viene con Delphi es un control de datagramas de usuario del protocolo UDP. Sin embargo, cabe señalar que si utiliza este control, debe salir del programa antes de apagar la computadora, porque el control TNMXXX tiene un ERROR. El ThreadTimer utilizado por PowerSocket, la base de todos los controles de nm, utiliza una ventana oculta (clase TmrWindowClass) para solucionar fallos.
Qué salió mal:
Psock::TThreadTimer::WndProc(var mensaje:TMessage)
si msg.message=WM_TIMER entonces
Él mismo lo maneja
msj.resultado:=0
demás
msg.resultado:=DefWindowProc(0,....)
fin
El problema es que al llamar a DefWindowProc, el parámetro HWND transmitido es en realidad un 0 constante, por lo que, de hecho, DefWindowProc no puede funcionar. La llamada a cualquier mensaje de entrada devuelve 0, incluido WM_QUERYENDsession, por lo que Windows no puede salir. Debido a la llamada anormal de DefWindowProc, de hecho, excepto WM_TIMER, otros mensajes procesados por DefWindowProc no son válidos.
La solución está en PSock.pas
Dentro de TThreadTimer.Wndproc
Resultado := DefWindowProc( 0, Mensaje, WPARAM, LPARAM );
Cambiar a:
Resultado := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
Las primeras versiones de bajo nivel de OICQ también tenían este problema. Si OICQ no estaba apagado, la pantalla parpadeaba por un momento y luego regresaba cuando se apagaba la computadora.
Bien, sin más preámbulos, escribamos nuestro OICQ. Este es en realidad un ejemplo que viene con Delphi :)
Cree un nuevo proyecto, arrastre un control NMUDP desde el panel FASTNET a la ventana y luego coloque tres EDITS llamados Editip, EditPort, EditMyTxt, tres botones BtSend, BtClear, BtSave, un MEMOMemoReceive, un SaveDialog y una barra de estado. Cuando el usuario hace clic en BtSend, se crea un objeto de flujo de memoria, la información de texto que se enviará se escribe en el flujo de memoria y luego NMUDP envía el flujo. Cuando NMUDP recibe datos, se activa su evento DataReceived. Aquí convertimos el flujo recibido en información de caracteres y luego lo mostramos.
Nota: Recuerde liberar (Libre) todos los objetos de la secuencia después de crearlos y usarlos. De hecho, su destructor debe ser Destruir. Sin embargo, si la creación de la secuencia falla, usar Destroy generará una excepción. Primero comprobará si la transmisión no se establece correctamente y se publicará solo si se establece, por lo que es más seguro utilizarlo de forma gratuita.
En este programa utilizamos el control NMUDP, que tiene varias propiedades importantes. RemoteHost representa la IP o el nombre de la computadora remota, y LocalPort es el puerto local, que monitorea principalmente si hay datos entrantes. RemotePort es un puerto remoto y los datos se envían a través de este puerto al enviar datos. Comprenderlos ya puede comprender nuestro programa.
El código completo es el siguiente:
unidad Unidad1;
interfaz
usos
Windows, Mensajes, SysUtils, Clases, Gráficos, Controles, Formularios, Diálogos, StdCtrls, ComCtrls, NMUDP;
tipo
TForm1 = clase(TForm)
NMUDP1: TNMUDP;
EditarIP: TEditar;
Puerto de edición: TEdit;
EditMyTxt: TEdit;
Recibir notas: TMemo;
BtEnviar: TButton;
BtClear: Botón T;
BtGuardar: TBotón;
Barra de estado1: TStatusBar;
SaveDialog1: TSaveDialog;
procedimiento BtSendClick(Remitente: TObject);
procedimiento NMUDP1DataReceived(Remitente: TComponent; NumberBytes: Entero;
DesdeIP: Cadena; Puerto: Entero);
procedimiento NMUDP1InvalidHost(var manejada: booleana);
procedimiento NMUDP1DataSend(Remitente: TObject);
procedimiento FormCreate(Remitente: TObject);
procedimiento BtClearClick(Remitente: TObject);
procedimiento BtSaveClick(Remitente: TObject);
procedimiento EditMyTxtKeyPress(Remitente: TObject; var Clave: Char);
privado
{Declaraciones privadas}
público
{Declaraciones públicas}
fin;
var
Formulario1: TForm1;
implementación
{$R *.DFM}
procedimiento TForm1.BtSendClick(Remitente: TObject);
var
Mi flujo: TMemoryStream;
MySendTxt: Cadena;
Importar,icode:entero;
Comenzar
Val(EditPort.Text,Iportar,icode);
si icode<>0 entonces
comenzar
Application.MessageBox('El puerto debe ser un número, ¡vuelva a ingresar!','Message',MB_ICONINFORMATION+MB_OK);
Salida;
fin;
NMUDP1.RemoteHost:= EditIP.Text; {host remoto}
NMUDP1.LocalPort:=Iportar; {puerto local}
NMUDP1.RemotePort := Iport; {puerto remoto}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create; {Crear secuencia}
intentar
MyStream.Write(MySendTxt[1], Longitud(EditMyTxt.Text));{Escribir datos}
NMUDP1.SendStream(MyStream); {Enviar secuencia}
finalmente
MyStream.Free; {transmisión de lanzamiento}
fin;
fin;
procedimiento TForm1.NMUDP1DataReceived(Remitente: TComponent;
NumberBytes: Entero; DesdeIP: Cadena; Puerto: Entero);
var
Mi flujo: TMemoryStream;
MyReciveTxt: Cadena;
comenzar
MyStream := TMemoryStream.Create; {Crear secuencia}
intentar
NMUDP1.ReadStream(MyStream);{Recibir flujo}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes es el número de bytes recibidos}
MyStream.Read(MyReciveTxt[1],NumberBytes);{leer datos}
MemoReceive.Lines.Add('Información recibida del host '+FromIP+':'+MyReciveTxt);
finalmente
MyStream.Free; {transmisión de lanzamiento}
fin;
fin;
procedimiento TForm1.NMUDP1InvalidHost(var manejada: booleana);
comenzar
Application.MessageBox('La dirección IP de la otra parte es incorrecta, ¡vuelva a ingresarla!','Message',MB_ICONINFORMATION+MB_OK);
fin;
procedimiento TForm1.NMUDP1DataSend(Remitente: TObject);
comenzar
StatusBar1.SimpleText:='¡Mensaje enviado correctamente!';
fin;
procedimiento TForm1.FormCreate(Remitente: TObject);
comenzar
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='Enviar';
BtClear.Caption:='Borrar historial de chat';
BtSave.Caption:='Guardar historial de chat';
MemoReceive.ScrollBars:=ssAmbos;
MemoReceive.Borrar;
EditMyTxt.Text:='Ingrese la información aquí y haga clic en Enviar.';
StatusBar1.SimplePanel:=verdadero;
fin;
procedimiento TForm1.BtClearClick(Remitente: TObject);
comenzar
MemoReceive.Borrar;
fin;
procedimiento TForm1.BtSaveClick(Remitente: TObject);
comenzar
si SaveDialog1.Execute entonces MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
fin;
procedimiento TForm1.EditMyTxtKeyPress(Remitente: TObject; var Clave: Char);
comenzar
si Clave=#13 entonces BtSend.Click;
fin;
fin.
El programa anterior ciertamente está muy por detrás de OICQ, porque OICQ utiliza el método de comunicación Socket5. Cuando se conecta, primero recupera la información del amigo y el estado de conexión del servidor. Cuando se agota el tiempo de envío, primero guardará la información en el servidor, esperará a que la otra parte se conecte la próxima vez, luego la enviará y luego la eliminará. copia de seguridad del servidor. Puede mejorar este programa basándose en los conceptos que aprendió anteriormente. Por ejemplo, agregue un control NMUDP para administrar el estado en línea. La información enviada primero se convierte en código ASCII para operación AND y luego agrega una información de encabezado. Al recibir la información, ya sea que el encabezado de la información sea correcto o no, la información se descifrará y se mostrará solo si es correcta, mejorando así la seguridad y la confidencialidad.
Además, otra gran ventaja del protocolo UDP es que puede transmitir, lo que significa que cualquier persona en el mismo segmento de red puede recibir información sin especificar una dirección IP específica. Los segmentos de red generalmente se dividen en tres categorías: A, B y C.
1~126.XXX.XXX.XXX (red Clase A): la dirección de transmisión es XXX.255.255.255
128~191.XXX.XXX.XXX (red Clase B): la dirección de transmisión es XXX.XXX.255.255
192~254.XXX.XXX.XXX (red de categoría C): la dirección de transmisión es XXX.XXX.XXX.255
Por ejemplo, si tres computadoras son 192.168.0.1, 192.168.0.10 y 192.168.0.18, al enviar información, solo necesita especificar la dirección IP 192.168.0.255 para lograr la transmisión. A continuación se muestra una función que convierte IP en IP de transmisión. Úsela para mejorar su propio OICQ^-^.
Función Trun_ip(S:cadena):cadena;
var s1,s2,s3,ss,sss,Cabeza: cadena;
n,m:entero;
comenzar
sss:=S;
n:=pos('.',s);
s1:=copiar(s,1,n);
m:=longitud(s1);
eliminar(s,1,m);
Cabeza:=copia(s1,1,(longitud(s1)-1));
n:=pos('.',s);
s2:=copiar(s,1,n);
m:=longitud(s2);
eliminar(s,1,m);
n:=pos('.',s);
s3:=copiar(s,1,n);
m:=longitud(s3);
eliminar(s,1,m);
ss:=sss;
si strtoint(Head) en [1..126] entonces ss:=s1+'255.255.255'; //1~126.255.255.255 (red Clase A)
si strtoint(Head) en [128..191] entonces ss:=s1+s2+'255.255';//128~191.XXX.255.255 (red Clase B)
si strtoint(Head) en [192..254] entonces ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255 (Categoría de red)
Resultado:=ss;
fin;
-------------------------------------------------- --------------------------
5. Aplicación práctica 4: uso de transmisiones para transmitir imágenes de pantalla a través de la red
Debería haber visto muchos programas de administración de redes. Una de las funciones de dichos programas es monitorear la pantalla de una computadora remota. De hecho, esto también se logra mediante operaciones de flujo. A continuación damos un ejemplo. Este ejemplo se divide en dos programas, uno es el servidor y el otro es el cliente. Una vez compilado el programa, se puede utilizar directamente en una sola máquina, una red local o Internet. En el programa se han dado los comentarios correspondientes. Haremos un análisis detallado más adelante.
Cree un nuevo proyecto y arrastre un control ServerSocket a la ventana en el panel de Internet. Este control se utiliza principalmente para monitorear el cliente y establecer conexiones y comunicaciones con el cliente. Después de configurar el puerto de escucha, llame al método Open o Active:=True para comenzar a trabajar. Nota: A diferencia del NMUDP anterior, una vez que el Socket comienza a escuchar, su puerto no se puede cambiar. Si desea cambiarlo, primero debe llamar a Cerrar o configurar Activo en Falso; de lo contrario, se producirá una excepción. Además, si el puerto ya está abierto, este puerto ya no se podrá utilizar. Por lo tanto, no puede volver a ejecutar el programa antes de que salga; de lo contrario, se producirá una excepción, es decir, aparecerá una ventana de error. En aplicaciones prácticas, los errores se pueden evitar determinando si el programa se ha ejecutado y saliendo si ya se está ejecutando.
Cuando se pasan datos desde el cliente, se activará el evento ServerSocket1ClientRead y podremos procesar los datos recibidos aquí. En este programa recibe principalmente la información de caracteres enviada por el cliente y realiza las operaciones correspondientes según el acuerdo previo.
El código completo del programa es el siguiente:
unidad Unidad1; {programa de servidor}
interfaz
usos
Windows, Mensajes, SysUtils, Clases, Gráficos, Controles, Formularios, Diálogos, JPEG, ExtCtrls, ScktComp;
tipo
TForm1 = clase(TForm)
ServerSocket1: TServerSocket;
procedimiento ServerSocket1ClientRead(Remitente: TObject;Socket: TCustomWinSocket);
procedimiento FormCreate(Remitente: TObject);
procedimiento FormClose(Remitente: TObject; var Acción: TCloseAction);
privado
procedimiento Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
{Función de captura de pantalla personalizada, DrawCur indica si se debe capturar la imagen del mouse}
{Declaraciones privadas}
público
{Declaraciones públicas}
fin;
var
Formulario1: TForm1;
MyStream: TMemorystream;{objeto de flujo de memoria}
implementación
{$R *.DFM}
procedimiento TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
var
Cursorx, Cursorio: entero;
CC:HDC;
Mycan: Tcanvas;
R: TRecto;
DrawPos: PuntoT;
MiCursor: TIcon;
hld:hwnd;
Hilo: dword;
pf: punto t;
pIconInfo: TIconInfo;
comenzar
Mibmp := Tbitmap.Create; {Crear BMPMAP}
Mycan := TCanvas.Create; {captura de pantalla}
corriente continua := GetWindowDC(0);
intentar
Mycan.Handle := dc;
R := Rect(0, 0, pantalla.Ancho, pantalla.Alto);
Mybmp.Width := R.Derecha;
Mybmp.Height := R.Inferior;
Mybmp.Canvas.CopyRect(R, Mycan, R);
finalmente
liberarDC(0, DC);
fin;
Mycan.Handle := 0;
Mycan.Gratis;
si DrawCur entonces {Dibujar la imagen del mouse}
comenzar
ObtenerCursorPos(DrawPos);
MiCursor := TIcon.Create;
getcursorpos(mp);
hld := VentanaDesdePunto(mp);
Threadld := GetWindowThreadProcessId(hld, nil);
AdjuntarThreadInput(GetCurrentThreadId, Threadld, Verdadero);
MiCursor.Handle := Getcursor();
AdjuntarThreadInput(GetCurrentThreadId, threadld, False);
GetIconInfo(Micursor.Handle, pIconInfo);
cursorx := DrawPos.x - ronda(pIconInfo.xHotspot);
superficial := DrawPos.y - round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, cursory, MyCursor);
DeleteObject(pIconInfo.hbmColor);{GetIconInfo crea dos objetos de mapa de bits cuando se usan. Estos dos objetos deben liberarse manualmente}
DeleteObject(pIconInfo.hbmMask); {De lo contrario, después de llamarlo, creará un mapa de bits y varias llamadas generarán varios hasta que se agoten los recursos}
Mycursor.ReleaseHandle; {Liberar memoria de matriz}
MyCursor.Free; {suelte el puntero del mouse}
fin;
fin;
procedimiento TForm1.FormCreate(Remitente: TObject);
comenzar
ServerSocket1.Puerto: = 3000;
ServerSocket1.Open; {El socket comienza a escuchar}
fin;
procedimiento TForm1.FormClose(Remitente: TObject; var Acción: TCloseAction);
comenzar
si ServerSocket1.Active entonces ServerSocket1.Close;
fin;
procedimiento TForm1.ServerSocket1ClientRead(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
S, S1: cuerda;
MiBmp: TBitmap;
Mijpg: TJpegimage;
comenzar
S := Socket.ReceiveText;
si S = 'cap' entonces {El cliente emite un comando de captura de pantalla}
comenzar
intentar
MyStream := TMemorystream.Create;{Crear flujo de memoria}
MiBmp := TBitmap.Create;
Mijpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True significa tomar la imagen del mouse}
Myjpg.Assign(MyBmp); {Convertir imágenes BMP a formato JPG para facilitar su transmisión en Internet}
Myjpg.CompressionQuality := 10; {Configuración del porcentaje de compresión del archivo JPG, cuanto mayor sea el número, más clara será la imagen, pero mayores serán los datos}
Myjpg.SaveToStream(MyStream); {Escribir imagen JPG para transmitir}
Mijpg.gratis;
MyStream.Position := 0;{Nota: esta oración debe agregarse}
s1 := inttostr(MyStream.size);{tamaño de la secuencia}
Socket.sendtext(s1); {enviar tamaño de flujo}
finalmente
MiBmp.gratis;
fin;
fin;
si s = 'listo' entonces {El cliente está listo para recibir imágenes}
comenzar
MiSecuencia.Posición:= 0;
Socket.SendStream(MyStream); {Enviar la transmisión}
fin;
fin;
fin.
Lo anterior es el servidor, escribamos el programa cliente a continuación. Cree un nuevo proyecto y agregue el control Socket ClientSocket, el control de visualización de imágenes Image, un Panel, un Edit, dos Buttons y un control de barra de estado StatusBar1. Nota: Coloque Edit1 y dos botones encima del Panel1. Los atributos de ClientSocket son similares a los de ServerSocket, pero hay un atributo de Dirección adicional, que indica la dirección IP del servidor que se va a conectar. Después de completar la dirección IP, haga clic en "Conectar" para establecer una conexión con el programa del servidor. Si tiene éxito, puede comenzar la comunicación. Al hacer clic en "Captura de pantalla" se enviarán caracteres al servidor. Debido a que el programa utiliza unidades de imagen JPEG, se debe agregar Jpeg a Usos.
El código completo es el siguiente:
unidad Unidad2{cliente};
interfaz
usos
Windows, Mensajes, SysUtils, Clases, Gráficos, Controles, Formularios, Diálogos, StdCtrls, ScktComp, ExtCtrls, Jpeg, ComCtrls;
tipo
TForm1 = clase(TForm)
ClientSocket1: TClientSocket;
Imagen 1: TImagen;
Barra de estado1: TStatusBar;
Panel1: Panel T;
Editar1: TEditar;
Botón1: TBotón;
Botón2: TBotón;
procedimiento Button1Click(Remitente: TObject);
procedimiento ClientSocket1Connect(Remitente: TObject;
Zócalo: TCustomWinSocket);
procedimiento Button2Click(Remitente: TObject);
procedimiento ClientSocket1Error(Remitente: TObject; Socket: TCustomWinSocket;
ErrorEvent: TErrorEvent; var ErrorCode: entero);
procedimiento ClientSocket1Read(Remitente: TObject; Socket: TCustomWinSocket);
procedimiento FormCreate(Remitente: TObject);
procedimiento FormClose(Remitente: TObject; var Acción: TCloseAction);
procedimiento ClientSocket1Disconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
privado
{Declaraciones privadas}
público
{Declaraciones públicas}
fin;
var
Formulario1: TForm1;
MiTamaño: Entero largo;
MyStream: TMemorystream;{objeto de flujo de memoria}
implementación
{$R *.DFM}
procedimiento TForm1.FormCreate(Remitente: TObject);
comenzar
{-------- Lo siguiente es establecer las propiedades de apariencia del control de ventana------------- }
{Nota: Coloque el Botón1, el Botón2 y Editar1 encima del Panel1}
Editar1.Texto: = '127.0.0.1';
Button1.Caption := 'Conectar al host';
Button2.Caption := 'Captura de pantalla';
Botón2.Enabled:= falso;
Panel1.Align := alTop;
Imagen1.Align := alClient;
Imagen1.Estirar: = Verdadero;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel: = Verdadero;
{------------------------------------------------- ---------}
MyStream := TMemorystream.Create; {Crear un objeto de flujo de memoria}
MiTamaño := 0; {inicialización}
fin;
procedimiento TForm1.Button1Click (Remitente: TObject);
comenzar
si no es ClientSocket1.Active entonces
comenzar
ClientSocket1.Address := Edit1.Text; {dirección IP remota}
ClientSocket1.Port := 3000; {Puerto de socket}
ClientSocket1.Open; {Establecer conexión}
fin;
fin;
procedimiento TForm1.Button2Click (Remitente: TObject);
comenzar
Clientsocket1.Socket.SendText('cap'); {Enviar un comando para notificar al servidor que capture la imagen de la pantalla}
Botón2.Enabled:= Falso;
fin;
procedimiento TForm1.ClientSocket1Connect(Remitente: TObject;
Zócalo: TCustomWinSocket);
comenzar
StatusBar1.SimpleText := 'Con host' + ClientSocket1.Address + '¡Conexión establecida exitosamente!';
Botón2.Enabled:= Verdadero;
fin;
procedimiento TForm1.ClientSocket1Error(Remitente: TObject;
Conector: TCustomWinSocket; ErrorEvent: TErrorEvent;
varErrorCode: Entero);
comenzar
Código de error: = 0; {No abrir ventana de error}
StatusBar1.SimpleText := 'No se puede conectar al host' + ClientSocket1.Address + '¡Conexión establecida!';
fin;
procedimiento TForm1.ClientSocket1Disconnect(Remitente: TObject;
Zócalo: TCustomWinSocket);
comenzar
StatusBar1.SimpleText := 'Con host' + ClientSocket1.Address + '¡Desconectar!';
Botón2.Enabled:= Falso;
fin;
procedimiento TForm1.ClientSocket1Read(Remitente: TObject;
Zócalo: TCustomWinSocket);
var
MyBuffer: matriz [0..10000] de bytes; {Establecer búfer de recepción}
MyReceviceLength: entero;
S: cuerda;
MiBmp: TBitmap;
MiJpg: TJpegimage;
comenzar
StatusBar1.SimpleText := 'Recibiendo datos...';
si MySize = 0 entonces {MySize es el número de bytes enviados por el servidor. Si es 0, significa que la recepción de la imagen aún no ha comenzado}
comenzar
S := Socket.ReceiveText;
MySize := Strtoint(S); {Establecer el número de bytes que se recibirán}
Clientsocket1.Socket.SendText('ready'); {Enviar un comando para notificar al servidor que comience a enviar imágenes}
fin
demás
comenzar {La siguiente es la parte de recepción de datos de imagen}
MyReceviceLength := socket.ReceiveLength; {leer longitud del paquete}
StatusBar1.SimpleText := 'Recibiendo datos, el tamaño de los datos es:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {Recibir el paquete de datos y leerlo en el búfer}
MyStream.Write(MyBuffer, MyReceviceLength); {Escribir datos en la secuencia}
si MyStream.Size >= MySize entonces {Si la longitud del flujo es mayor que el número de bytes a recibir, la recepción se completa}
comenzar
Mi flujo.Posición: = 0;
MiBmp := tbitmap.Create;
MiJpg := tjpegimage.Create;
intentar
MyJpg.LoadFromStream(MyStream); {Leer los datos de la secuencia en el objeto de imagen JPG}
MyBmp.Assign(MyJpg); {Convertir JPG a BMP}
StatusBar1.SimpleText := 'Mostrando imagen';
Image1.Picture.Bitmap.Assign(MyBmp); {Asignado al elemento image1}
finalmente {El siguiente es el trabajo de limpieza}
MiBmp.gratis;
MiJpg.gratis;
Botón2.Enabled:= verdadero;
{Socket.SendText('cap');Agregue esta oración para capturar la pantalla continuamente}
Mi flujo.Borrar;
MiTamaño := 0;
fin;
fin;
fin;
fin;
procedimiento TForm1.FormClose(Remitente: TObject; var Acción: TCloseAction);
comenzar
MyStream.Free; {Liberar el objeto de flujo de memoria}
si ClientSocket1.Active entonces ClientSocket1.Close; {Cerrar conexión de socket}
fin;
fin.
Principio del programa: ejecute el servidor para comenzar a escuchar, luego ejecute el cliente, ingrese la dirección IP del servidor para establecer una conexión y luego envíe un carácter para notificar al servidor que capture la pantalla. El servidor llama a la función personalizada Cjt_GetScreen para capturar la pantalla y guardarla como BMP, convertir el BMP a JPG, escribir el JPG en la secuencia de memoria y luego enviar la secuencia al cliente. Después de recibir la transmisión, el cliente realiza la operación opuesta, convirtiendo la transmisión a JPG y luego a BMP y luego mostrándola.
Nota: Debido a las limitaciones de Socket, no se pueden enviar datos demasiado grandes a la vez, solo se pueden enviar varias veces. Por lo tanto, en el programa, después de convertir la captura de pantalla en una transmisión, el servidor primero envía el tamaño de la transmisión para notificar al cliente qué tan grande es la transmisión. El cliente usa este número para determinar si la transmisión se ha recibido. Si es así, se convertirá y se mostrará.
Este programa y el OICQ anterior de fabricación propia utilizan el objeto de flujo de memoria TMemoryStream. De hecho, este objeto de flujo es el más utilizado en programación. Puede mejorar las capacidades de lectura y escritura de E/S, y si desea operar varios tipos diferentes de flujos al mismo tiempo e intercambiar datos entre sí, utilícelo como. un "intermediario" Es lo mejor. Por ejemplo, si comprime o descomprime una secuencia, primero crea un objeto TMemoryStream, luego copia otros datos en él y luego realiza las operaciones correspondientes. Como funciona directamente en la memoria, la eficiencia es muy alta. A veces ni siquiera notarás ningún retraso.
Áreas de mejora en el programa: Por supuesto, puedes agregar una unidad de compresión para comprimir antes de enviar y luego enviar. Nota: También hay un truco aquí, que consiste en comprimir BMP directamente en lugar de convertirlo a JPG y luego comprimirlo. Los experimentos han demostrado que el tamaño de una imagen en el programa anterior es de aproximadamente 40 a 50 KB. Si se procesa utilizando el algoritmo de compresión LAH, solo será de 8 a 12 KB, lo que hace que la transmisión sea más rápida. Si quieres ir más rápido, puedes utilizar este método: primero captura la primera imagen y envíala, y luego comienza desde la segunda imagen y envía solo imágenes en áreas diferentes a la anterior. Existe un programa externo llamado Remote Administrator que utiliza este método. Los datos que probaron son los siguientes: 100-500 cuadros por segundo en la red local y 5-10 cuadros por segundo en Internet cuando la velocidad de la red es extremadamente baja. Estas digresiones solo quieren ilustrar una verdad: cuando piense en un problema, especialmente cuando escriba un programa, especialmente un programa que parece muy complicado, no se deje atrapar por él. A veces es mejor pensar en ello desde otro ángulo. . El programa está muerto, el talento está vivo. Por supuesto, esto sólo puede depender de la acumulación de experiencia. ¡Pero formar buenos hábitos desde el principio dará sus frutos a lo largo de su vida!