Сокеты 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}

Разбиение полученных данных через ClientSocket и ServerSocket, когда приходит несколько пакетов в одном

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

Автор: Cyborg

WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****

>> Разбиение полученных данных через ClientSocket и ServerSocket,

когда приходит несколько пакетов в одном.

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

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

Зависимости: System, ScktComp

Автор: cyborg, cyborg1979@newmail.ru, ICQ:114205759, Бузулук

Copyright: Собственное написание (Осипов Евгений Анатольевич)

Дата: 23 мая 2002 г.

***************************************************** }

{Разделитель строк, добавляется в конец отсылаемых данных,

можно написать любой, какой нравится/какой вам подойдёт}

const

Delitel: string = #13#10;

{Процедура обработки строк, S-передаваемая строка,

Socket — Передаваемый сокет откуда пришли данные}

procedure Process_Message(var S: AnsiString; var Socket: TCustomWinSocket);

begin

{Обрабатываем здесь полученные данные S из сокета Socket}

end;

{Обработака события по получению данных из ClientSocket,

а так же действенно и для ServerSocket}

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

var

S1, S2: AnsiString;

I: Integer;

begin

S1 := Socket.ReceiveText; {Присваиваем S1 полученную строку из Socket}

I := 0;

repeat {Разделяем строку, если одновременно пришло несколько}

I := Pos(Delitel, S1); {Ищем раздлелитель Delitel в строке S1}

if I 0 then {Если найден делитель, тогда …

Если не найден, то вы не добавили в конце отсылаемой строки Delitel !}

begin

S2 := Copy(S1, 1, I — 1); {Копируем одну пришедшую строку в S2}

Delete(S1, 1, I Length(Delitel) — 1); {Удаляем уже скопированную строку}

Process_Message(S2, Socket);

{Вызываем процедуру обработки полученных строк,

где S2 наша выделенная строка}

end;

until I = 0; {Повторять, пока будет не найден разделитель Delitel}

end;

Пример использования:

// При посылке данных в socket нужно

// не забывать прибавлять разделитель строк:

Socket.SendText(‘Отправляемая строка’ Delitel);

// или

S := ‘Отправляемая строка’ Delitel;

Socket.SendText(S);

// или

S := ‘Отправляемая строка’;

Socket.SendText(S Delitel);

{/codecitation}

Работа с Winsock на Delphi

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

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

Наверное, все, кто хотя бы немного работал с Delphi, сталкивались с компонентами закладки Internet, а именно с TServerSocket и TClientSocket. Эти два невизуальных компонента очень просты в использовании и вполне пригодны для выполнения стандартных задач. Но что делать, если мы хотим написать приложение малого размера или нам необходим больший контроль над сокетом, чем дают стандартные компоненты? В этом случае необходимо использовать средство Windows для работы с сокетами, чем и является библиотека winsock.dll.

Заголовки всех процедур и функций, находящихся в этой dll-ке можно найти в Delphi5\source\rtl\win\winsock.pas.

Я кратко опишу те из них, который понадобятся для понимания нижеприведенного исходника:

1. function WSAStartup(wVersionRequired: word; var WSData: TWSAData): Integer; Входящий параметр wVersionRequired — наивысшая версия сокетов Windows, которую можно использовать. Исходящий параметр WSData — указатель на структуру данных WSAData. В случае успешного завершения функция возвращает значение ноль, в противном случае код ошибки. Эта функция должна быть ОБЯЗАТЕЛЬНО вызвана один раз, перед началом работы с сокетами.

2. function inet_addr(cp: PChar): u_long; Входящий параметр cp — нуль терминальная строка. Если нет ошибок, функция возвращает стандартный IP адрес для использования по протоколу TCP\IP. В случае ошибки возвращаемое значение — INADDR_NONE.

3. function htons(hostshort: u_short): u_short; Входящий параметр hostshort — число (16 битное). Функция возвращает 16 битный номер в специальном формате, который можно использовать в протоколе TCP\IP.

4. function socket(af, Struct, protocol: Integer): TSocket; Входящий параметр af — спецификация семейства сокетов, может принимать значение AF_INET, AF_IPX и др. struct — спецификация типа нового сокета (принимает значение SOCK_STREAM или SOCK_DGRAM). protocol — специфический протокол, который будет использоваться сокетом (если не хочешь ничего специфического — пиши 0). Если функция выполнена без ошибок, она возвращает дескриптор на новый сокет, если ошибки есть возвращается INVALID_SOCKET. Код ошибки можно узнать, вызвав функцию WSAGetLastError.

5. function connect(s: TSocket; var name: TSockAddr; namelen: Integer): Integer; Входящий параметр s — дескриптор, идентифицырующий сокет (это значение возвращает функция socket). name — имя с которым будет связан сокет после коннекта, namelen — длинна этого имени (легко можно получить, используя функцию sizeof). В случае успешного коннекта функция возвращает значение ноль. Если коннект не удался, возвращаемое значение — SOCKET_ERROR (код ошибки можно получить, используя функцию WSAGetLastError).

6. function WSACleanup: Integer; Если выполнена успешно — возвращает ноль и прекращает использования сокетов Windows. Если есть ошибка во время выполнения возвращает код ошибки.

Это далеко не все функции, находящиеся в winsock.dll. Еще раз повторю, что полный их список можно найти в файле winsock.pas, а описание можно посмотреть в Win32 API Reference — Windows Sockets 2 Reference. Надеюсь, что Вы поняли все вышенаписанное и знакомы с основами функционирования сокетов, т.е. понимаете, что такое сокет. Для закрепления полученных знаний, как обычно, напишим маленькую программку.. Все знают, что такое Socks сервер и для чего он нужен.. Если у Вас провал в памяти, я напомню: Socks прокси работают по Socks протоколу, который не зависит от высокоуровневых протоколов (http, ftp, telnet и.т.д.) и поэтому может использоваться для передачи данных по любому протоколу «высокого уровня». После всего выше изложенного будет вполне закономерно, что для тренировки мы напишем простой консольный сканер диапазонов IP адресов на открытый 1080 порт, который является стандартным портом Socks серверов.

program scan;

{$APPTYPE CONSOLE}

{ Для использования winsock необходимо описать этот модуль в uses. }

uses

sysutils,winsock;

{ дефолтовый порт Socks прокси. Сюда можно вписать любой порт,

превратив наш сканнер, к примеру, в httpd (80 порт) сканнер. }

const

port = 1080;

{ Здесь объявляем переменные }

var

D:WSAData;

S:TSocket;

A:TSockAddr;

m1,m2,mask,val:String;

i:Integer;

begin

{ Если наш сканер запущен без параметров, выводим некоторую информацию.. }

if paramcount < 1 then

begin

writeln(‘Socks Scanner by har0n, har0n@gmx.net’);

writeln(‘Example: scan.exe 127.0.0 1-255’);

writeln(‘http://www.security.net.tf’);

exit;

end

else

{ Если сканер запущен с параметрами, в переменную mask заносим 1-ый параметр,

в val 2-ой параметр }

begin

mask:=paramstr(1);

val:=paramstr(2);

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

m1:= copy(val,1,pos(‘-‘,val)-1);

m2:= copy(val,pos(‘-‘,val) 1,length(val));

writeln(‘- Scanning begin: ‘ mask ‘.’ m1 ‘ — ‘ mask ‘.’ m2 ‘ -‘);

writeln;

{ Если WSAStartup() возвращает не нулевое значение, выводим сообщение об ошибке

и выходим из программы}

if WSAStartup($101,D)0 then

begin

writeln(‘error..’);

exit;

end;

{ Начинаем процесс сканирования }

for i:= strtoint(m1) to strtoint(m2) do

begin

{ Определяем тип семейства сокетов, и IP адрес для сканирования }

A.sin_family:=AF_INET;

A.sin_addr.S_addr:=inet_addr(pchar(mask ‘.’ inttostr(i)));

{ Создаем сокет }

S:=socket(AF_INET,SOCK_STREAM,0);

{ Если возвращено значение INVALID_SOCKET, выводим сообщение об ошибке }

if S=INVALID_SOCKET then

writeln(‘socket error’);

{ Определяем порт (задается константой) }

A.sin_port:=htons(port);

{ Пытаемся подконнектиться, если удачно — выводим сообщение, что порт открыт,

в другом случае — сообщение о том, что порт закрыт (или недоступен) }

if connect(S,A,sizeof(A))=0 then

writeln(mask ‘.’ inttostr(i) ‘ port ‘ inttostr(port) ‘ opened’) else

writeln(mask ‘.’ inttostr(i) ‘ port ‘ inttostr(port) ‘ closed’);

end;

{ Завершаем работу с сокетами }

WSACleanup;

writeln;

writeln(‘- Scanning is completed -‘);

end;

end.

Запускать сканер будем так: scan.exe 127.0.0 1-255, при этом будут проверены IP адреса с 127.0.0.1 по 127.0.0.255. Я компилировал исходник на Delphi 5 update pack 1, Windows 2000 SP2, но, думаю, и под другими версиями Delphi и Windows проблем не возникнет. Ну все, удачи!

{/codecitation}

Процедуры передачи и приема блоков данных, с учетом фрагментации и склейки пакетов, построено на TServerSocket и TClientSocket

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

Автор: Camsonov Aleksandr

WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****

>> Процедуры передачи и приема блоков данных, с учетом фрагментации

и склейки пакетов. Построено на TServerSocket,TClientSocket ..SendText

Отправка:

пользователь создает строку ‘Строка пользователя’

дорабатываем строку до ‘Строка пользователя’

отправляем

Принимаем:

1 принятый кусок строки добавляем в конец буферной строки bstr;

2 вызываем прочедуру которая

a) удаляет (если есть ;|) часть bstr до ‘<'; //(это на случай ошибки,

правда такого явления я незамечал здесь но на всякий случай предусмотрел так спокойнее)

b) копирует участок » и достает из него число;

c) если длинна полученного буфера минус длинна участка » меньше bstr

то ниче не делаем и выходим из проседуры.

иначе отрезаем от bstr участок » копируем кусок bstr длинной ‘число’

символов в ostr, удаляем этотже кусок из bstr.

d) передает ostr кому оно надо ибо ostr это то что послал пользоатель отдельным куском.

все. Пом очень просто алгоритм работает без отказно и ниче тут непопишеш.

Зависимости: ScktComp

Автор: Camsonov Aleksandr, s002156@mail.ru, Tver

Copyright: SELMAP_Group_Programmers/s002156Shurik

Дата: 2 октября 2002 г.

***************************************************** }

var

Buffer: string = »;

{$R *.dfm}

function GetUserStringFromBuffer(var UserString: string): Boolean;

var

i: Integer;

bf: string;

begin

Result := False;

if Length(Buffer) > 0 then

repeat

if Length(Buffer) > 0 then

if Buffer[1] ‘<' then

Delete(Buffer, 1, 1);

until (Buffer[1] = ‘<') or (Length(Buffer) <= 1);

if Length(Buffer) < 3 then

exit;

i := 1;

bf := »;

repeat

if Length(Buffer) >= i then

begin

inc(i);

if Buffer[i] ‘>’ then

bf := bf Buffer[i];

end;

until (Buffer[i] = ‘>’) or (Length(Buffer) <= 1);

if StrToInt(bf) i > Length(Buffer) then

exit

else

begin

Delete(Buffer, 1, i);

UserString := Copy(Buffer, 1, StrToInt(bf));

Result := True;

Delete(Buffer, 1, StrToInt(bf));

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

var

S: string;

begin

s := » Edit1.Text;

ClientSocket1.Socket.SendText(S);

//В качестве ТЕСТА отправляю еще несколько копий этой строки

//для того чтобы все они ушли в одном пакете. (слипание)

ClientSocket1.Socket.SendText(S);

ClientSocket1.Socket.SendText(S);

ClientSocket1.Socket.SendText(S);

end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;

Socket: TCustomWinSocket);

var

GetResult: Boolean;

UserStr: string;

begin

Buffer := Buffer Socket.ReceiveText;

// в буфер приходят слипшиеся строки

//перезапуск функции вытаскивания кусков до False (пока куски незакончатся)

//если отправленный текст получен неполностью тоже возвращается False

repeat

GetResult := GetUserStringFromBuffer(UserStr);

if GetResult then

ShowMessage(UserStr); //передается отосланная строка

//ЦЕЛАЯ И БЕЗ МУСОРА!

until not GetResult;

end;

{/codecitation}

Программирование сокетов в Delphi

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

Автор: Павел

Введение

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

Впервые я познакомился с сокетами, если не ошибаюсь, год или полтора назад. Тогда стояла задача разработать прикладной протокол, который бы передавал на серверную машину (работающую на ОС Unix/Linux) запрос и получал ответ по сокетному каналу. Надо заметить, что в отличие от любых других протоколов (FTP, POP, SMTP, HTTP, и т.д.), сокеты — это база для этих протоколов. Таким образом, пользуясь сокетами, можно самому создать (симитировать) и FTP, и POP, и любой другой протокол, причем не обязательно уже созданный, а даже свой собственный!

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

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

Так что же позволяют нам делать сокеты?… Да все что угодно! И в этом одно из главных достоинств этого способа обмена данными в сети. Дело в том, что при работе с сокетом Вы просто посылаете другому компьютеру последовательность символов. Так что этим методом Вы можете посылать как простые сообщения, так и целые файлы! Причем, контролировать правильность передачи Вам не нужно (как это было при работе с COM-портами)!

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

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

Определение св-в Host и Port — чтобы успешно установить соединение, нужно присвоить свойствам Host и Port компонента TClientSocket требуемые значения. Host — это хост-имя (например: nitro.borland.com) либо IP-адрес (например: 192.168.0.88) компьютера, с которым надо соединиться. Port — номер порта (от 1 до 65535) для установления соединения. Обычно номера портов берутся, начиная с 1001 — т.к. номера меньше 1000 могут быть заняты системными службами (например, POP — 110). Подробнее о практической части см.ниже;

Открытие сокета — после того, как Вы назначили свойствам Host и Port соответствующие значения, можно приступить непосредственно к открытию сокета (сокет здесь рассматривается как очередь, в которой содержатся символы, передающиеся от одного компьютера к другому). Для этого можно вызвать метод Open компонента TClientSocket, либо присвоить свойству Active значение True. Здесь полезно ставить обработчик исключительной ситуации на тот случай, если соединиться не удалось. Подробнее об этом можно прочитать ниже, в практической части;

Авторизация — этот пункт можно пропустить, если сервер не требует ввода каких-либо логинов и/или паролей. На этом этапе Вы посылаете серверу свой логин (имя пользователя) и пароль. Но механизм авторизации зависит уже от конкретного сервера;

Посылка/прием данных — это, собственно и есть то, для чего открывалось сокетное соединение. Протокол обмена данными также зависит от сервера;

Закрытие сокета — после всех выполненных операций необходимо закрыть сокет с помощью метода Close компонента TClientSocket (либо присвоить свойству Active значение False).

Описание свойств и методов компонента TClientSocket

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

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

Active — показывает, открыт сокет или нет. Тип: Boolean. Соответственно, True — открыт, а False — закрыт. Это свойство доступно для записи;

Host — строка (Тип: string), указывающая на хост-имя компьютера, к которому следует подключиться;

Address — строка (Тип: string), указывающая на IP-адрес компьютера, к которому следует подключиться. В отличие от Host, здесь может содержаться лишь IP. Отличие в том, что если Вы укажете в Host символьное имя компьютера, то IP адрес, соответствующий этому имени будет запрошен у DNS;

Port — номер порта (Тип: Integer (Word)), к которому следует подключиться. Допустимые значения — от 1 до 65535;

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

ClientType — тип соединения. ctNonBlocking — асинхронная передача данных, т.е. посылать и принимать данные по сокету можно с помощью OnRead и OnWrite. ctBlocking — синхронная (одновременная) передача данных. События OnRead и OnWrite не работают. Этот тип соединения полезен для организации обмена данными с помощью потоков (т.е. работа с сокетом как с файлом);

Open — открытие сокета (аналогично присвоению значения True свойству Active);

Close — закрытие сокета (аналогично присвоению значения False свойству Active);

На этом все методы компонента TClientSocket исчерпываются. А Вы спросите: «А как же работать с сокетом? Как тогда пересылать данные?». Об этом Вы узнаете чуть дальше. OnConnect — как следует из названия, это событие возникает при установлении соединения. Т.е. в обработчике этого события уже можно начинать авторизацию или прием/передачу данных;

OnConnecting — возникает при установлении соединения. Отличие от OnConnect в том, что соединение еще не установлено. Обычно такие промежуточные события используются для обновления статуса;

OnDisconnect — возникает при закрытии сокета. Причем, закрытия как из Вашей программы, так и со строноны удаленного компьютера (либо из-за сбоя);

OnError — продолжает грустную тему предыдущего события :). Возникает при ошибке в работе сокета. Следует отметить, что это событие не поможет Вам отловить ошибку в момент открытия сокета (Open). Для того, чтобы избежать выдачи виндозного сообщения об ошибке, надо заключить операторы открытия сокета в блок try..except (обработка исключительных ситуаций);

OnLookup — возникает при попытке получения от DNS IP-адреса указанного хоста;

OnRead — возникает, когда удаленный компьютер послал Вам какие-либо данные. При возникновении этого события возможна обработка данных;

OnWrite — возникает, когда Вам разрешена запись данных в сокет.

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

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

Пример 1. Простейшая сокетная программа

{… Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

{В форму нужно поместить кнопку TButton и два TEdit.

При нажатии на кнопку вызывается обработчик события OnClick — Button1Click.

Перед этим в первый из TEdit-ов нужно ввести хост-имя,

а во второй — порт удаленного компьютера.

НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}

procedure Button1Click(Sender: TObject);

begin

{Присваиваем свойствам Host и Port нужные значения}

ClientSocket1.Host := Edit1.Text;

ClientSocket1.Port := StrToInt(Edit2.Text);

{Пытаемся открыть сокет и установить соединение}

ClientSocket1.Open;

end;

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

begin

{Как только произошло соединение — закрываем сокет и прерываем связь}

ClientSocket1.Close;

end;

Если Вы думаете, что данный пример программы совершенно бесполезен и не может принести никакой пользы, то глубоко ошибаетесь. Приведенный код — простейший пример сканера портов (PortScanner). Суть такой утилиты в том, чтобы проверять, включен ли указанный порт и готов ли он к приему/передаче данных. Именно на таком принципе основан PortScanner из программы NetTools Pro.

Далее следует другой пример, в котором по сокету передаются и принимаются текстовые сообщения:

Пример 2. Посылка/прием текстовых сообщений по сокетам

{… Здесь идет заголовок файла и определение формы TForm1 и ее экземпляра Form1}

{В форму нужно поместить две кнопки TButton и три TEdit.

При нажатии на первую кнопку вызывается обработчик события OnClick — Button1Click.

Перед этим в первый из TEdit-ов нужно ввести хост-имя,

а во второй — порт удаленного компьютера. После установления соединения

можно посылать текстовые сообщения, вводя текст в третий TEdit и нажимая

вторую кнопку TButton. Чтобы отсоединиться, нужно еще раз нажать первую TButton.

Еще нужно добавить TListBox, в который будем помещать принятые

и отправленные сообщения.

НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}

procedure Button1Click(Sender: TObject);

begin

{Если соединение уже установлено — прерываем его.}

if ClientSocket1.Active then

begin

ClientSocket1.Close;

Exit; {…и выходим из обработчика}

end;

{Присваиваем свойствам Host и Port нужные значения}

ClientSocket1.Host := Edit1.Text;

ClientSocket1.Port := StrToInt(Edit2.Text);

{Пытаемся открыть сокет и установить соединение}

ClientSocket1.Open;

end;

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

begin

{Как только произошло соединение — посылаем приветствие}

Socket.SendText(‘Hello!’);

ListBox1.Items.Add(‘< Hello!');

end;

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

begin

{Если пришло сообщение — добавляем его в ListBox}

ListBox1.Items.Add(‘> ‘ Socket.ReceiveText);

end;

procedure Button2Click(Sender: TObject);

begin

{Нажата кнопка — посылаем текст из третьего TEdit}

ClientSocket1.Socket.SendText(Edit3.Text);

ListBox1.Items.Add(‘< ' Edit3.Text);

end;

ПРИМЕЧАНИЕ: В некоторых случаях (зависящих от сервера) нужно после каждого сообщения посылать перевод строки:

ClientSocket1.Socket.SendText(Edit3.Text #10);

Работа с сокетным потоком

«А как еще можно работать с сокетом?», — спросите Вы. Естественно, приведенный выше метод — не самое лучшее решение. Самих методов организации работы с сокетами очень много. Я приведу лишь еще один дополнительный — работа через поток. Наверняка, многие из Вас уже имеют опыт работы, если не с потоками (stream), то с файлами — точно. Для тех, кто не знает, поток — это канал для обмена данными, работа с которым аналогична работе с обычным файлом. Нижеприведенный пример показывает, как организовать поток для работы с сокетом:

Пример 3. Поток для работы с сокетом

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

var

c: Char;

MySocket: TWinSocketStream;

begin

{Как только произошло соединение — создаем поток и

ассоциируем его с сокетом (60000 — таймаут в мсек)}

MySocket := TWinSocketStream.Create(Socket, 60000);

{Оператор WaitForData ждет данных из потока указанное время в мсек

(в данном примере — 100) и возвращает True, если получен хотя бы один

байт данных, False — если нет никаких данных из потока.}

while not MySocket.WaitForData(100) do

Application.ProcessMessages;

{Application.ProcessMessages позволяет Windows перерисовать нужные

элементы окна и дает время другим программам. Если бы этого оператора

не было и данные бы довольно долго не поступали,

то система бы слегка «подвисла».}

MySocket.Read(c, 1);

{Оператор Read читает указанное количество байт из потока

(в данном примере — 1) в указанную переменную определенного типа

(в примере — в переменную c типа Char). Обратите внимание на то,

что Read, в отличие от ReadBuffer, не устанавливает строгих

ограничений на количество принятой информации. Т.е. Read читает

не больше n байтов из потока (где n — указанное число).

Эта функция возвращает количество полученных байтов данных.}

MySocket.Write(c, 1);

{Оператор Write аналогичен оператору Read, за тем лишь исключением,

что Write пишет данные в поток.}

MySocket.Free;

{Не забудем освободить память, выделенную под поток}

end;

ПРИМЕЧАНИЕ: Для использования потока не забудьте установить свойство ClientType в ctBlocking.

Посылка и прием сложных данных

Иногда необходимо пересылать по сети не только простые текстовые сообщения, но и сложные структуры (тип record в Паскале), или даже файлы. И тогда Вам необходимо использовать специальные операторы. Некоторые из них перечислены ниже:

Методы TClientSocket.Socket (TCustomWinSocket, TClientWinSocket):

SendBuf(var Buf; Count: Integer) — Посылка буфера через сокет. Буфером может являться любой тип, будь то структура (record), либо простой Integer. Буфер указывается параметром Buf, вторым параметром Вы должны указать размер пересылаемых данных в байтах (Count);

SendText(const S: string) — Посылка текстовой строки через сокет. Этот метод рассматривался в примере 2 (см.выше);

SendStream(AStream: TStream) — Посылка содержимого указанного потока через сокет. Пересылаемый поток должен быть открыт. Поток может быть любого типа — файловый, из ОЗУ, и т.д. Описание работы непосредственно с потоками выходит за рамки данной статьи;

Всем перечисленным методам соответствуют методы Receive… Их описание можно посмотреть в справочном файле по Дельфи (VCL help).

Авторизация на сервере

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

Пример 4. Авторизация

{В данном примере нужно добавить в форму еще два

TEdit — Edit3 и Edit4 для ввода логина и пароля}

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

var

c: Char;

MySocket: TWinSocketStream;

login, password: string;

begin

MySocket := TWinSocketStream.Create(Socket, 60000);

{Добавляем к логину и паролю символ перевода строки,

чтобы сервер смог отделить логин и пароль.}

login := Edit3.Text #10;

password := Edit4.Text #10;

MySocket.Write(login, Length(Edit3.Text) 1);

MySocket.Write(password, Length(Edit4.Text) 1);

while not MySocket.WaitForData(100) do

Application.ProcessMessages;

MySocket.Read(c, 1);

{Здесь сервер посылает нам один байт, значение 1 которого

соответствует подтверждению успешной авторизации, а 0 — ошибку

(это лишь пример). Далее мы выполняем нужные действия

(прием/пересылку данных) и закрываем поток.}

MySocket.Free;

end;

Эпилог

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

В ближайшем будущем планирую также статью про сокетные серверы (TServerSocket).

{/codecitation}

Проверить, доступен ли WinSock

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

Оформил: DeeCo

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

uses

Winsock;

function WinsockEnabled: Boolean;

var

wsaData: TWSAData;

begin

Result := True;

case Winsock.WSAStartup($0101, wsaData) of

WSAEINVAL, WSASYSNOTREADY, WSAVERNOTSUPPORTED: Result := False;

else

Winsock.WSACleanup;

end;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

if WinsockEnabled then

ShowMessage(‘Winsock is enabled’)

else

ShowMessage(‘Winsock is disabled’);

end;

{/codecitation}

Приём и обработка пакетов переданных методом SendText — с учётом склеенных и полученных неполностью пакетов

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

Автор: VID

WEB-сайт: http://delphibase.endimus.com

{ **** UBPFD *********** by delphibase.endimus.com ****

>> Приём и обработка пакетов переданных методом SendText() —

с учётом «склеенных» и полученных неполностью пакетов.

Юнит RecvPckt предназначен для приёма текста, передаваемого с помощью метода SendText

объекта Socket:TCustomWinSocket. Данный юнит может использоваться как клиентом так

и сервером для обработки принятого пакета.

Функции данного юнита предусматривают возможность получения «склеенных» пакетов,

или пакетов, пришедших не полностью.

Тип TBuffer;

FBuffer — хранит в себе принимаемый пакет

FCurrentPacketSize = храни сведения о полной длине пакета.

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

Procedure ClearBuffer(var ABuffer:TBuffer);

Очищает буффер FBuffer и обнуляет значение FCurrentPacketSize;

Function ProcessReceivedPacket(var ABuffer:TBuffer; var APacket:String):Boolean;

В данную функцию передаётся полученный от клиента/сервера пакет, через аргумент APacket

Принцип работы этой функции заключается в накоплении получаемого текста в поле

FBuffer объекта ABuffer. В случае когда FBuffer будет содержать полностью весь пакет,

функция возвратит True, иначе возвращает False

Функция ОТПРАВКИ текста:

Function SendTextToSocket(Socket:TCustomWinSocket; Text:String):Integer;

Var S:String;

begin

Result := -1;

IF Text = » then exit;

IF Socket.Connected then

begin

S:=IntToStr(Length(Text));

Result := Socket.SendText(S ‘#’ Text);

end;

end;

Зависимости: sysutils

Автор: VID, snap@iwt.ru, ICQ:132234868, Махачкала

Copyright: VID

Дата: 30 сентября 2002 г.

***************************************************** }

unit RecvPckt;

interface

uses

SysUtils;

type

TReadHeaderResult = record

FPacketSize: Integer;

FPacketSizeStr: string;

FTextStartsAt: Integer;

end;

type

TBuffer = record

FBuffer: string;

FHeaderBuffer: string;

FCurrentPacketSize: Integer;

end;

procedure ClearBuffer(var ABuffer: TBuffer);

function ReadHeader(var ABuffer: TBuffer; var APacket: string):

TReadHeaderResult;

function ProcessReceivedPacket(var ABuffer: TBuffer; var APacket: string):

Boolean;

implementation

procedure ClearBuffer(var ABuffer: TBuffer);

begin

ABuffer.FBuffer := »;

ABuffer.FHeaderBuffer := »;

ABuffer.FCurrentPacketSize := 0;

end;

function ReadHeader(var ABuffer: TBuffer; var APacket: string):

TReadHeaderResult;

var

X, HBuffLen: Integer;

procedure ClearHeader;

begin

ABuffer.FHeaderBuffer := »;

end;

function CorrectPacket: Boolean;

var

I, L: Integer;

begin

X := 0;

L := Length(APacket);

for I := 1 to L do

if (APacket[I] in [‘0’..’9′]) then

BREAK

else if (APacket[I] = ‘#’) and (ABuffer.FHeaderBuffer ») then

BREAK

else

X := I;

if X > 0 then

Delete(APacket, 1, X);

RESULT := APacket »;

end;

procedure GetHeader;

var

I, L: Integer;

begin

L := Length(APacket);

X := 0;

for I := 1 to L do

begin

X := I;

if (APacket[I] in [‘0’..’9′]) then

begin

HBuffLen := Length(ABuffer.FHeaderBuffer);

if HBuffLen > 0 then

Inc(HBuffLen);

Insert(APacket[I], ABuffer.FHeaderBuffer, HBuffLen);

end

else

Break;

end;

end;

procedure SetResultToNone;

begin

Result.FPacketSize := 0;

Result.FTextStartsAt := 0;

Result.FPacketSizeStr := »;

end;

begin

SetResultToNone;

if APacket = » then

Exit;

if ABuffer.FCurrentPacketSize > 0 then

begin

Result.FPacketSize := ABuffer.FCurrentPacketSize;

Result.FPacketSizeStr := IntToStr(ABuffer.FCurrentPacketSize);

Result.FTextStartsAt := 1;

Exit;

end;

if not CorrectPacket then

Exit;

GetHeader;

if APacket[X] = ‘#’ then

begin

Inc(X);

try

Result.FPacketSize := StrToInt(ABuffer.FHeaderBuffer);

except

end;

Result.FPacketSizeStr := ABuffer.FHeaderBuffer;

ClearHeader;

end

else if not (APacket[X] in [‘0’..’9′]) then

ClearHeader;

Result.FTextStartsAt := X;

end;

function ProcessReceivedPacket(var ABuffer: TBuffer; var APacket: string):

Boolean;

var

ReadHeaderResult: TReadHeaderResult;

NeedToCopy, DelSize: Integer;

S: string;

BuffLen: Integer;

function FullPacket: Boolean;

begin

Result := Length(ABuffer.FBuffer) = ABuffer.FCurrentPacketSize;

end;

begin

Result := True;

if APacket = » then

Exit;

if ABuffer.FBuffer = » then

begin

ReadHeaderResult := ReadHeader(ABuffer, APacket);

ABuffer.FCurrentPacketSize := ReadHeaderResult.FPacketSize;

S := Copy(APacket, ReadHeaderResult.FTextStartsAt,

ReadHeaderResult.FPacketSize);

DelSize := Length(ReadHeaderResult.FPacketSizeStr)

ReadHeaderResult.FPacketSize 1;

end

else

begin

NeedToCopy := ABuffer.FCurrentPacketSize — Length(ABuffer.FBuffer);

S := Copy(APacket, 1, NeedToCopy);

DelSize := NeedToCopy;

end;

if ABuffer.FCurrentPacketSize > 0 then

begin

BuffLen := Length(ABuffer.FBuffer);

if BuffLen > 0 then

Inc(BuffLen);

Insert(S, ABuffer.FBuffer, BuffLen);

end;

if not FullPacket then

Result := False;

if ABuffer.FHeaderBuffer = » then

DELETE(APacket, 1, DelSize)

else

begin

APacket := »;

Result := False;

end;

end;

end.

Пример использования:

// Объявляем переменную типа TBuffer. Для каждого клиента на

// сервере должна быть объявлена отдельная переменная этого типа

var

GBuffer: TBuffer;

procedure TForm1.ServerClientRead(Sender: TObject;

Socket: TCustomWinSocket);

var

S: string;

begin

S := Socket.ReceiveText;

repeat

if ProcessReceivedPacket(GBuffer, S) then

begin

if GBuffer.FBuffer » then

Recv.Lines.Add(GBuffer.FBuffer);

//или же передать GBuffer.FBuffer на исполнение.

ClearBuffer(GBuffer);

end;

until S = »;

end;

{/codecitation}

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

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

Скорость передачи между двумя и более пользователями Intеrnеtа при обрыве связи внезапно возрастает до 2 мегаматов в секунду.

Итак, во-первых, надо заметить, что посылаемые через сокет данные могут не только объединяться в один блок, но и разъединяться по нескольким блокам. Дело в том, что сокет — обычный поток, но в отличие, скажем, от файлового (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;

{/codecitation}