Говоря о применении «потока» в программировании на Delphi
Чэнь Цзинтао
Что такое поток? Проще говоря, Stream — это абстрактный инструмент обработки данных, основанный на объектно-ориентированном подходе. В потоке определяются некоторые основные операции обработки данных, такие как чтение данных, запись данных и т. д. Программист выполняет все операции над потоком, не заботясь о фактическом направлении потока данных на другом конце потока. Потоки могут обрабатывать не только файлы, но и динамическую память, сетевые данные и другие формы данных. Если вы хорошо разбираетесь в потоковых операциях и используете удобство потоков в своей программе, эффективность написания программ значительно повысится.
Ниже автор использует четыре примера: шифратор EXE-файлов, электронную поздравительную открытку, самодельный OICQ и передачу сетевого экрана, чтобы проиллюстрировать использование «потока» в программировании на Delphi. Некоторые из методов в этих примерах когда-то были секретами многих программ и не были общедоступными. Теперь каждый может бесплатно процитировать код.
«Высокие здания поднимаются из земли». Прежде чем разбирать примеры, давайте сначала разберемся в основных понятиях и функциях потока. Только после понимания этих основных вещей можно переходить к следующему шагу. Пожалуйста, внимательно изучите эти основные методы. Конечно, если вы с ними уже знакомы, этот шаг можно пропустить.
1. Основные понятия и объявления функций потоков в Delphi
В Delphi базовым классом всех объектов потока является класс TStream, который определяет общие свойства и методы всех потоков.
Свойства, определенные в классе TStream, представлены следующим образом:
1. Размер. Это свойство возвращает размер данных в потоке в байтах.
2. Позиция: этот атрибут управляет положением указателя доступа в потоке.
В Tstream определены четыре виртуальных метода:
1. Чтение. Этот метод считывает данные из потока. Прототип функции:
Функция Read(var Buffer;Count:Longint):Longint;виртуальный;абстрактный;
Параметр Buffer — это буфер, помещаемый при чтении данных. Count — это количество байтов данных, которые необходимо прочитать. Возвращаемое значение этого метода — это фактическое количество прочитанных байтов, которое может быть меньше или равно значению, указанному в Count. .
2. Запись. Этот метод записывает данные в поток. Прототип функции:
Функция Write(var Buffer;Count:Longint):Longint;виртуальный;абстрактный;
Параметр Buffer — это буфер данных, которые будут записаны в поток, Count — длина данных в байтах, а возвращаемое значение этого метода — это количество байтов, фактически записанных в поток.
3. Поиск. Этот метод реализует перемещение указателя чтения в потоке. Прототип функции:
Функция Seek(Смещение:Longint;Origin:Word):Longint;виртуальный;абстрактный;
Параметр Offset — это количество байтов смещения, а параметр Origin указывает фактическое значение Offset. Его возможные значения следующие:
soFromBeginning:Offset — это позиция указателя от начала данных после перемещения. В это время Offset должен быть больше или равен нулю.
soFromCurrent:Offset — это относительное положение указателя после перемещения и текущего указателя.
soFromEnd:Offset — это позиция указателя от конца данных после перемещения. В это время Offset должен быть меньше или равен нулю. Возвращаемое значение этого метода — это позиция указателя после перемещения.
4. Setsize: этот метод изменяет размер данных. Прототип функции:
Функция Setsize(NewSize:Longint);virtual;
Кроме того, в классе TStream также определено несколько статических методов:
1. ReadBuffer: функция этого метода — чтение данных из текущей позиции в потоке. Прототип функции:
PROcedure ReadBuffer(var Buffer;Count:Longint);
Определение параметров такое же, как описано выше. Примечание. Если количество прочитанных байтов данных не совпадает с количеством байтов, которые необходимо прочитать, будет сгенерировано исключение EReadError.
2. WriteBuffer: функция этого метода — запись данных в поток в текущей позиции. Прототип функции:
Процедура WriteBuffer(var Buffer;Count:Longint);
Определение параметров такое же, как и в Write выше. Примечание. Если количество записанных байтов данных не совпадает с количеством байтов, которые необходимо записать, будет сгенерировано исключение EWriteError.
3. CopyFrom: этот метод используется для копирования потоков данных из других потоков. Прототип функции:
Функция CopyFrom(Source:TStream;Count:Longint):Longint;
Параметр Source — это поток, предоставляющий данные, а Count — это количество скопированных байтов данных. Если значение Count больше 0, CopyFrom копирует байты данных Count из текущей позиции параметра Source; когда Count равно 0, CopyFrom устанавливает для свойства Position параметра Source значение 0, а затем копирует все данные источника; ;
У TStream есть и другие производные классы, наиболее часто используемым из которых является класс TFileStream. Чтобы использовать класс TFileStream для доступа к файлам, необходимо сначала создать экземпляр. Заявление заключается в следующем:
конструктор Create(const Filename:string;Mode:Word);
Имя файла — это имя файла (включая путь), а параметр Mode — это способ открытия файла, который включает режим открытия файла и режим совместного использования. Его возможные значения и значения следующие:
Открытый режим:
fmCreate: создайте файл с указанным именем или откройте файл, если он уже существует.
fmOpenRead: открыть указанный файл в режиме только для чтения.
fmOpenWrite: открыть указанный файл в режиме только для записи.
fmOpenReadWrite: открыть указанный файл для записи.
Режим обмена:
fmShareCompat: режим общего доступа совместим с FCB.
fmShareExclusive: Не разрешать другим программам каким-либо образом открывать файл.
fmShareDenyWrite: не разрешать другим программам открывать файл для записи.
fmShareDenyRead: не разрешать другим программам открывать файл в режиме чтения.
fmShareDenyNone: другие программы могут открыть файл любым способом.
У TStream также есть производный класс TMemoryStream, который очень часто используется в реальных приложениях. Это называется потоком памяти, что означает создание объекта потока в памяти. Его основные методы и функции такие же, как указано выше.
Что ж, имея вышеуказанную основу, мы можем начать наше путешествие по программированию.
-------------------------------------------------- --------------------------
2. Первое практическое применение: использование потоков для создания шифраторов EXE-файлов, пакетов, самораспаковывающихся файлов и программ установки.
Давайте сначала поговорим о том, как сделать шифратор EXE-файлов.
Принцип шифрования файлов EXE: создайте два файла, один из которых используется для добавления ресурсов в другой файл EXE, который называется программой-надстройкой. Другой добавляемый EXE-файл называется файлом заголовка. Функция этой программы - читать добавленные к себе файлы. Структура EXE-файла в Windows относительно сложна, и некоторые программы также имеют контрольные суммы. Когда они обнаружат, что они были изменены, они подумают, что заражены вирусом, и откажутся выполняться. Поэтому мы добавляем файл в нашу собственную программу, чтобы исходная структура файла не была изменена. Давайте сначала напишем функцию добавления. Функция этой функции — добавить файл в виде потока в конец другого файла. Функция следующая:
Функция Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
вар
Цель, Источник: TFileStream;
MyFileSize: целое число;
начинать
пытаться
Источник:=TFileStream.Create(SourceFile,fmOpenRead или fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite или fmShareExclusive);
пытаться
Target.Seek(0,soFromEnd);//Добавляем ресурсы в конец
Target.CopyFrom(Источник,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Рассчитываем размер ресурса и записываем его в конец вспомогательного процесса
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
окончательно
Цель.Бесплатно;
Источник.Бесплатно;
конец;
кроме
Результат: = Ложь;
Выход;
конец;
Результат:=Истина;
конец;
Имея вышеизложенное основание, мы должны легко понять эту функцию. Параметр SourceFile — это добавляемый файл, а параметр TargetFile — добавляемый целевой файл. Например, чтобы добавить файл a.exe в файл b.exe: Cjt_AddtoFile('a.exe',b.exe'); Если добавление прошло успешно, оно вернет значение True, в противном случае — значение False.
На основе приведенной выше функции мы можем написать противоположную функцию чтения:
Функция Cjt_LoadFromFile(SourceFile,TargetFile:string):Boolean;
вар
Источник: TFileStream;
Цель:TMemoryStream;
MyFileSize: целое число;
начинать
пытаться
Цель:=TMemoryStream.Create;
Источник:=TFileStream.Create(SourceFile,fmOpenRead или fmShareDenyNone);
пытаться
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Читать размер ресурса
Source.Seek(-MyFileSize,soFromEnd);//Определение местоположения ресурса
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//Удалить ресурсы
Target.SaveToFile(TargetFile);//Сохранить в файл
окончательно
Цель.Бесплатно;
Источник.Бесплатно;
конец;
кроме
Результат: = ложь;
Выход;
конец;
Результат:=истина;
конец;
Параметр SourceFile — это имя файла, в который файл был добавлен, а параметр TargetFile — это имя целевого файла, сохраненного после извлечения файла. Например, Cjt_LoadFromFile('b.exe','a.txt'); извлекает файл из b.exe и сохраняет его как a.txt. Если извлечение прошло успешно, оно возвращает True, в противном случае — False.
Откройте Delphi, создайте новый проект и поместите в окно элемент управления Edit1 и две кнопки: Button1 и Button2. Свойству кнопки Caption присвоено значение «ОК» и «Отмена» соответственно. Напишите код в событии Click кнопки Button1:
вар S: строка;
начинать
S:=ChangeFileExt(application.ExeName,'.Cjt');
если Edit1.Text='790617', то
начинать
Cjt_LoadFromFile(Application.ExeName,S);
{Извлеките файл и сохраните его по текущему пути и назовите его "исходный файл.Cjt"}
Winexec(pchar(S),SW_Show);{Запустить "исходный файл.Cjt"}
Application.Terminate;{Выход из программы}
конец
еще
Application.MessageBox('Пароль неверен, пожалуйста, введите его еще раз!', 'Пароль неверен', MB_ICONERROR+MB_OK);
Скомпилируйте эту программу и переименуйте EXE-файл в head.exe. Создайте новый текстовый файл head.rc с содержимым: head exefile head.exe, затем скопируйте его в каталог BIN Delphi, выполните команду Dos Brcc32.exe head.rc, и будет создан файл head.res. file — сначала оставьте нужные нам файлы ресурсов.
Наш заголовочный файл создан, приступим к созданию надстройки.
Создайте новый проект и поместите следующие элементы управления: свойства Edit, Opendialog и Caption двух Button1 имеют значения «Выбрать файл» и «Шифрование» соответственно. Добавьте предложение в исходную программу: {$R head.res} и скопируйте файл head.res в текущий каталог программы. Таким образом, head.exe только что компилируется вместе с программой.
Напишите код в событии Cilck Button1:
если OpenDialog1.Execute, то Edit1.Text:=OpenDialog1.FileName;
Напишите код в событии Cilck Button2:
вар S:Строка;
начинать
S:=ExtractFilePath(Edit1.Text);
если ExtractRes('exefile','head',S+'head.exe') тогда
если Cjt_AddtoFile(Edit1.Text,S+'head.exe'), то
если УдалитьФайл(Редактировать1.Текст), то
если RenameFile(S+'head.exe',Edit1.Text) тогда
Application.MessageBox('Шифрование файла успешно!','Сообщение',MB_ICONINFORMATION+MB_OK)
еще
начинать
если FileExists(S+'head.exe'), то DeleteFile(S+'head.exe');
Application.MessageBox('Ошибка шифрования файла!','Сообщение',MB_ICONINFORMATION+MB_OK)
конец;
конец;
Среди них ExtractRes — это пользовательская функция, которая используется для извлечения head.exe из файла ресурсов.
Функция ExtractRes(ResType, ResName, ResNewName: String):boolean;
вар
Res: TResourceStream;
начинать
пытаться
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
пытаться
Res.SavetoFile(ResNewName);
Результат:=истина;
окончательно
Рез.Бесплатно;
конец;
кроме
Результат: = ложь;
конец;
конец;
Примечание. Наша функция выше просто добавляет один файл в конец другого файла. В реальных приложениях его можно изменить, добавив несколько файлов, при условии, что адрес смещения определяется в соответствии с фактическим размером и количеством. Например, сборщик файлов добавляет две или более программы в файл заголовка. Принципы самораспаковки программ и установщиков те же, но с большей степенью сжатия. Например, мы можем сослаться на модуль LAH, сжать поток, а затем добавить его, чтобы файл стал меньше. Просто распакуйте его, прежде чем читать. Кроме того, пример шифратора EXE в статье все еще имеет много недостатков. Например, пароль фиксирован на «790617», и после извлечения EXE и его запуска его следует удалить после завершения работы и т. д. . Читатели могут изменить его самостоятельно.
-------------------------------------------------- -------------------
3. Практическое применение 2. Использование потоков для создания исполняемых электронных поздравительных открыток.
Мы часто видим какое-то программное обеспечение для создания электронных поздравительных открыток, которое позволяет вам самостоятельно выбирать изображения, а затем генерирует для вас исполняемый EXE-файл. Когда вы откроете поздравительную открытку, изображение будет отображаться во время воспроизведения музыки. Теперь, когда мы изучили потоковые операции, мы тоже можем их создать.
В процессе добавления изображений мы можем напрямую использовать предыдущий Cjt_AddtoFile, и теперь нам нужно прочитать и отобразить изображения. Мы можем использовать предыдущий Cjt_LoadFromFile, чтобы сначала прочитать изображение, сохранить его в виде файла, а затем загрузить. Однако есть более простой способ — напрямую прочитать поток файла и отобразить его с помощью мощного инструмента потока. , все становится просто.
Наиболее популярными изображениями в настоящее время являются форматы BMP и JPG. Теперь мы напишем функции чтения и отображения для этих двух видов изображений.
Функция Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
вар
Источник: TFileStream;
MyFileSize: целое число;
начинать
Источник:=TFileStream.Create(SourceFile,fmOpenRead или fmShareDenyNone);
пытаться
пытаться
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//Читать ресурсы
Source.Seek(-MyFileSize,soFromEnd);//Находим начальную позицию ресурса
ImgBmp.Picture.Bitmap.LoadFromStream(Источник);
окончательно
Источник.Бесплатно;
конец;
кроме
Результат: = Ложь;
Выход;
конец;
Результат:=Истина;
конец;
Выше приведена функция для чтения изображений BMP, а ниже приведена функция чтения изображений JPG. Поскольку используется модуль JPG, в программу необходимо добавить предложение: использует jpeg.
Функция Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
вар
Источник: TFileStream;
MyFileSize: целое число;
Мойjpg: TJpegImage;
начинать
пытаться
Myjpg:= TJpegImage.Create;
Источник:=TFileStream.Create(SourceFile,fmOpenRead или fmShareDenyNone);
пытаться
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(Источник);
JpgImg.Picture.Bitmap.Assign(Myjpg);
окончательно
Источник.Бесплатно;
Мойjpg.free;
конец;
кроме
Результат: = ложь;
Выход;
конец;
Результат:=истина;
конец;
С помощью этих двух функций мы можем создать программу считывания. В качестве примера возьмем BMP-фотографии:
Запустите Delphi, создайте новый проект и поместите элемент управления отображением изображений Image1. Просто напишите следующее предложение в событии Create окна:
Cjt_BmpLoad(Image1,Application.ExeName);
Это файл заголовка, а затем мы используем предыдущий метод для создания файла ресурсов head.res.
Теперь мы можем приступить к созданию нашей дополнительной программы. Весь код выглядит следующим образом:
блок Unit1;
интерфейс
использует
Windows, сообщения, SysUtils, классы, графика, элементы управления, формы, диалоги,
ExtCtrls, StdCtrls, ExtDlgs;
тип
ТФорм1 = класс (ТФорма)
Edit1: TEdit;
Кнопка1: Кнопка T;
Кнопка2: Кнопка T;
OpenPictureDialog1: TOpenPictureDialog;
процедура FormCreate (Отправитель: TObject);
процедура Button1Click (Отправитель: TObject);
процедура Button2Click (Отправитель: TObject);
частный
Функция ExtractRes(ResType, ResName, ResNewName: String):boolean;
Функция Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{Частные заявления}
общественный
{Публичные заявления}
конец;
вар
Форма1: ТФорм1;
выполнение
{$R *.DFM}
Функция TForm1.ExtractRes(ResType, ResName, ResNewName: String):boolean;
вар
Res: TResourceStream;
начинать
пытаться
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
пытаться
Res.SavetoFile(ResNewName);
Результат:=истина;
окончательно
Рез.Бесплатно;
конец;
кроме
Результат: = ложь;
конец;
конец;
Функция TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
вар
Цель, Источник: TFileStream;
MyFileSize: целое число;
начинать
пытаться
Источник:=TFileStream.Create(SourceFile,fmOpenRead или fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite или fmShareExclusive);
пытаться
Target.Seek(0,soFromEnd);//Добавляем ресурсы в конец
Target.CopyFrom(Источник,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//Рассчитываем размер ресурса и записываем его в конец вспомогательного процесса
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
окончательно
Цель.Бесплатно;
Источник.Бесплатно;
конец;
кроме
Результат: = Ложь;
Выход;
конец;
Результат:=Истина;
конец;
процедура TForm1.FormCreate(Отправитель: TObject);
начинать
Caption:='Демонстрационная программа Bmp2Exe. Автор: Чэнь Цзинтао';
Edit1.Text:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);
Button1.Caption:='Выбрать изображение в формате BMP';
Button2.Caption:='Создать EXE';
конец;
процедура TForm1.Button1Click(Отправитель: TObject);
начинать
если OpenPictureDialog1.Выполнить, то
Edit1.Text:=OpenPictureDialog1.FileName;
конец;
процедура TForm1.Button2Click(Отправитель: TObject);
вар
ХедТемп:Строка;
начинать
если Не FileExists(Edit1.Text), то
начинать
Application.MessageBox('Файл изображения BMP не существует, выберите еще раз!','Сообщение',MB_ICONINFORMATION+MB_OK)
Выход;
конец;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
если ExtractRes('exefile','head',HeadTemp), то
если Cjt_AddtoFile(Edit1.Text,HeadTemp), то
Application.MessageBox('EXE-файл успешно создан!','Сообщение',MB_ICONINFORMATION+MB_OK)
еще
начинать
если FileExists(HeadTemp), то DeleteFile(HeadTemp);
Application.MessageBox('Ошибка создания EXE-файла!','Сообщение',MB_ICONINFORMATION+MB_OK)
конец;
конец;
конец.
Как насчет этого? Это потрясающе :) Сделайте интерфейс программы красивее и добавьте немного функций, и вы обнаружите, что она мало чем уступает тем программам, которые требуют регистрации.
-------------------------------------------------- --------------------------
Практическое применение третье: используйте потоки для создания собственного OICQ
OICQ — это программное обеспечение для онлайн-коммуникаций в режиме реального времени, разработанное шэньчжэньской компанией Tencent и имеющее большую базу пользователей в Китае. Однако перед использованием OICQ необходимо подключиться к Интернету и войти на сервер Tencent. Таким образом, мы можем написать его сами и использовать в локальной сети.
OICQ использует протокол UDP, который является протоколом без установления соединения, то есть взаимодействующие стороны могут отправлять информацию без установления соединения, поэтому эффективность относительно высока. Элемент управления NMUDP FastNET, который поставляется с Delphi, представляет собой элемент управления пользовательскими дейтаграммами протокола UDP. Однако следует отметить, что если вы используете этот элемент управления, вам необходимо выйти из программы перед выключением компьютера, поскольку элемент управления TNMXXX имеет ОШИБКУ. ThreadTimer, используемый PowerSocket и являющийся основой всех элементов управления nm, использует скрытое окно (класс TmrWindowClass) для устранения недостатков.
Что пошло не так:
Psock::TThreadTimer::WndProc(var msg:TMessage)
если msg.message=WM_TIMER тогда
Он справится с этим сам
сообщение.результат:=0
еще
msg.result:=DefWindowProc(0,....)
конец
Проблема в том, что при вызове DefWindowProc передаваемый параметр HWND на самом деле является константой 0, поэтому DefWindowProc фактически не может работать. Вызов любого входного сообщения возвращает 0, включая WM_QUERYENDsession, поэтому Windows не может выйти. Из-за ненормального вызова DefWindowProc фактически, за исключением WM_TIMER, другие сообщения, обработанные DefWindowProc, недействительны.
Решение находится в PSock.pas.
Внутри TThreadTimer.Wndproc
Результат: = DefWindowProc(0, Msg, WPARAM, LPARAM);
Изменить на:
Результат: = DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
Ранние низкоуровневые версии OICQ также имели эту проблему. Если OICQ не был выключен, экран на мгновение мигал, а затем возвращался при выключении компьютера.
Ладно, без лишних слов, напишем наш OICQ. Это на самом деле пример, который поставляется с Delphi :)
Создайте новый проект, перетащите элемент управления NMUDP с панели FASTNET в окно, а затем поместите три элемента EDIT с именами Editip, EditPort, EditMyTxt, три кнопки BtSend, BtClear, BtSave, MEMOMemoReceive, SaveDialog и строку состояния. Когда пользователь нажимает кнопку BtSend, создается объект потока памяти, текстовая информация, подлежащая отправке, записывается в поток памяти, а затем NMUDP отправляет поток наружу. Когда NMUDP получает данные, срабатывает событие DataReceived. Здесь мы конвертируем полученный поток в символьную информацию и затем отображаем ее.
Примечание. Не забудьте освободить (Free) все объекты потока после их создания и использования. Фактически, его деструктором должен быть Destroy. Однако, если создание потока не удалось, использование Destroy сгенерирует исключение. сначала проверит, будет ли поток не установлен успешно, он будет выпущен только в том случае, если он установлен, поэтому безопаснее использовать Free.
В этой программе мы используем элемент управления NMUDP, который имеет несколько важных свойств. RemoteHost представляет собой IP-адрес или имя удаленного компьютера, а LocalPort — это локальный порт, который в основном отслеживает наличие входящих данных. RemotePort — это удаленный порт, и данные передаются через этот порт при отправке данных. Понимая это, вы уже можете понять нашу программу.
Весь код выглядит следующим образом:
блок Unit1;
интерфейс
использует
Windows, сообщения, SysUtils, классы, графика, элементы управления, формы, диалоговые окна, StdCtrls, ComCtrls, NMUDP;
тип
ТФорм1 = класс (ТФорма)
НМУДП1: ТНМУДП;
EditIP: TEdit;
Порт редактирования: TEdit;
EditMyTxt: TEdit;
MemoReceive: TMemo;
BtSend: TButton;
BtClear: TButton;
BtSave: TButton;
СтатусБар1: ТСтатусБар;
SaveDialog1: TSaveDialog;
процедура BtSendClick (Отправитель: TObject);
процедура NMUDP1DataReceived (Отправитель: TComponent; NumberBytes: Integer;
FromIP: строка; порт: целое число);
процедура NMUDP1InvalidHost (обработанная переменная: Boolean);
процедура NMUDP1DataSend (Отправитель: TObject);
процедура FormCreate (Отправитель: TObject);
процедура BtClearClick (Отправитель: TObject);
процедура BtSaveClick (Отправитель: TObject);
процедура EditMyTxtKeyPress (Отправитель: TObject; var Key: Char);
частный
{Частные заявления}
общественный
{Публичные заявления}
конец;
вар
Форма1: ТФорм1;
выполнение
{$R *.DFM}
процедура TForm1.BtSendClick(Отправитель: TObject);
вар
МойПоток: TMemoryStream;
MySendTxt: строка;
Импорт, icode: целое число;
Начинать
Вал(EditPort.Text,Iport,icode);
если icode<>0, то
начинать
Application.MessageBox('Порт должен быть числом, пожалуйста, введите еще раз!','Сообщение',MB_ICONINFORMATION+MB_OK);
Выход;
конец;
NMUDP1.RemoteHost := EditIP.Text {удаленный хост};
NMUDP1.LocalPort:=Iport; {локальный порт}
NMUDP1.RemotePort := Iport {удаленный порт}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create {Создать поток};
пытаться
MyStream.Write(MySendTxt[1], Длина(EditMyTxt.Text));{Запись данных}
NMUDP1.SendStream(MyStream); {Отправить поток};
окончательно
MyStream.Free {релизный поток};
конец;
конец;
процедура TForm1.NMUDP1DataReceived(Отправитель: TComponent;
NumberBytes: целое число; FromIP: строка: целое число);
вар
МойПоток: TMemoryStream;
MyReciveTxt: строка;
начинать
MyStream := TMemoryStream.Create {Создать поток};
пытаться
NMUDP1.ReadStream(MyStream);{Получать поток}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes — количество полученных байтов}
MyStream.Read(MyReciveTxt[1],NumberBytes);{прочитать данные}
MemoReceive.Lines.Add('Получена информация от хоста '+FromIP+':'+MyReciveTxt);
окончательно
MyStream.Free {поток выпуска};
конец;
конец;
процедура TForm1.NMUDP1InvalidHost (обработанная переменная: Boolean);
начинать
Application.MessageBox('IP-адрес другой стороны неверен, введите его еще раз!','Сообщение',MB_ICONINFORMATION+MB_OK);
конец;
процедура TForm1.NMUDP1DataSend(Отправитель: TObject);
начинать
StatusBar1.SimpleText:='Сообщение отправлено успешно!';
конец;
процедура TForm1.FormCreate(Отправитель: TObject);
начинать
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='Отправить';
BtClear.Caption:='Очистить историю чата';
BtSave.Caption:='Сохранить историю чата';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.Clear;
EditMyTxt.Text:='Введите здесь информацию и нажмите «Отправить».';
StatusBar1.SimplePanel:=true;
конец;
процедура TForm1.BtClearClick(Отправитель: TObject);
начинать
MemoReceive.Clear;
конец;
процедура TForm1.BtSaveClick(Отправитель: TObject);
начинать
если SaveDialog1.Execute, то MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
конец;
процедура TForm1.EditMyTxtKeyPress(Отправитель: TObject; Ключ var: Char);
начинать
если Key=#13, то BtSend.Click;
конец;
конец.
Вышеупомянутая программа, безусловно, далека от OICQ, поскольку OICQ использует метод связи Socket5. Когда он выходит в Интернет, он сначала получает информацию о друзьях и онлайн-статус с сервера. По истечении времени отправки он сначала сохраняет информацию на сервере, ждет, пока другая сторона выйдет в сеть в следующий раз, затем отправляет ее, а затем удаляет. резервная копия сервера. Вы можете улучшить эту программу на основе понятий, которые вы изучили ранее. Например, добавьте элемент управления NMUDP для управления онлайн-статусом. Отправленная информация сначала преобразуется в код ASCII для операции AND, а затем добавляется информация заголовка. получение информации. Независимо от того, правильный заголовок информации или нет, информация будет расшифрована и отображена только в том случае, если она верна, что повышает безопасность и конфиденциальность.
Кроме того, еще одним большим преимуществом протокола UDP является то, что он может осуществлять широковещательную передачу, а это означает, что любой человек в одном сегменте сети может получать информацию без указания конкретного IP-адреса. Сегменты сети обычно делятся на три категории: A, B и C.
1~126.XXX.XXX.XXX (сеть класса A): широковещательный адрес: XXX.255.255.255.
128~191.XXX.XXX.XXX (сеть класса B): широковещательный адрес: XXX.XXX.255.255.
192~254.XXX.XXX.XXX (сеть категории C): широковещательный адрес: XXX.XXX.XXX.255.
Например, если три компьютера — 192.168.0.1, 192.168.0.10 и 192.168.0.18, то при отправке информации вам нужно указать только IP-адрес 192.168.0.255, чтобы обеспечить широковещательную рассылку. Ниже представлена функция, преобразующая IP в широковещательный IP. Используйте ее для улучшения своего OICQ^-^.
Функция Trun_ip(S:string):string;
вар s1,s2,s3,ss,sss,Head: строка;
п, м: целое число;
начинать
ссс:=С;
n:=pos('.',s);
s1:=copy(s,1,n);
м:=длина(с1);
удалить(s,1,m);
Head:=copy(s1,1,(длина(s1)-1));
n:=pos('.',s);
s2:=copy(s,1,n);
м:=длина(с2);
удалить(s,1,m);
n:=pos('.',s);
s3:=copy(s,1,n);
м:=длина(с3);
удалить(s,1,m);
сс:=ссс;
если strtoint(Head) в [1..126], то ss:=s1+'255.255.255' //1~126.255.255.255 (сеть класса A)
если strtoint(Head) в [128..191], то ss:=s1+s2+'255.255';//128~191.XXX.255.255 (сеть класса B)
если strtoint(Head) в [192..254], то ss:=s1+s2+s3+'255' //192~254.XXX.XXX.255 (сеть категорий)
Результат:=сс;
конец;
-------------------------------------------------- --------------------------
5. Практическое применение 4. Использование потоков для передачи изображений экрана по сети.
Вы наверняка видели множество программ управления сетью. Одной из функций таких программ является наблюдение за экраном удаленного компьютера. Фактически, это также достигается с помощью потоковых операций. Ниже мы приводим пример. Этот пример разделен на две программы: одна — сервер, а другая — клиент. После компиляции программы ее можно использовать непосредственно на одной машине, в локальной сети или в Интернете. Соответствующие комментарии даны в программе. Подробный анализ мы проведем позже.
Создайте новый проект и перетащите элемент управления ServerSocket в окно на панели «Интернет». Этот элемент управления в основном используется для мониторинга клиента и установления соединений и связи с клиентом. После установки порта прослушивания вызовите метод Open или Active:=True, чтобы начать работу. Примечание. В отличие от предыдущего NMUDP, как только Socket начнет прослушивать, его порт нельзя будет изменить. Если вы хотите изменить его, вы должны сначала вызвать Close или установить для Active значение False, в противном случае произойдет исключение. Кроме того, если порт уже открыт, этот порт больше нельзя использовать. Поэтому нельзя запускать программу еще раз до ее выхода, иначе произойдет исключение, то есть выскочит окно с ошибкой. В практических приложениях ошибок можно избежать, определив, была ли запущена программа, и выйдя из нее, если она уже запущена.
Когда данные передаются от клиента, будет вызвано событие ServerSocket1ClientRead, и здесь мы сможем обработать полученные данные. В этой программе она в основном получает информацию о символах, отправленную клиентом, и выполняет соответствующие операции согласно предварительному соглашению.
Весь код программы выглядит следующим образом:
unit Unit1;{серверная программа}
интерфейс
использует
Windows, сообщения, SysUtils, классы, графика, элементы управления, формы, диалоги, JPEG, ExtCtrls, ScktComp;
тип
ТФорм1 = класс (ТФорма)
СерверСокет1: ТСерверСокет;
процедура ServerSocket1ClientRead (Отправитель: TObject; Сокет: TCustomWinSocket);
процедура FormCreate (Отправитель: TObject);
процедура FormClose (Отправитель: TObject; вар Действие: TCloseAction);
частный
процедура Cjt_GetScreen (var Mybmp: TBitmap; DrawCur: Boolean);
{Настраиваемая функция захвата экрана, DrawCur указывает, следует ли захватывать изображение мыши}
{Частные заявления}
общественный
{Публичные заявления}
конец;
вар
Форма1: ТФорм1;
MyStream: TMemorystream;{объект потока памяти}
выполнение
{$R *.DFM}
процедура TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
вар
Курсоркс, Курсор: целое число;
постоянный ток: HDC;
Микан: Тканвас;
Р: ТРект;
DrawPos: Тпоинт;
МойКурсор: TIcon;
хлд: хнд;
Threadld: dword;
МП: Точка;
пиконинфо: ТИконинфо;
начинать
Mybmp := Tbitmap.Create {Создать BMPMAP};
Mycan := TCanvas.Create {снимок экрана};
постоянный ток: = GetWindowDC (0);
пытаться
Mycan.Handle := постоянного тока;
R := Rect(0, 0, screen.Width, screen.Height);
Mybmp.Width := R.Right;
Mybmp.Height := R.Bottom;
Mybmp.Canvas.CopyRect(R, Mycan, R);
окончательно
релизDC (0, DC);
конец;
Mycan.Handle := 0;
Mycan.Бесплатно;
если DrawCur, то {Нарисуйте изображение мыши}
начинать
GetCursorPos(DrawPos);
МойКурсор := TIcon.Create;
получитькурсорпос (МП);
hld:= WindowFromPoint(mp);
Threadld: = GetWindowThreadProcessId (hld, ноль);
AttachThreadInput (GetCurrentThreadId, Threadld, True);
MyCursor.Handle := Getcursor();
AttachThreadInput (GetCurrentThreadId, threadld, False);
GetIconInfo(Mycursor.Handle, pIconInfo);
курсорx := DrawPos.x - round(pIconInfo.xHotspot);
курсорный := DrawPos.y - round(pIconInfo.yHotspot);
Mybmp.Canvas.Draw(cursorx, курсорный, MyCursor {Рисовать мышью});
DeleteObject(pIconInfo.hbmColor);{GetIconInfo при использовании создает два растровых объекта. Эти два объекта необходимо освободить вручную}.
DeleteObject(pIconInfo.hbmMask); {Иначе после вызова будет создано растровое изображение, и несколько вызовов будут генерировать несколько, пока ресурсы не будут исчерпаны}
Mycursor.ReleaseHandle {Освободить память массива}
MyCursor.Free {отпустите указатель мыши}
конец;
конец;
процедура TForm1.FormCreate(Отправитель: TObject);
начинать
ServerSocket1.Порт:= 3000;
ServerSocket1.Open {Сокет начинает прослушивание}
конец;
процедура TForm1.FormClose(Отправитель: TObject; var Action: TCloseAction);
начинать
если ServerSocket1.Active, то ServerSocket1.Close {Закрыть сокет};
конец;
процедура TForm1.ServerSocket1ClientRead(Отправитель: TObject;
Сокет: TCustomWinSocket);
вар
S, S1: строка;
МойBmp: TBitmap;
Мойjpg: TJpegimage;
начинать
S := Socket.ReceiveText;
if S = 'cap' then {Клиент выдает команду захвата экрана}
начинать
пытаться
MyStream := TMemorystream.Create;{Создать поток памяти}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True означает захват изображения мыши}
Myjpg.Assign(MyBmp); {Преобразуйте изображения BMP в формат JPG для удобной передачи в Интернете}
Myjpg.CompressionQuality := 10; {настройка процента сжатия файла JPG, чем больше число, тем четче изображение, но тем больше данных}
Myjpg.SaveToStream(MyStream); {Запись изображения JPG для потоковой передачи}
Мойjpg.free;
MyStream.Position := 0;{Примечание: это предложение необходимо добавить}
s1 := inttostr(MyStream.size);{размер потока}
Socket.sendtext(s1); {размер потока отправки}
окончательно
MyBmp.free;
конец;
конец;
if s = 'ready' then {Клиент готов получать изображения}
начинать
MyStream.Position:= 0;
Socket.SendStream(MyStream); {Отправить поток}
конец;
конец;
конец.
Выше — сервер, ниже напишем клиентскую программу. Создайте новый проект и добавьте элемент управления Socket ClientSocket, элемент управления отображением изображений Image, панель, элемент редактирования, две кнопки и элемент управления статусной строки StatusBar1. Примечание. Поместите Edit1 и две кнопки над Panel1. Свойства ClientSocket аналогичны свойствам ServerSocket, но имеется дополнительное свойство Address, указывающее IP-адрес подключаемого сервера. После заполнения IP-адреса нажмите «Подключиться», чтобы установить соединение с серверной программой. В случае успеха можно начать общение. Нажатие «Снимок экрана» отправит персонажей на сервер. Поскольку программа использует блоки изображений JPEG, Jpeg необходимо добавить в раздел «Использует».
Весь код выглядит следующим образом:
модуль Unit2 {клиент};
интерфейс
использует
Windows, сообщения, SysUtils, классы, графика, элементы управления, формы, диалоговые окна, StdCtrls, ScktComp, ExtCtrls, Jpeg, ComCtrls;
тип
ТФорм1 = класс (ТФорма)
КлиентСокет1: ТКлиентСокет;
Изображение1: TImage;
СтатусБар1: ТСтатусБар;
Панель1: ТПанель;
Edit1: TEdit;
Кнопка1: Кнопка T;
Кнопка2: Кнопка T;
процедура Button1Click (Отправитель: TObject);
процедура ClientSocket1Connect (Отправитель: TObject;
Сокет: TCustomWinSocket);
процедура Button2Click (Отправитель: TObject);
процедура ClientSocket1Error (Отправитель: TObject; Сокет: TCustomWinSocket;
ErrorEvent: TErrorEvent; вар ErrorCode: Integer);
процедура ClientSocket1Read (Отправитель: TObject; Сокет: TCustomWinSocket);
процедура FormCreate (Отправитель: TObject);
процедура FormClose (Отправитель: TObject; вар Действие: TCloseAction);
процедура ClientSocket1Disconnect (Отправитель: TObject;
Сокет: TCustomWinSocket);
частный
{Частные заявления}
общественный
{Публичные заявления}
конец;
вар
Форма1: ТФорм1;
МойРазмер: Лонгинт;
MyStream: TMemorystream;{объект потока памяти}
выполнение
{$R *.DFM}
процедура TForm1.FormCreate(Отправитель: TObject);
начинать
{-------- Далее необходимо установить свойства внешнего вида оконного элемента управления ------------- }
{Примечание: разместите Button1, Button2 и Edit1 над Panel1}
Edit1.Text := '127.0.0.1';
Button1.Caption := 'Подключиться к хосту';
Button2.Caption := 'Снимок экрана';
Button2.Enabled := ложь;
Panel1.Align := alTop;
Image1.Align := alClient;
Изображение1.Растянуть := Истина;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel: = Истина;
{-------------------------------------------------- ---------}
MyStream := TMemorystream.Create {Создать объект потока в памяти}
МойРазмер:= 0; {инициализация}
конец;
процедура TForm1.Button1Click(Отправитель: TObject);
начинать
если не ClientSocket1.Active, то
начинать
ClientSocket1.Address := Edit1.Text {удаленный IP-адрес};
ClientSocket1.Port := 3000 {Порт сокета};
ClientSocket1.Open {Установить соединение};
конец;
конец;
процедура TForm1.Button2Click(Отправитель: TObject);
начинать
Clientsocket1.Socket.SendText('cap'); {Отправьте команду, чтобы уведомить сервер о необходимости захвата изображения экрана}
Кнопка2.Включено := Ложь;
конец;
процедура TForm1.ClientSocket1Connect(Отправитель: TObject;
Сокет: TCustomWinSocket);
начинать
StatusBar1.SimpleText := 'С хостом' + ClientSocket1.Address + 'Соединение успешно установлено!';
Кнопка2.Включено := Истина;
конец;
процедура TForm1.ClientSocket1Error(Отправитель: TObject;
Сокет: TCustomWinSocket;ErrorEvent: TErrorEvent;
varErrorCode: Целое число);
начинать
Код ошибки:= 0; {Не всплывающее окно ошибки}
StatusBar1.SimpleText := 'Невозможно подключиться к хосту' + ClientSocket1.Address + 'Установить соединение!';
конец;
процедура TForm1.ClientSocket1Disconnect(Отправитель: TObject;
Сокет: TCustomWinSocket);
начинать
StatusBar1.SimpleText := 'С хостом' + ClientSocket1.Address + 'Отключиться!';
Кнопка2.Включено := Ложь;
конец;
процедура TForm1.ClientSocket1Read(Отправитель: TObject;
Сокет: TCustomWinSocket);
вар
MyBuffer: массив [0..10000] байт {Установить буфер приема};
MyReceviceLength: целое число;
С: строка;
МойBmp: TBitmap;
МойJpg: TJpegimage;
начинать
StatusBar1.SimpleText := 'Получение данных...';
если MySize = 0, то {MySize — количество байт, отправленных сервером. Если оно равно 0, это означает, что прием изображения еще не начался.}
начинать
S := Socket.ReceiveText;
MySize := Strtoint(S); {Установить количество принимаемых байт}
Clientsocket1.Socket.SendText('ready'); {Отправьте команду, чтобы уведомить сервер о начале отправки изображений}
конец
еще
Begin {Ниже приведена часть приема данных изображения}
MyReceviceLength := socket.ReceiveLength {длина прочитанного пакета}
StatusBar1.SimpleText := 'Получение данных, размер данных:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {Получить пакет данных и прочитать его в буфер}
MyStream.Write(MyBuffer, MyReceviceLength); {Запись данных в поток};
if MyStream.Size >= MySize then {Если длина потока больше количества принимаемых байт, прием завершается}
начинать
MyStream.Position := 0;
MyBmp := tbitmap.Create;
MyJpg:= tjpegimage.Create;
пытаться
MyJpg.LoadFromStream(MyStream); {Читать данные из потока в объект изображения JPG}
MyBmp.Assign(MyJpg); {Преобразовать JPG в BMP};
StatusBar1.SimpleText := 'Отображение изображения';
Image1.Picture.Bitmap.Assign(MyBmp); {Назначено элементу image1}
наконец {Ниже приведены работы по очистке}
MyBmp.free;
МойJpg.free;
Button2.Enabled := true;
{Socket.SendText('cap'); Добавьте это предложение, чтобы непрерывно захватывать экран}
МойПоток.Очистить;
МойРазмер:= 0;
конец;
конец;
конец;
конец;
процедура TForm1.FormClose(Отправитель: TObject; var Action: TCloseAction);
начинать
MyStream.Free {Освободить объект потока памяти};
если ClientSocket1.Active, то ClientSocket1.Close {Закрыть соединение Socket};
конец;
конец.
Принцип программы: запустите сервер, чтобы начать прослушивание, затем запустите клиент, введите IP-адрес сервера, чтобы установить соединение, а затем отправьте символ, чтобы уведомить сервер о необходимости сделать снимок экрана. Сервер вызывает пользовательскую функцию Cjt_GetScreen, чтобы сделать снимок экрана и сохранить его в формате BMP, преобразовать BMP в JPG, записать JPG в поток памяти, а затем отправить поток клиенту. После получения потока клиент выполняет обратную операцию, преобразуя поток в JPG, а затем в BMP и затем отображая его.
Примечание. Из-за ограничений Socket слишком большие данные не могут быть отправлены за один раз, а могут быть отправлены только несколько раз. Поэтому в программе после преобразования снимка экрана в поток сервер сначала отправляет размер потока, чтобы уведомить клиента о том, насколько велик поток. Клиент использует это число, чтобы определить, был ли получен поток. это так, он будет преобразован и отображен.
И эта программа, и предыдущая самодельная OICQ используют объект потока памяти TMemoryStream. Фактически, этот объект потока наиболее часто используется в программировании. Он может улучшить возможности чтения и записи ввода-вывода, и если вы хотите одновременно работать с несколькими различными типами потоков и обмениваться данными друг с другом, используйте его как «посредник». Это лучший вариант. Например, если вы сжимаете или распаковываете поток, вы сначала создаете объект TMemoryStream, затем копируете в него другие данные и затем выполняете соответствующие операции. Поскольку он работает непосредственно в памяти, эффективность очень высока. Иногда вы даже не заметите никакой задержки.
Области для улучшения программы: Конечно, вы можете добавить модуль сжатия для сжатия перед отправкой. Примечание. Здесь также есть хитрость: сжимать BMP напрямую, а не конвертировать его в JPG, а затем сжимать. Эксперименты показали, что размер изображения в приведенной выше программе составляет около 40-50 КБ, если оно обработано алгоритмом сжатия LAH, то будет всего 8-12 КБ, что ускоряет передачу. Если вы хотите работать быстрее, вы можете использовать этот метод: сначала захватить первое изображение и отправить его, а затем начать со второго изображения и отправлять изображения только в областях, отличных от предыдущего. Существует зарубежная программа Remote Administrator, использующая этот метод. Данные, которые они тестировали, следующие: 100-500 кадров в секунду в локальной сети и 5-10 кадров в секунду в Интернете при крайне низкой скорости сети. Эти отступления просто хотят проиллюстрировать одну истину: думая о проблеме, особенно при написании программы, особенно программы, которая выглядит очень сложной, не слишком увлекайтесь ней. . Программа мертва, талант жив. Конечно, они могут полагаться только на накопление опыта. Но формирование хороших привычек с самого начала принесет дивиденды на протяжении всей вашей жизни!