Закачать файл на WEB-сервер с помощью HTML

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Оригинал статьи: Delphi Web Development

На этой страничке вы узнаете как закачать файл на веб-сервер, используя HTML.

Перед тем как приступить к разработке, желательно ознакомиться с RFC1867: Form-based File Upload in HTML, в котором описаны форматы передачи файлов в HTML.

Клиент

Чтобы закачать файл на сервер, Вам нужно использовать тэг FORM. Для передачи данных формы, обязательно, используется метод POST и устанавливается MIME media type: multipart/form-data.

Вы должны установить в тэг FORM атрибут ENCTYPE равным multipart/form-data, и в форму добавить элемент INPUT типа file.

Например, давайте создадим следующий HTML файл, назовем его upload.htm и положим его в корневой директорий веб сервера.

DWD File Upload

CheckBox Test

RadioBox Test:

ON

OFF

TextBox Test:

File2 Test:

File2 Test:

File2 Test:

Пример 1

Итак, мы создали форму которая содержит элементы checkbox, radio, text и три(!) элемента file.

Загрузите этот файл браузером, например, набирите http://localhost/upload.htm. Если, нажав кноку Browse на форме, выбрать файл 1: C:\\image.gif, файл 2: C:\\test.exe, файл 3: C:\\text.txt и нажать кнопку Submit, в соответствии с RFC1867 браузер должен сформировать и передать в заголовке HTTP поле

ContentType: multipart/form-data; boundary=—7d015813802c4

и примерно следующий контент:

—-7d015813802c4

Content-Disposition: form-data; name=»checkbox»

ON

—-7d015813802c4

Content-Disposition: form-data; name=»radiobox»

ON

—-7d015813802c4

Content-Disposition: form-data; name=»text»

…текст из элемента текст…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile1″; filename=»C:\\image.gif»

Content-Type: image/gif

…содержимое файла 1…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile2″; filename=»C:\\test.exe»

Content-Type: application/octet-stream

…содержимое файла 2…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile3″; filename=»C:\\text.txt»

Content-Type: text/plain

…содержимое файла 3…

—-7d015813802c4—

Пример 2

Где boundary (граница) — разделитель полей.

Сервер

На стороне сервера, для приема данных, мы будем использовать ISAPI приложение, которое сейчас и создадим.

Запускаем Delphi, создаем с помощью Delphi IDE новый проект (мы создали ISAPI DLL, вы можете создать как ISAPI, так и CGI) и создаем обработчик WebActionItem. Установим его свойство Default равным True.

Приступим к написанию кода. Создадим обработчик события OnAction, для созданного WebActionItem. Прежде всего нужно быть уверенным, что принимаемые данные действительно имеют ContentType: multipart/form-data:

procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;

Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);

begin

if (Pos(‘multipart/form-data’, LowerCase(Request.ContentType)) > 0) and

(Pos(‘boundary’, LowerCase(Request.ContentType)) > 0) then

begin

end

else

Response.Content := ‘Content-type is not «multipart/form-data».’;

end;

Пример 3

Определим boundary, для этого воспользуемся функцией ExtractHeaderFields:

var

Boundary: string;

Header: TStrings;

begin

Header := TStringList.Create;

try

ExtractHeaderFields([‘;’], [‘ ‘], PChar(Request.ContentType), Header,

False, False);

Boundary := Header.Values[‘boundary’];

finally

Header.Free;

end;

Пример 4

Для того, чтобы прочитать переданные клиентом данные, нужно прочитать свойство Content объекта Request. Следует сказать, что Request.Content не обязательно должен содержать все данные т.к. размер свойства Content ограничен, особенно актуально это для передачи файлов большого размера. Для того чтобы прочитать следующую порцию данных, используйте метод ReadClient объекта Request. Метод ReadClient читает данные в буфер. В качесте буфера используем PChar. Определить размер данных, которые необходимо считать можно так:

AllContent := Request.Content;

ByteToRead := Request.ContentLength — Length(AllContent);

Далее следует зарезервировать память для буфера, для того чтобы экономнее расходовать память, ограничим максимальный размер буфера константой MaxReadBlockSize. Определим весь поток данных, переданный клиентом:

const

MaxReadBlockSize = 8192;

var

Boundary, BufferStr, AllContent: string;

Header: TStrings;

ByteToRead, ReadedBytes, RSize: Integer;

Buffer: PChar;

begin

AllContent := Request.Content;

ByteToRead := Request.ContentLength — Length(AllContent);

try

while ByteToRead > 0 do

begin

RSize := MaxReadBlockSize;

if RSize > ByteToRead then

RSize := ByteToRead;

GetMem(Buffer, RSize);

try

ReadedBytes := Request.ReadClient(Buffer^, RSize);

SetString(BufferStr, Buffer, ReadedBytes);

AllContent := AllContent BufferStr;

finally

FreeMem(Buffer, RSize);

end;

ByteToRead := Request.ContentLength — Length(AllContent);

end;

// и возвратим клиенту справочную информацию

Response.Content := Response.Content Format(‘ContentType = %s

‘ContentLength = %d
Readed Bytes = %d
Boundary = %s


‘,

[Request.ContentType, Request.ContentLength, Length(AllContent),

boundary]);

except

on E: Exception do

Response.Content := Response.Content ‘

‘ E.Message;

end;

Пример 5

Итог: мы прочитали весь переданный контент и присвоили его переменной AllContent, а также узнали разделитель Boundary.

Следующим этапом необходимо полученный контент разобрать по частям. Для этого напишем функцию

ReadMultipartRequest(const Boundary: string;

ARequest: string; var AHeader: TStrings; var Data: string): string;

которая будет запрашивать разделитель Boundary и AllContent, и возвращать поля заголовка в AHeader и содержимое поля в Data. Результатом выполнения функции будет передан AllContent без первого поля формы. Например, после первой обработки данных, показанных в Примере 2, результат возвращаемый функцией будет:

—-7d015813802c4

Content-Disposition: form-data; name=»radiobox»

ON

—-7d015813802c4

Content-Disposition: form-data; name=»text»

…текст из элемента текст…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile1″; filename=»C:\\image.gif»

Content-Type: image/gif

…содержимое файла 1…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile2″; filename=»C:\\test.exe»

Content-Type: application/octet-stream

…содержимое файла 2…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile3″; filename=»C:\\text.txt»

Content-Type: text/plain

…содержимое файла 3…

—-7d015813802c4—

Пример 6

объект AHeader будет содержать:

Content-Disposition=form-data; name=»checkbox»

а переменная Data = ‘ON’.

После четвертой обработки будет:

—-7d015813802c4

Content-Disposition: form-data; name=»myfile2″; filename=»C:\\test.exe»

Content-Type: application/octet-stream

…содержимое файла 2…

—-7d015813802c4

Content-Disposition: form-data; name=»myfile3″; filename=»C:\\text.txt»

Content-Type: text/plain

…содержимое файла 3…

—-7d015813802c4—

Пример 7

объект AHeader будет содержать:

Content-Disposition=form-data; name=»myfile1″; filename=»C:\\image.gif»

Content-Type=image/gif

а переменная Data = …содержимое файла 1….

Код функции приведен в Примере 8:

function TWebModule1.ReadMultipartRequest(const Boundary: string;

ARequest: string; var AHeader: TStrings; var Data: string): string;

var

Req, RHead: string;

i: Integer;

begin

Result := »;

AHeader.Clear;

Data := »;

if (Pos(Boundary, ARequest) < Pos (Boundary '--', ARequest))

and (Pos(Boundary, ARequest) = 1) then

begin

Delete(ARequest, 1, Length(Boundary) 2);

Req := Copy(ARequest, 1, Pos(Boundary, ARequest) — 3);

Delete(ARequest, 1, Length(Req) 2);

RHead := Copy(Req, 1, Pos(#13#10#13#10,Req)-1);

Delete(Req, 1, Length(RHead) 4);

AHeader.Text := RHead;

for i := 0 to AHeader.Count — 1 do

if Pos(‘:’, AHeader.Strings[i]) > 0 then

AHeader.Strings[i] := Trim(Copy(AHeader.Strings[i], 1,

Pos(‘:’, AHeader.Strings[i])-1)) ‘=’ Trim(Copy(AHeader.Strings[i],

Pos(‘:’, AHeader.Strings[i]) 1, Length(AHeader.Strings[i]) —

Pos(‘:’, AHeader.Strings[i])));

Data := Req;

Result := ARequest;

end

end;

Пример 8

Теперь осталось только вызвать данную процедуру в цикле и сохранить данные на диск.

Определим константу UploadPath, которая будет содержать путь к папке, в которую будем сохранять принятые файлы.

const

UploadPath = ‘C:\\temp\\upload\\’;

var

AllContent, Boundary, Data: string;

Header, HList: TStrings;

OutStream: TFileStream;

begin

if Request.ContentLength = Length(AllContent) then

while Length(AllContent) > Length(‘—‘ Boundary ‘—‘ #13#10)do

begin

Header := TStringList.Create;

HList := TStringList.Create;

try

AllContent := ReadMultipartRequest(‘—‘ Boundary, AllContent,

Header, Data);

ExtractHeaderFields([‘;’], [‘ ‘],

PChar(Header.Values[‘Content-Disposition’]), HList, False, True);

if (Header.Values[‘Content-Type’] ») and (Data ») then

begin

OutStream:=TFileStream.Create(UploadPath

ExtractFileName(HList.Values[‘filename’]), fmCreate);

try

try

OutStream.WriteBuffer(Pointer(Data)^, Length(Data));

Response.Content := Response.Content

Format(‘

File %s saved.’,

[ExtractFileName(HList.Values[‘filename’])]);

except

Response.Content := Response.Content

Format(‘

Unable to save file %s.’, [UploadPath

ExtractFileName(HList.Values[‘filename’])]);

end;

finally

OutStream.Free;

end

end

else

Response.Content := Response.Content Format(‘

Field %s = %s’,

[HList.Values[‘name’], Data]);

finally

Header.Free;

HList.Free;

end;

end;

Пример 9

В результате выполнения данного блока программы, поля, которые содержат файл, будут сохранены на диск в папку заданную константой UploadPath, а поля, не содержащие файл будут показаны в результате выполнения скрипта, имя поля и значение будут переданы клиенту.

{/codecitation}

Закачать файл из Интернета 3

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Оформил: DeeCo

Автор: http://www.swissdelphicenter.ch

{Forces a download of the requested file, object, or directory

listing from the origin server, not from the cache}

function DownloadURL_NOCache(const aUrl: string; var s: String): Boolean;

var

hSession: HINTERNET;

hService: HINTERNET;

lpBuffer: array[0..1024 1] of Char;

dwBytesRead: DWORD;

begin

Result := False;

s := »;

// hSession := InternetOpen( ‘MyApp’, INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0);

hSession := InternetOpen(‘MyApp’, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

try

if Assigned(hSession) then

begin

hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);

if Assigned(hService) then

try

while True do

begin

dwBytesRead := 1024;

InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead);

if dwBytesRead = 0 then break;

lpBuffer[dwBytesRead] := #0;

s := s lpBuffer;

end;

Result := True;

finally

InternetCloseHandle(hService);

end;

end;

finally

InternetCloseHandle(hSession);

end;

end;

//aufrufen

var

s: String;

begin

if DownloadURL(‘http://www.swissdelphicenter.ch/’, s) then

ShowMessage(s);

end;

{/codecitation}

Закачать файл из Интернета 2

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Сколько пользуюсь интернетом — никак не могу понять: я вхожу в интернет или я выхожу в интернет?

uses

Wininet;

function DownloadURL(const aUrl: string): Boolean;

var

hSession: HINTERNET;

hService: HINTERNET;

lpBuffer: array[0..1024 1] of Char;

dwBytesRead: DWORD;

begin

Result := False;

// hSession := InternetOpen( ‘MyApp’, INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0);

hSession := InternetOpen(‘MyApp’, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

try

if Assigned(hSession) then

begin

hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, 0, 0);

if Assigned(hService) then

try

while True do

begin

dwBytesRead := 1024;

InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead);

if dwBytesRead = 0 then break;

lpBuffer[dwBytesRead] := #0;

Form1.Memo1.Lines.Add(lpBuffer);

end;

Result := True;

finally

InternetCloseHandle(hService);

end;

end;

finally

InternetCloseHandle(hSession);

end;

end;

{/codecitation}

Закачать файл из Интернета

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Сколько пользуюсь интернетом — никак не могу понять: я вхожу в интернет или я выхожу в интернет?

uses

URLMon, ShellApi;

function DownloadFile(SourceFile, DestFile: string): Boolean;

begin

try

Result := UrlDownloadToFile(nil, PChar(SourceFile), PChar(DestFile), 0, nil) = 0;

except

Result := False;

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

const

// URL Location

SourceFile = ‘http://www.google.com/intl/de/images/home_title.gif’;

// Where to save the file

DestFile = ‘c:\temp\google-image.gif’;

begin

if DownloadFile(SourceFile, DestFile) then

begin

ShowMessage(‘Download succesful!’);

// Show downloaded image in your browser

ShellExecute(Application.Handle, PChar(‘open’), PChar(DestFile),

PChar(»), nil, SW_NORMAL)

end

else

ShowMessage(‘Error while downloading ‘ SourceFile)

end;

// Minimum availability: Internet Explorer 3.0

// Minimum operating systems Windows NT 4.0, Windows 95

{********************************************************}

{2.}

uses

Wininet;

function DownloadURL(const aUrl: string): Boolean;

var

hSession: HINTERNET;

hService: HINTERNET;

lpBuffer: array[0..1024 1] of Char;

dwBytesRead: DWORD;

begin

Result := False;

// hSession := InternetOpen( ‘MyApp’, INTERNET_OPEN_TYPE_DIRECT, nil, nil, 0);

hSession := InternetOpen(‘MyApp’, INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);

try

if Assigned(hSession) then

begin

hService := InternetOpenUrl(hSession, PChar(aUrl), nil, 0, 0, 0);

if Assigned(hService) then

try

while True do

begin

dwBytesRead := 1024;

InternetReadFile(hService, @lpBuffer, 1024, dwBytesRead);

if dwBytesRead = 0 then break;

lpBuffer[dwBytesRead] := #0;

Form1.Memo1.Lines.Add(lpBuffer);

end;

Result := True;

finally

InternetCloseHandle(hService);

end;

end;

finally

InternetCloseHandle(hSession);

end;

end;

{/codecitation}

Докачка файлов по сети (NetBios)

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Автор: http://forum.vingrad.ru

Мне вот все интересно было…думал проги, которые файлы по сети докачивают как-то хитро с нетбиосом работают…И вот решил попробовать написать нечто подобное…Токо похоже все они ( проги ) про нетбиос и слыхом не слыхивали…нашел простейшее решение этой проблемы… Код некрасивый, потому как писал чисто для проверки…более того, тут надо переписать его весь, но общий смысл должен быть понятен…я разочарован :((

procedure TForm1.Button1Click(Sender: TObject);

var

Stream,

Stream1: TFileStream;

Temp: array[0..$FFFF] of Byte;

Access: Integer;

FileNames, Filenames1: string;

begin

with TOpenDialog.Create(Form1) do

begin

Execute;

FileNames := FileName;

Free;

end;

if Filenames = » then

Exit;

with TSaveDialog.Create(Form1) do

begin

Execute;

FileNames1 := FileName;

Free;

end;

if Filenames1 = » then

Exit;

Access := fmOpenReadWrite;

ZeroMemory(@Temp, sizeof(Temp));

Stream := TFileStream.Create(FileNames, fmOpenRead);

if not FileExists(Filenames1) then

Access := fmCreate;

Stream1 := TFileStream.Create(Filenames1, Access);

Gauge1.MaxValue := Stream.Size;

Stream.Position := Stream1.Size;

Stream1.Position := Stream1.Size;

Label1.Caption := IntToStr(Stream1.Position);

Label2.Caption := IntToStr(Stream.Size);

Gauge1.Progress := Stream.Position;

while Stream.Size Stream1.Size do

begin

if (Stream.Size — Stream1.Position) < sizeof(Temp) then

begin

Stream1.CopyFrom(Stream, Stream.Size — Stream1.Position);

end

else

Stream1.CopyFrom(Stream, sizeof(Temp));

Gauge1.Progress := Stream.Position;

Label1.Caption := IntToStr(Stream.Position);

Label2.Caption := IntToStr(Stream.Size);

Form1.Update;

Application.ProcessMessages;

end;

Stream.Free;

Stream1.Free;

end;

{/codecitation}

Что такое сокет

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Мама приходит из аптеки и говорит дочке, которая сидит в чате:

— Я марганцовку купила.

— Рулез! — отвечает дочка не отрываясь от компа.

Мама через 5 минут раздумия:

— Нет не рулез, а марганцовку!

Существует мировой стандарт структуры протоколов связи — семиуровневая OSI (Open Systems Interface — интерфейс открытых систем). Hа каждом из уровней этой структуры решается свой объем задач своими методами. Сокеты находятся на так назывемом транспортном уровне — ниже находится сетевой протокол IP, выше — специализированные протоколы сеансового уровня, ориентированные на решение конкретных задач — это всем известные FTP, SMTP, etc.

Если смотреть по сути, сокет — это модель одного конца сетевого соединения, со всеми присущими ему свойствами, и, естественно — возможностью получать и передавать данные. По содержанию — это прикладной программный интерфейс, входящий в состав многих ОС. В семействе Windows — начиная с версии 3.11, и носит название WinSock. Прототипы функций WinSock API находятся в файле winsock.pas. В Delphi есть полноценная инкапсуляция клиентского и серверного сокетов, представленная компонентами TClientSocket и TServerSocket, находящимися на закладке Internet.

Сокеты не обязательно базируются на протоколе TCP/IP, они могут также базироваться на IPX/SPX, etc.

Также Вам следует ознакомиться со списком зарезервированных номеров портов.

Механизм взаимодействия сокетов таков. С одной из двух сторон запускается серверный сокет, который сразу после запуска находится в режиме прослушивания (listening), точнее — ожидания запросов от клиентов. После получения запроса от клиента устанавливается связь, и создается новый экземпляр серверного сокета.

Так как работа с сокетами, это, по сути — операции ввода/вывода, которые бывают синхронными или асинхронными, то и тип работы сокета обладает бывает синхронным или асинхронным. Компоненты TClientSock и TServerSock поддерживают оба режима работы.

Дополнение от Анатолия Подгорецкого:

Когда говорят СОКЕТ то часто не представляют, что это такое. Можно говорить об моделях, об реализациях и так далее. о есть одно простое толкование, применимое для протокола IP. Как известно для взаимодействия между машинами по протоколу IP используются адреса и порты.

Первое на текущий момент представляют из себя 32-x битный адрес, наиболее часто его представляют в символьной форме mmm.nnn.ppp.qqq (адрес разбитый на четыре октета по одному байту в октете и разделеный точками) .

Второе — это номер порта в диапазоне от нуля до 65535

Так вот эта пара и есть сокет (гнездо в в котором расположены адрес и порт).

В процессе обмена как правило используются два сокета — сокет отправителя и сокет получателя.

апример при обращении к моему серверу на HTTP порт сокет будет выглядеть так: 194.106.118.30:80, а ответ будет поступать на mmm.nnn.ppp.qqq:xxx

{/codecitation}

Сокеты в Delphi

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Данная статья посвящена созданию приложений архитектуры клиент/сервер в Borland Delphi на основе сокетов («sockets» — гнезда). В отличие от предыдущей статьи на тему сокетов, здесь мы разберем создание серверных приложений.

Следует сразу заметить, что для сосуществования отдельных приложений клиента и сервера не обязательно иметь несколько компьютеров. Достаточно иметь лишь один, на котором Вы одновременно запустите и сервер, и клиент. При этом нужно в качестве имени компьютера, к которому надо подключиться, использовать хост-имя localhost или IP-адрес — 127.0.0.1.

Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.

Алгоритм работы сокетного сервера

Что же позволяет делать сокетный сервер?.. По какому принципу он работает?.. Сервер, основанный на сокетном протоколе, позволяет обслуживать сразу множество клиентов. Причем, ограничение на их количество Вы можете указать сами (или вообще убрать это ограничение, как это сделано по умолчанию). Для каждого подключенного клиента сервер открывает отдельный сокет, по которому Вы можете обмениваться данными с клиентом. Также отличным решением является создание для каждого подключения отдельного процесса (Thread).

Ниже следует примерная схема работы сокетного сервера в Дельфи-приложениях:

Разберем схему подробнее:

Определение св-в Port и ServerType — чтобы к серверу могли нормально подключаться клиенты, нужно, чтобы порт, используемый сервером точно совпадал с портом, используемым клиентом (и наоборот). Свойство ServerType определяет тип подключения (подробнее см.ниже);

Открытие сокета — открытие сокета и указанного порта. Здесь выполняется автоматическое начало ожидания подсоединения клиентов (Listen);

Подключение клиента и обмен данными с ним — здесь подключается клиент и идет обмен данными с ним. Подробней об этом этапе можно узнать ниже в этой статье и в статье про сокеты (клиентская часть);

Отключение клиента — Здесь клиент отключается и закрывается его сокетное соединение с сервером;

Закрытие сервера и сокета — По команде администратора сервер завершает свою работу, закрывая все открытые сокетные каналы и прекращая ожидание подключений клиентов.

Следует заметить, что пункты 3-4 повторяются многократно, т.е. эти пункты выполняются для каждого нового подключения клиента.

Примечание:

Документации по сокетам в Дельфи на данный момент очень мало, так что, если Вы хотите максимально глубоко изучить эту тему, то советую просмотреть литературу и электронную документацию по Unix/Linux-системам — там очень хорошо описана теория работы с сокетами. Кроме того, для этих ОС есть множество примеров сокетных приложений (правда, в основном на C/C и Perl).

Краткое описание компонента TServerSocket

Здесь мы познакомимся с основными свойствами, методами и событиями компонента TServerSocket.

Свойства Методы События:

Socket

класс TServerWinSocket, через который Вы имеете доступ к открытым сокетным каналам. Далее мы рассмотрим это свойство более подробно, т.к. оно, собственно и есть одно из главных. Тип: TServerWinSocket;

ServerType

тип сервера. Может принимать одно из двух значений: stNonBlocking — синхронная работа с клиентскими сокетами. При таком типе сервера Вы можете работать с клиентами через события OnClientRead и OnClientWrite. stThreadBlocking — асинхронный тип. Для каждого клиентского сокетного канала создается отдельный процесс (Thread). Тип: TServerType;

ThreadCacheSize

количество клиентских процессов (Thread), которые будут кэшироваться сервером. Здесь необходимо подбирать среднее значение в зависимости от загруженности Вашего сервера. Кэширование происходит для того, чтобы не создавать каждый раз отдельный процесс и не убивать закрытый сокет, а оставить их для дальнейшего использования. Тип: Integer;

Active

показатель того, активен в данных момент сервер, или нет. Т.е., фактически, значение True указывает на то, что сервер работает и готов к приему клиентов, а False — сервер выключен. Чтобы запустить сервер, нужно просто присвоить этому свойству значение True. Тип: Boolean;

Port

номер порта для установления соединений с клиентами. Порт у сервера и у клиентов должны быть одинаковыми. Рекомендуются значения от 1025 до 65535, т.к. от 1 до 1024 — могут быть заняты системой. Тип: Integer;

Service

строка, определяющая службу (ftp, http, pop, и т.д.), порт которой будет использован. Это своеобразный справочник соответствия номеров портов различным стандартным протоколам. Тип: string;

Open

Запускает сервер. По сути, эта команда идентична присвоению значения True свойству Active;

Close

Останавливает сервер. По сути, эта команда идентична присвоению значения False свойству Active.

OnClientConnect

возникает, когда клиент установил сокетное соединение и ждет ответа сервера (OnAccept);

OnClientDisconnect

возникает, когда клиент отсоединился от сокетного канала;

OnClientError

возникает, когда текущая операция завершилась неудачно, т.е. произошла ошибка;

OnClientRead

возникает, когда клиент передал берверу какие-либо данные. Доступ к этим данным можно получить через пеаедаваемый параметр Socket: TCustomWinSocket;

OnClientWrite

возникает, когда сервер может отправлять данные клиенту по сокету;

OnGetSocket

в обработчике этого события Вы можете отредактировать параметр ClientSocket;

OnGetThread

в обработчике этого события Вы можете определить уникальный процесс (Thread) для каждого отдельного клиентского канала, присвоив параметру SocketThread нужную подзадачу TServerClientThread;

OnThreadStart, OnThreadEnd

возникает, когда подзадача (процесс, Thread) запускается или останавливается, соответственно;

OnAccept

возникает, когда сервер принимает клиента или отказывает ему в соединении;

OnListen

возникает, когда сервер переходит в режим ожидания подсоединения клиентов.

TServerSocket.Socket (TServerWinSocket)

Итак, как же сервер может отсылать данные клиенту? А принимать данные? В основном, если Вы работаете через события OnClientRead и OnClientWrite, то общаться с клиентом можно через параметр ClientSocket (TCustomWinSocket). Про работу с этим классом можно прочитать в статье про клиентские сокеты, т.к. отправка/посылка данных через этот класс аналогична — методы (Send/Receive)(Text,Buffer,Stream). Также и при работе с TServerSocket.Socket. Однако, т.к. здесь мы рассматриваем сервер, то следует выделить некоторые полезные свойства и методы:

ActiveConnections (Integer)

количество подключенных клиентов;

ActiveThreads (Integеr)

количество работающих процессов; Connections (array) — массив, состоящий из отдельных классов TClientWinSocket для каждого подключенного клиента. Например, такая команда:

ServerSocket1.Socket.Connections[0].SendText(‘Hello!’);

отсылает первому подключенному клиенту сообщение ‘Hello!’. Команды для работы с элементами этого массива — также (Send/Receive)(Text,Buffer, Stream);

IdleThreads (Integer)

количество свободных процессов. Такие процессы кэшируются сервером (см. ThreadCacheSize);

LocalAddress, LocalHost, LocalPort

соответственно — локальный IP-адрес, хост-имя, порт;

RemoteAddress, RemoteHost, RemotePort

соответственно — удаленный IP-адрес, хост-имя, порт;

Методы Lock и UnLock

соответственно, блокировка и разблокировка сокета.

Практика и примеры

А теперь рассмотрим вышеприведенное на конкретном примере. Скачать уже готовые исходники можно, щелкнув здесь.

Итак, рассмотрим очень неплохой пример работы с TServerSocket (этот пример — наиболее наглядное пособие для изучения этого компонента). В приведенных ниже исходниках демонстрируется протоколирование всех важных событий сервера, плюс возможность принимать и отсылать текстовые сообщения:

Пример 1. Протоколирование и изучение работы сервера, посылка/прием сообщений через сокеты.

{… Здесь идет заголовок файла и определение

формы TForm1 и ее экземпляра Form1}

procedure TForm1.Button1Click(Sender: TObject);

begin

{Определяем порт и запускаем сервер}

ServerSocket1.Port := 1025;

{Метод Insert вставляет строку в массив в указанную позицию}

Memo2.Lines.Insert(0, ‘Server starting’);

ServerSocket1.Open;

end;

procedure TForm1.Button2Click(Sender: TObject);

begin

{Останавливаем сервер}

ServerSocket1.Active := False;

Memo2.Lines.Insert(0, ‘Server stopped’);

end;

procedure TForm1.ServerSocket1Listen(Sender: TObject;

Socket: TCustomWinSocket);

begin

{Здесь сервер «прослушивает» сокет на наличие клиентов}

Memo2.Lines.Insert(0, ‘Listening on port ‘ IntToStr(ServerSocket1.Port));

end;

procedure TForm1.ServerSocket1Accept(Sender: TObject;

Socket: TCustomWinSocket);

begin

{Здесь сервер принимает клиента}

Memo2.Lines.Insert(0, ‘Client connection accepted’);

end;

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

{Здесь клиент подсоединяется}

Memo2.Lines.Insert(0, ‘Client connected’);

end;

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

{Здесь клиент отсоединяется}

Memo2.Lines.Insert(0, ‘Client disconnected’);

end;

procedure TForm1.ServerSocket1ClientError(Sender: TObject;

Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;

var ErrorCode: Integer);

begin

{Произошла ошибка — выводим ее код}

Memo2.Lines.Insert(0, ‘Client error. Code = ‘ IntToStr(ErrorCode));

end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;

Socket: TCustomWinSocket);

begin

{От клиента получено сообщение — выводим его в Memo1}

Memo2.Lines.Insert(0, ‘Message received from client’);

Memo1.Lines.Insert(0, ‘> ‘ Socket.ReceiveText);

end;

procedure TForm1.ServerSocket1ClientWrite(Sender: TObject;

Socket: TCustomWinSocket);

begin

{Теперь можно слать данные в сокет}

Memo2.Lines.Insert(0, ‘Now can write to socket’);

end;

procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer;

var ClientSocket: TServerClientWinSocket);

begin

Memo2.Lines.Insert(0, ‘Get socket’);

end;

procedure TForm1.ServerSocket1GetThread(Sender: TObject;

ClientSocket: TServerClientWinSocket;

var SocketThread: TServerClientThread);

begin

Memo2.Lines.Insert(0, ‘Get Thread’);

end;

procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject;

Thread: TServerClientThread);

begin

Memo2.Lines.Insert(0, ‘Thread end’);

end;

procedure TForm1.ServerSocket1ThreadStart(Sender: TObject;

Thread: TServerClientThread);

begin

Memo2.Lines.Insert(0, ‘Thread start’);

end;

procedure TForm1.Button3Click(Sender: TObject);

var i: Integer;

begin

{Посылаем ВСЕМ клиентам сообщение из Edit1}

for i := 0 to ServerSocket1.Socket.ActiveConnections — 1 do

ServerSocket1.Socket.Connections[i].SendText(Edit1.Text);

Memo1.Lines.Insert(0, ‘< ' Edit1.Text);

end;

Далее мы будем рассматривать уже не примеры, а приемы работы с TServerSocket.

Приемы работы с TServerSocket (и просто с сокетами)

Хранение уникальных данных для каждого клиента.

Наверняка, если Ваш сервер будет обслуживать множество клиентов, то Вам потребуется хранить какую-либо информацию для каждого клиента (имя, и др.), причем с привязкой этой информации к сокету данного клиента. В некоторых случаях делать все это вручную (привязка к handle сокета, массивы клиентов, и т.д.) не очень удобно. Поэтому для каждого сокета существует специальное свойство — Data. На самом деле, Data — это всего-навсего указатель. Поэтому, записывая данные клиента в это свойство будьте внимательны и следуйте правилам работы с указателями (выделение памяти, определение типа, и т.д.)!

Посылка файлов через сокет

Здесь мы рассмотрим посылку файлов через сокет (по просьбе JINX-а) :-). Итак, как же послать файл по сокету? Очень просто! Достаточно лишь открыть этот файл как файловый поток (TFileStream) и отправить его через сокет (SendStream)! Рассмотрим это на примере:

{Посылка файла через сокет}

procedure SendFileBySocket(filename: string);

var srcfile: TFileStream;

begin

{Открываем файл filename}

srcfile := TFileStream.Create(filename, fmOpenRead);

{Посылаем его первому подключенному клиенту}

ServerSocket1.Socket.Connections[0].SendStream(srcfile);

{Закрываем файл}

srcfile.Free;

end;

Нужно заметить, что метод SendStream используется не только сервером, но и клиентом (ClientSocket1.Socket.SendStream(srcfile))

Почему несколько блоков при передаче могут обьединяться в один

Это тоже по просьбе JINX-а :-). За это ему огромное спасибо! Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет — обычный поток, но в отличие, скажем, от файлового (TFileStream), он передает данные медленнее (сами понимаете — сеть, ограниченный трафик, и т.д.). Именно поэтому две команды:

ServerSocket1.Socket.Connections[0].SendText(‘Hello, ‘);

ServerSocket1.Socket.Connections[0].SendText(‘world!’);

совершенно идентичны одной команде:

ServerSocket1.Socket.Connections[0].SendText(‘Hello, world!’);

И именно поэтому, если Вы отправите через сокет файл, скажем, в 100 Кб, то тому, кому Вы посылали этот блок, придет несколько блоков с размерами, которые зависят от трафика и загруженности линии. Причем, размеры не обязательно будут одинаковыми. Отсюда следует, что для того, чтобы принять файл или любые другие данные большого размера, Вам следует принимать блоки данных, а затем объединять их в одно целое (и сохранять, например, в файл). Отличным решением данной задачи является тот же файловый поток — TFileStream (либо поток в памяти — TMemoryStream). Принимать частички данных из сокета можно через событие OnRead (OnClientRead), используя универсальный метод ReceiveBuf. Определить размер полученного блока можно методом ReceiveLength. Также можно воспользоваться сокетным потоком (см. статью про TClientSocket). А вот и небольшой примерчик (приблизительный):

{Прием файла через сокет}

procedure TForm1.ClientSocket1Read(Sender: TObject;

Socket: TCustomWinSocket);

var

l: Integer;

buf: PChar;

src: TFileStream;

begin

{Записываем в l размер полученного блока}

l := Socket.ReceiveLength;

{Заказываем память для буфера}

GetMem(buf,l 1);

{Записываем в буфер полученный блок}

Socket.ReceiveBuf(buf,l);

{Открываем временный файл для записи}

src := TFileStream.Create(‘myfile.tmp’,fmOpenReadWrite);

{Ставим позицию в конец файла}

src.Seek(0,soFromEnd);

{Записываем буфер в файл}

src.WriteBuffer(buf,l);

{Закрываем файл}

src.Free;

{Освобождаем память}

FreeMem(buf);

end;

Как следить за сокетом

Это вопрос сложный и требует долгого рассмотрения. Пока лишь замечу, что созданный Вашей программой сокет Вы можете промониторить всегда :-). Сокеты (как и большинство объектов в Windows) имеют свой дескриптор (handle), записанный в свойстве Handle. Так вот, узнав этот дескриптор Вы свободно сможете управлять любым сокетом (даже созданным чужой программой)! Однако, скорее всего, чтобы следить за чужим сокетом, Вам придется использовать исключительно функции WinAPI Sockets.

Эпилог

В этой статье отображены основные приемы работы с компонентом TServerSocket в Дельфи и несколько общих приемов для обмена данными по сокетам. Если у Вас есть вопросы — скидывайте их мне на E-mail: snick@mailru.com, а еще лучше — пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!

{/codecitation}

Сокеты 4 (Использование DScan)

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

— Миколо, ты що свою домашню строныцю на домен «ru» засувов?

— А шо?

— Так то ж «Раша»!

— От, гады! А я думав, Ридно Украина!

В этой статье я расскажу примеры использования моей программы «DScan». Вместе с исходниками и предыдущими статьями так будет легче понять принцип работы сокетного движка. Первые статьи и исходники можно взять на http://www.danil.dp.ua

Сканер по диапазону

Сканер по диапазону портов или адресов предназначен для определения открытых портов на удаленном компьютере или открытого определенного порта по диапазону адресов. Таким образом можно просканировать весь диапазон адресов провайдера на 139 открытый порт. Или 10001 (DTr). Или по порту BO, LamerDeath и т.п. Для этого необходимо указать диапазон адресов в спецификации ip4 (xxx.xxx.xxx.xxx) и порт. Сканирование осуществляется в многопоточном режиме по изменению последнего значения. Количество потоков можно задать и оно зависит от скорости соединения. В версии 1.3 сканирование реализовано через неблокирующие сокеты (см. предыдущие статьи). Сканирование по диапазону портов в основном предназначено для определения установленных сервисов на удаленном компьютере. Таким образом можно выяснить и операционную систему компьютера, подключен ли он еще и в локальную сеть, установлен ли Apach, E-mail-SERVER, FireWall и т.д.

Сканер по выбранным портам

То же самое, что и сканер по диапазону портов, но можно задавать значения только тех портов, по которым необходимо просканировать удаленный компьютер. В версии 1.3 добавлена многопоточность сканирования и алгоритм написан на неблокирующем сокетном движке.

Универсальный TCP-клиент

После определения открытых портов на удаленном компьютере, можно с помощью универсального TCP-клиента выяснить тип установленного сервиса, протокол обмена данными, его ответ на попытку соединения, сколько байт отправляет при соединении, сколько получает и т.д. Может работать как TelNet-клиент, e-mail-клиент, броузер, клиент различных BackDoor, Whois-клиент и т.п. В общем — универсальный клиент. Также есть опции отправки/приема данных в виде Out-of-Band. Можно указать разное окончание отправляемого пакета и сгенерировать данные случайным образом. Область применения очень обширна.

Флудер

Два примера использования можно взять в третьей статье по этой теме. Также он может использоваться для флуда чата, гостевой и т.п. Принцип работы — посылка на указанный адрес и открытый порт указанного кол-ва сформированных пакетов. Пакеты можно формировать случайным образом, заполнять нулями, #1, #13, #10, буквой A, #10#13, /n,

или пробелами. Справа и слева сформированных данных можно ввести обязательные для этого пакета данные (для чатов, гостевых и т.д.). Вот пример. Возьму мою гостевую книгу на http://www.danil.dp.ua. После некоторых исследований port-mapper-ом, можно получить, что url для добавления —

GET http://www.danil.dp.ua/cgi-bin/add.pl?name=1111

Сокеты 1

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

Автор: Danil

WEB-сайт: http://www.danil.dp.ua

Приходит девица под утро с интернет-тусовки, вся в синяках, заплаканная:

— Мама! Меня хакнули!

В этой статье я немного расскажу о сокетах и о граблях, на которые я понаступал, программируя различные клиентские и серверные приложения на протоколе TCP/IP. Постараюсь объяснить простым языком для неспециалистов. Здесь будут даны самые начальные сведения и будет попытка обобщения. В некоторых статьях есть такая фраза — «для … необходимо знать это и то, а для тех кто не знает — идите смотрите там, не знаю где». Теперь будет ясно «где»; и эти статьи, я думаю, могут быть справочником в дальнейшем. Будет рассмотренна работа с сокетами в m$ windows. Для программирования сокетов в никсах различие очень незначительны (все функции и структуры мелкософт постарался без изменений передрать) и основные из них рассмотрены в статьях, ссылки на которые приведены в конце, в разделе «Что еще почитать». Программа, использующая сокеты, может работать с одним сокетом или с множеством одноременно «открытых» сокетов (сокетный движок). Сразу стоит выделить различие между блокирующими (асинхронными) и неблокирующими (синхронными

, требующими синхронизацию) сокетами.

Попытка соединения и приема данных может быть вызванна в блокирующем режиме (отправка всегда неблокирующая — просто добавление в очередь сокета данных). Это значит, что пока программа не соеденится или не примет данные, передача управления на следующий оператор не происходит. Это подходит для приложений, использующих один сокет, где ожидание приема данных обязательно, в программах с сокетным движком (много сокетов), где создается несколько отдельно работающих процессов с линейными алгоритмами и т.п.

Или же работа с сокетным движком может бытиь реализована в неблокирующеим режиме. При этом сразу же, не дожидаясь соединения или приема данных, происходит передача управления на следующий оператор программы. В последующем алгоритме необходимо использовать оператор select (переключение) и делать опросы сокетов на предмет приняты/нет данные. Обычно это выполняется в цикле — выход по установленному тайм-ауту (количество произведенных опросов сокета, при котором можно считать, что произошла ошибка или скорость соединения неудовлетворительна) или с использованием работы с назначением и реакцией сообщений операционной системы.

Итак, вначале некоторый ликбез. Программы, о которых пойдет речь ниже, делятся на клиенты и серверы. Вначале необходимо рассмотреть минимум используемых функций, для работы этих программ. В m$ windows работой с сокетами «заведует» winsock.dll. Для разных языков программирования синтаксис вызова функций из этой DLL незначителен. В Delphi, например, в катологе Source находится файл winsock.pas, который всего лишь объявляет нужные функции и структуры данных. При подгрузке модуля WinSock в uses, их можно вызывать с синтаксисом паскаля, но от этого принцип их работы не изменится. Кстати, не советовал бы использовать стандартные компоненты Delphi и Builder (TServerSocket, TClientSocket) из-за их глючности. Если не очень хочется использовать стандартные winsock-функции, то можно взять набор компонент Indy. Вот функции winsock:

int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData);

говорит оси, что во всех процессах программы могут быть использованы функции WinSock. Должна вызываться самой первой.

int WSACleanup;

«деинициализирует» WSAStartup.

SOCKET socket (int af, int type, int protocol);

создает сокет. Второй параметр — вид данных, третий — вид протокола. Порт и адрес задается в функции bind (сервер) или connect (клиент).

int closesocket (SOCKET s);

закрывает сокет.

int bind (SOCKET s, const struct sockaddr FAR* name, int namelen);

ассоциирует адрес с сокетом. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для сервера обычно указывается INADDR_ANY — любой).

int connect (SOCKET s, const struct sockaddr FAR* name, int namelen);

функция соединения для клиента. Структура адреса содержит порт (необходимо привести функцией htons) и адрес (для клиента необходимо привести из имени или спецификации ip4 — xxx.xxx.xxx.xxx).

int WSAAsyncSelect (SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);

связывает сокет с получением сообщений окна. Обычно используется для сервера. При вызове этой функции, сообщения о соединении, чтении/записи данных в сокет и закрытии сокета можно обрабатывать в функции обработки сообщений от окна.

int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);

контроль состояния сокета. Используется для неблокирующих вызовов, чтобы получить состояние сокета на данный момент, с использованием макросов FD_.

int WSAEventSelect (SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);

связывает сокет с получением сообщений операционной системы. Можно проинициализировать необходимый Event (см. документацию) и обрабатывать сообщения FD_ без использования окна m$ windows.

int send (SOCKET s, const char FAR * buf, int len, int flags);

отправка данных. Помещает в очередь сокета s, кусок данных из buf, длиной len. Последний параметр отвечает за вид передачи сообщения. Может быть проигнорирован.

int recv (SOCKET s, char FAR* buf, int len, int flags);

получение данных.

Кстати, и в блокирующих и в неблокирующих сокетах, используется так или иначе функция […]select. Только опрос о состоянии очереди сокета мы производим сами (неблокирующий режим) или это делает операционная система (блокирующий).

Преобразование адреса

Вот пример функции на Delphi, которая преобразует адрес из имени или спецификации ip4, в четырехбайтное значение, требующееся для структуры sockaddr_in, с которой работает сокет:

uses WinSock;

function d_addr(IPaddr : string) : Cardinal;

var

pa: PChar;

sa: TInAddr;

Host: PHostEnt;

begin

Result:=inet_addr(PChar(IPaddr));

// Перевод если адреа не в ip4

if Result=INADDR_NONE then

begin

host:=GetHostByName(PChar(IPaddr));

if Host = nil then

exit

else

begin

// Преобразование

pa := Host^.h_addr_list^;

sa.S_un_b.s_b1 := pa[0];

sa.S_un_b.s_b2 := pa[1];

sa.S_un_b.s_b3 := pa[2];

sa.S_un_b.s_b4 := pa[3];

with TInAddr(sa).S_un_b do

Result:=inet_addr(PChar(IntToStr(Ord(s_b1)) ‘.’ IntToStr(Ord(s_b2)) ‘.’

IntToStr(Ord(s_b3)) ‘.’ IntToStr(Ord(s_b4))));

end;

end;

end;

Использование блокировки в некоторых программах:

Сервер. Проще говоря, задача сервера открыть порт на компьютере и «висеть», принимая команды или некоторые данные от клиента по инициализированному порту. Например, FTP-сервер открывает 21 порт и обрабатывая определенные команды от клиента, выполняет необходимые операции с файловой системой. POP3-сервер открывает 110 порт и занимается приемом электронных сообщений (e-mail). SMTP (25 порт) — отправка писем. TelNet (23), SSh (22) и т.д. Обычно используются блокирующие сокеты, потому что следующие за приемом данных операторы — это обработка принятых данных. Нет смысла делать передачу на них управления, пока эти данные не приняты. Сервер может «занять» несколько портов, принимать данные от множества клиентов, но все равно из-за постоянного опроса select инициализированных сокетов и черезчур больших требований к алгоритму корректной обработки данных, это не лучшее решение для сервера. Хотя есть исключения и все зависит от приверженности программиста к тому или иному способу.

Клиент. Задача клиента — соедениться с сервером и посылать ему (принимать от него) данные. Естественно, если клиент пытается соедениться по порту, на котором по указанному адресу не «висит» сервер, то будет ошибка соединения. Если сервер проинициализирован на другой протокол (UDP, ICMP, TCP,…) или вид обмена данными, а клиент на другой, то соединение скорее всего произойдет, но обмен данными станет невозможен. В случае, если клиент работает с сокетным движком (несколько сокетов), то я обычно использую блокирующие сокеты, работающие каждый на своей нити (Threads) или процессе (Process) с минимальным приоритетом. Таким образом достигается необходимая многозадачность, параллельность работы и, в то же время, удобно обрабатывать принятые/отправленные данные в несинхронном режиме работы. Таким образом достигается упрощение алгоритма и минимальное количество выполняемых операций из-за того, что нет необходимости постоянно вызывать select.

Различные сканеры, брутфорсеры. Задача — создать максимальное количество сокетов, поддерживаемое операционной системой для достижения максимальной скорости сканирования или перебора. Здесь надо очень хорошо продумывать сокетный движок. Предыдущий способ создания клиента хорош для относительно небольшого количества одновременно проинициализированных сокетов. Иначе, из-за очень большого количества однновременно запущенных процессов, повышается нагрузка на ядро системы. Здесь все зависит от скорости соединения и компьютерного «железа». Можно использовать как блокирующие, так и неблокирующие сокеты. При современном развитии компьютерного «железа» и относительно небольшой скорости соединения (DialUp, выделенка с низкой скоростью), я бы порекомендовал использовать способ, рассмотренный выше. Количество нитей 255 вполне «потянет» практически любой компьютер, а большее количество из-за скорости соединения использовать нет смысла. При очень хорошем канале (можно создать практически неограниченное количество сокетов) и

ли не очень хорошем компьютере (всего один процесс обработки), надо все-таки использовать неблокирующие сокеты. Несмотря на эти преимущества, кроме рассмотренных недостатков синхронных сокетов, будет рассмотрено еще несколько в примерах, во второй статье.

Создание отдельной нити, процесса (Threads)

Для создания отдельного процесса обработки сокета при использовании сокетного движка (несколько сокетов, которые должны работать параллельно), необходимо этот процесс описать. Вот небольшой пример описания, создания и завершение процесса на Delphi:

uses WinSock;

//Описываем процесс как класс, типа TThread

type

TScaner = class(TThread)

Sock : TSocket;

private

protected

procedure Execute; override;

procedure Run;

end;

//забиваем место в памяти под процесс

var

Scaner : TScaner;

//фунцция, вызываемая при создании процесса

procedure TScaner.Execute;

var

begin

//запуск дополнительной фунцции процесса

Synchronize(Run);

//закрытие сокета

closesocket(Sock)

//Прервать процесс

Terminate;

end;

//дополнительная фунцция

procedure TScaner.Run;

var

begin

end;

//программа

begin

//создать процесс, но пока не запускать

Scaner:=TScaner.Create(true);

//Освободить память при прерывании процесса

Scaner.FreeOnTerminate:=true;

//установить приоритет

Scaner.Priority:=tpLowest;

//запустить процесс

Scaner.Resume;

end.

Этот прием очень удобен, например, для вызова обработки сокета и прерывания по какой-либо клавише. На клавишу «Start» цепляем создание процесса, а на клавишу «Stop» — Scaner.Terminate. Можно также описать процедуру Terminate процесса, где будет closesocket. Этот процесс будет работать независимо от основной программы. Правда для синхронизации его с VCL главного окна, его необходимо немного дописать. Этот прием одинаково удобен и для неблокирующего (создается процесс, в котором уже идет цикл по select по множеству сокетов) и для блокирующего сокета (создается много процессов и для каждого свой сокет) при написании различных клиентов и сканеров. В частности, он использовался мной для написания моей многонитевой программы «DScan», которая включает универсальный клиент, сканер, брутфорсер и предоставленна со всеми исходниками.

Для получения первичных сведений, поданной информации вполне достаточно. Во второй части будут приводится примеры простейших программ с использованием сокета и с подробными комментариями.

P.S. Статья и программа предоставлена в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

{/codecitation}

Создание чата

{codecitation class=»brush: pascal; gutter: false;» width=»600px»}

unit main;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

Menus, StdCtrls, Buttons, ScktComp, ExtCtrls, ComCtrls;

type

TChatForm = class(TForm)

MainMenu1: TMainMenu;

File1: TMenuItem;

Exit1: TMenuItem;

FileConnectItem: TMenuItem;

FileListenItem: TMenuItem;

StatusBar1: TStatusBar;

Bevel1: TBevel;

Panel1: TPanel;

Memo1: TMemo;

Memo2: TMemo;

N1: TMenuItem;

SpeedButton1: TSpeedButton;

Disconnect1: TMenuItem;

ServerSocket: TServerSocket;

ClientSocket: TClientSocket;

procedure FileListenItemClick(Sender: TObject);

procedure FileConnectItemClick(Sender: TObject);

procedure Exit1Click(Sender: TObject);

procedure Memo1KeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

procedure FormCreate(Sender: TObject);

procedure ServerSocketError(Sender: TObject; Number: Smallint;

var Description: string; Scode: Integer; const Source,

HelpFile: string; HelpContext: Integer; var CancelDisplay: Wordbool);

procedure Disconnect1Click(Sender: TObject);

procedure ClientSocketConnect(Sender: TObject;

Socket: TCustomWinSocket);

procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket);

procedure ServerSocketClientRead(Sender: TObject;

Socket: TCustomWinSocket);

procedure ServerSocketAccept(Sender: TObject;

Socket: TCustomWinSocket);

procedure ServerSocketClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

procedure ClientSocketDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket;

ErrorEvent: TErrorEvent; var ErrorCode: Integer);

procedure ServerSocketClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

protected

IsServer: Boolean;

end;

var

ChatForm: TChatForm;

Server: string;

implementation

{$R *.DFM}

procedure TChatForm.FileListenItemClick(Sender: TObject);

begin

FileListenItem.Checked := not FileListenItem.Checked;

if FileListenItem.Checked then

begin

ClientSocket.Active := False;

ServerSocket.Active := True;

Statusbar1.Panels[0].Text := ‘Listening…’;

end

else

begin

if ServerSocket.Active then

ServerSocket.Active := False;

Statusbar1.Panels[0].Text := »;

end;

end;

procedure TChatForm.FileConnectItemClick(Sender: TObject);

begin

if ClientSocket.Active then

ClientSocket.Active := False;

if InputQuery(‘Computer to connect to’, ‘Address Name:’, Server) then

if Length(Server) > 0 then

with ClientSocket do

begin

Host := Server;

Active := True;

FileListenItem.Checked := False;

end;

end;

procedure TChatForm.Exit1Click(Sender: TObject);

begin

ServerSocket.Close;

ClientSocket.Close;

Close;

end;

procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;

Shift: TShiftState);

begin

if Key = VK_Return then

if IsServer then

ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1.Lines.Count

— 1])

else

ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count — 1]);

end;

procedure TChatForm.FormCreate(Sender: TObject);

begin

FileListenItemClick(nil);

end;

procedure TChatForm.ServerSocketError(Sender: TObject; Number: Smallint;

var Description: string; Scode: Integer; const Source, HelpFile: string;

HelpContext: Integer; var CancelDisplay: Wordbool);

begin

ShowMessage(Description);

end;

procedure TChatForm.Disconnect1Click(Sender: TObject);

begin

ClientSocket.Active := False;

ServerSocket.Active := True;

Statusbar1.Panels[0].Text := ‘Listening…’;

end;

procedure TChatForm.ClientSocketConnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

Statusbar1.Panels[0].Text := ‘Connected to: ‘ Socket.RemoteHost;

end;

procedure TChatForm.ClientSocketRead(Sender: TObject;

Socket: TCustomWinSocket);

begin

Memo2.Lines.Add(Socket.ReceiveText);

end;

procedure TChatForm.ServerSocketClientRead(Sender: TObject;

Socket: TCustomWinSocket);

begin

Memo2.Lines.Add(Socket.ReceiveText);

end;

procedure TChatForm.ServerSocketAccept(Sender: TObject;

Socket: TCustomWinSocket);

begin

IsServer := True;

Statusbar1.Panels[0].Text := ‘Connected to: ‘ Socket.RemoteAddress;

end;

procedure TChatForm.ServerSocketClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

Memo2.Lines.Clear;

end;

procedure TChatForm.ClientSocketDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

FileListenItemClick(nil);

end;

procedure TChatForm.ClientSocketError(Sender: TObject;

Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;

var ErrorCode: Integer);

begin

Memo2.Lines.Add(‘Error connecting to : ‘ Server);

ErrorCode := 0;

end;

procedure TChatForm.ServerSocketClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

Statusbar1.Panels[0].Text := ‘Listening…’;

end;

end.

{/codecitation}