Сокеты delphi примеры два порта. Сокеты в Delphi. Основные события компонента TClientSocket
В данной статье будут рассмотрены базовые свойства и функции компонентов Delphi: TClientSocket и TServerSocket – использующихся для работы с сетью по протоколу TCP\IP.
Внимание! Если вы используете версию Delphi выше 6.0, то предварительно вам необходимо установить Sockets Components, во всех версиях Delphi это делается следующим образом:
- Заходим в диалог Install Packages… : (Главное меню) Component -> Install Packages;
- Щелкаем по кнопке Add… , после чего находим папку Bin вашего Delphi (н-р: C:\Program Files\Borland\Delphi 7\bin , либо C:\Program Files\Embarcadero\RAD Studio\7.0\Bin);
- В найденной папке Bin уже ищем файлик dclsockets[тут циферки] .bpl , жмем ОК ;
- Радуемся, потому что теперь на вкладке панели компонентов на вкладке Internet у нас появились два замечательных компонента TServerSocket и TClientSocket.
При разработке любых сетевых приложений обычно разработку всегда начинают с сервера (конечно если у вас есть команда, дизайнеры интерфейсов могут приступать к работе над клиентом). Для реализации сервера, удивительно, необходимо использовать TServerSocket.
Основные свойства:
- Active – поле типа boolean, при установлении в true – сервер запускается, можно использовать, как присвоение конкретных значений, так и вызывая функции ServerSocket1.Open (… Active:=true;) или ServerSocket1.Close (… Active:=false).
- Port – порт на котором будет прослушивать (принимать клиентов) сервер, любое не занятое другими серверами в системе значение в пределах диапазона integer .
Основные события:
- OnListen – вызывается при установлении сервера в режим прослушивания, может испоьзоватся, когда нам нужно определить время реального старта сервера.
- OnClientRead – вызывается в момент принятия данных от клиента.
- OnClientError
- OnClientConnect – вызывается во время присоединения нового клиента к серверу.
- OnClientDisconnect – обратное событие событию, OnClientConnect
у каждой функции события есть атрибут Socket: TCustomWinSocket , в него передаеться указатель на объект сокета с которым мы в данный момент работаем, если нам нужно что-то ответить или что-то сделать с клиентом, который вызвал событие, нам необходимо использовать именно этот объект, во всех остальных случаях мы используем ServerSocket1.Socket , аналогичная ситуация и с клиентским компонентом.
Readonly свойства и функции:
- – возвращает количество активных соединений.
- ServerSocket1.Socket.Connections – массив объектов типа TCustomWinSocket, массив всех объектов связанных с клиентами, счет индексов начинается с 0, длинна массива равна ServerSocket1.Socket.ActiveConnections .
- Функции и свойства, применимые к элементам массива ServerSocket1.Socket.Connections и передаваемому атрибуту Socket в функцию события сервера:
- Socket.LocalHost
- Socket.LocalAdress – возвращает IP сервера.
- Socket.RemoteHost
- Socket.RemoteAdress – возвращает IP клиента.
- Socket.ReceiveText – возвращает принятое от клиента текстовое сообщение, после чего очищает буфер, можно использовать только 1 раз, за 1 прием.
- Socket.SendText(Text) – отсылает клиенту текстовое сообщение Text типа string .
Для компонента TClientSocket все практически тоже самое, только наоборот + основное визуальное отличие сервера от клиента, сервер в системе может быть запущен 1 на 1 значение порта, в количестве клиентов вас ограничивает только оперативная память.
Основные свойства:
- Active – поле типа boolean, при установлении в true – клиент пытается соединится с сервером, можно использовать, как присвоение конкретных значений, так и вызывая функции ClientSocket1.Open (… Active:=true;) или ClientSocket1.Close (… Active:=false).
- Port – порт по которому клиент сможет присоединиться к серверу, любое значение в пределах диапазона integer .
- Adress – IPv4 адрес сервера типа string по шаблону 255.255.255.255, с которым будет соединяться клиент.
Основные события:
- OnRead – вызывается в момент принятия данных от севера.
- OnError – вызывается при возникновении ошибки в передаче данных.
- OnConnecting – вызывается во время присоединения клиента к серверу.
- OnDisconnect – обратное событие событию, OnConnecting , вызывается во время отсоединения от сервера клиента.
Readonly свойства и функции:
- ClientSocket1.Socket.SendText() string
- Socket.LocalHost – возвращает имя клиента в сети.
- Socket.LocalAdress – возвращает IP клиента.
- Socket.RemoteHost – возвращает имя сервера в сети.
- Socket.RemoteAdress – возвращает IP сервера.
- Socket.ReceiveText – возвращает принятое от сервера текстовое сообщение, после чего очищает буфер, можно использовать только 1 раз, за 1 прием.
- Socket.SendText(Text) – отсылает серверу текстовое сообщение Text типа string .
Предоставленной информации вполне достаточно, чтобы реализовать небольшой серверный чат, удовлетворяющий техническому заданию: internet_sockets.doc (Word Doc 97-2003, 26.5 Kb).
Эта статья написана в Воскресенье, Октябрь 10th, 2010 at 1:24 в разделе . Вы можете подписаться на обновления комментариев к статье - . Вы можете
Введение
Данная статья посвящена созданию приложений архитектуры
клиент/сервер в Borland Delphi на основе сокетов ("sockets" - гнезда). А написал
я эту статью не просто так, а потому что в последнее время этот вопрос очень
многих стал интересовать. Пока что затронем лишь создание клиентской части
сокетного приложения.
Впервые я познакомился с сокетами, если не ошибаюсь,
год или полтора назад. Тогда стояла задача разработать прикладной протокол,
который бы передавал на серверную машину (работающую на ОС
Unix/Linux) запрос и
получал ответ по сокетному каналу. Надо заметить, что в отличие от любых других
протоколов (FTP, POP, SMTP, HTTP, и т.д.), сокеты - это база для этих
протоколов. Таким образом, пользуясь сокетами, можно самому создать
(симитировать) и FTP, и POP, и любой другой протокол, причем не обязательно уже
созданный, а даже свой собственный!
Итак, начнем с теории. Если Вы убежденный практик (и в глаза не можете видеть всяких алгоритмов), то Вам следует пропустить этот раздел.
Алгоритм работы с сокетными протоколами
Так что же позволяют нам делать сокеты?... Да все что
угодно! И в этом одно из главных достоинств этого способа обмена данными в сети.
Дело в том, что при работе с сокетом Вы просто посылаете другому компьютеру
последовательность символов. Так что этим методом Вы можете посылать как простые
сообщения, так и целые файлы! Причем, контролировать правильность передачи Вам
не нужно (как это было при работе с COM-портами)!
Ниже следует примерная
схема работы с сокетами в Дельфи-приложениях:
Определение св-в Host и Port >>> Запуск Сокета (ClientSocket1.Open) >>> Авторизация >>> Посылка/прием данных >>> Закрытие Сокета
Разберем
схему подробнее:
Определение св-в 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 исчерпываются. А Вы спросите: "А как же работать с сокетом? Как тогда пересылать данные?". Об этом Вы узнаете чуть дальше.
Практика и примеры
Легче всего (и полезней) изучать любые методы программирования на практике. Поэтому далее приведены примеры с некоторыми комментариями:
Пример 1. Простейшая сокетная программа
{В форму нужно поместить кнопку TButton и два TEdit. При нажатии на кнопку вызывается обработчик события OnClick - Button1Click. Перед этим в первый из TEdit-ов нужно ввести хост-имя, а во второй - порт удаленного компьютера. НЕ ЗАБУДЬТЕ ПОМЕСТИТЬ В ФОРМУ КОМПОНЕНТ TClientSocket!}
begin
{Присваиваем свойствам Host и Port
нужные значения}
ClientSocket1.Host:= Edit1.Text;
ClientSocket1.Port:=
StrToInt(Edit2.Text);
{Пытаемся открыть сокет и установить соединение}
ClientSocket1.Open;
end;
begin
{Как только произошло
соединение - закрываем сокет и прерываем связь}
ClientSocket1.Close;
end;
Если Вы думаете, что данный пример программы совершенно бесполезен и не может принести никакой пользы, то глубоко ошибаетесь. Приведенный код - простейший пример сканера портов (PortScanner). Суть такой утилиты в том, чтобы проверять, включен ли указанный порт и готов ли он к приему/передаче данных. Именно на таком принципе основан PortScanner из программы NetTools Pro.
Пример 2. Посылка/прием текстовых сообщений по сокетам
{В форму нужно поместить две кнопки 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;
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).
Напоследок хочу привести несложный пример того, как можно реализовать авторизацию (вход на сервер). В данном примере пароль посылается нешифрованным текстом, так что если Вам нужен действительно надежный механизм входа, то Вам придется внести кое-какие изменения в исходник данного примера. Пример реализован как работа с сокетным потоком.
{В данном примере нужно добавить в форму еще два 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
выбрать меню: Component - Install Packages… - Add., далее нужно указать файл …\bin\dclsockets70.bpl.
Перейдем непосредственно к созданию проекта клиент-сервера, для начала на примере сетевого чата.
Сетевой чат на двух пользователей
Как правило, разработка любой программы начинается с определения задач, которые она должна выполнять, и определения уже на этом этапе нужных компонентов. Наша программа представляет собой чат на двоих пользователей, каждый из которых может быть как сервером, так и клиентом, значит, кидаем в форму компоненты ServerSocket и ClientSocket . Важным параметром для обоих является порт. Только при одинаковом значении свойства Port , связь между ними установится. Кинем в форму компонент Edit , чтобы оперативно изменять порт, назовем его PortEdit . Для соединения с сервером необходимо указывать IP сервера или его имя, поэтому кинем еще один Edit , назовем его HostEdit . Так же нам понадобятся еще два Edit "а для указания ника и ввода текста сообщения, назовем их NikEdit и TextEdit , соответственно. Текст принимаемых и отправляемых сообщений будет отображаться в Memo , кинем его в форму и назовем ChatMemo . Установим сразу вертикальную полосу прокрутки: ScrollBars = ssVertical , и свойство ReadOnly = True . Добавим клавиши управления Button : ServerBtn - для создания/закрытия сервера, ClientBtn - для подключения/отключения клиента к серверу, SendBtn - для отправки сообщений. Изменим Caption этих клавиш на "Создать сервер ", "Подключиться " и "Отправить ", соответственно. Последний штрих - добавим надписи Label для предания форме надлежащего вида (это по желанию).
Предупрежу сразу, проверок на корректность ввода значений в кодах не будет, поскольку это не есть главная цель. Проверки без труда вы можете прописать сами.
Определим, что должно происходить при создании формы. Опишем процедуру OnCreate :
begin
// предложенное значения порта
PortEdit.Text:="777";
// адрес при проверке программы на одном ПК ("сам на себя")
HostEdit.Text:="127.0.0.1";
// остальные поля просто очистим
NikEdit.Clear;
TextEdit.Clear;
ChatMemo.Lines.Clear;
end;
Будем считать, что выбран режим сервера. Перевод программы в режим сервера осуществляется клавишей "Создать сервер " (ServerBtn) . Чтобы не использовать лишних клавиш для отключения этого режима или компонентов типа RadioButton , можно использовать то же свойство Tag клавиши ServerBtn , изменяя его значения и выполняя те или иные операции, если значение соответствует указанному. Вот так выглядит процедура на нажатие клавиши ServerBtn (OnClick ):
procedure TForm1.ServerBtnClick(Sender: TObject);
begin
If ServerBtn.Tag=0 then
Begin
// клавишу ClientBtn и поля HostEdit, PortEdit заблокируем
ClientBtn.Enabled:=False;
HostEdit.Enabled:=False;
PortEdit.Enabled:=False;
// запишем указанный порт в ServerSocket
ServerSocket.Port:=StrToInt(PortEdit.Text);
// запускаем сервер
ServerSocket.Active:=True;
// добавим в ChatMemo сообщение с временем создания
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Сервер создан");
// изменяем тэг
ServerBtn.Tag:=1;
// меняем надпись клавиши
ServerBtn.Caption:="Закрыть сервер";
end
else
Begin
// клавишу ClientBtn и поля HostEdit, PortEdit разблокируем
ClientBtn.Enabled:=True;
HostEdit.Enabled:=True;
PortEdit.Enabled:=True;
// закрываем сервер
ServerSocket.Active:=False;
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Сервер закрыт.");
ServerBtn.Tag:=0;
ServerBtn.Caption:="Создать сервер";
end;
end;
Разберемся с событиями, которые должны происходить при определенном состоянии ServerSocket "а. Напишем процедуру, когда клиент подсоединился к серверу (OnClientConnect ):
procedure TForm1.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo сообщение с временем подключения клиента
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Подключился клиент.");
end;
Напишем процедуру, когда клиент отключается (OnClientDisconnect ):
procedure TForm1.ServerSocketClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo сообщение с временем отключения клиента
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Клиент отключился.");
end;
Когда на сервер приходит очередное сообщение клиента, мы должны сразу же отображать его. Напишем процедуру на чтение сообщения от клиента (OnClientRead ):
Socket: TCustomWinSocket);
begin
end;
Самое главное - отправка сообщений. У нас она осуществляется нажатием клавиши "Отправить" (SendBtn ), но необходима проверка режима программы сервер или клиент. Напишем ее процедуру (OnClick ):
procedure TForm1.SendBtnClick(Sender: TObject);
begin
// проверка, в каком режиме находится программа
// отправляем сообщение с сервера (он под номером 0, поскольку один)
ServerSocket.Socket.Connections.SendText("["+TimeToStr(Time)+"] "+NikEdit.Text+": "+TextEdit.Text)
else
// отправляем сообщение с клиента
ClientSocket.Socket.SendText("["+TimeToStr(Time)+"] "+NikEdit.Text+": "+TextEdit.Text);
// отобразим сообщение в ChatMemo
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] "+NikEdit.Text+": "+TextEdit.Text);
end;
Теперь разберемся с режимом клиента. Здесь наоборот, при нажатии клавиши "Подключиться" (ClientBtn ), блокируется ServerBtn и активируется ClientSocket . Вот процедура ClientBtn (OnClick) :
procedure TForm1.ClientBtnClick(Sender: TObject);
begin
If ClientBtn.Tag=0 then
Begin
// клавишу ServerBtn и поля HostEdit, PortEdit заблокируем
ServerBtn.Enabled:=False;
HostEdit.Enabled:=False;
PortEdit.Enabled:=False;
// запишем указанный порт в ClientSocket
ClientSocket.Port:=StrToInt(PortEdit.Text);
// запишем хост и адрес (одно значение HostEdit в оба)
ClientSocket.Host:=HostEdit.Text;
ClientSocket.Address:=HostEdit.Text;
// запускаем клиента
ClientSocket.Active:=True;
// изменяем тэг
ClientBtn.Tag:=1;
// меняем надпись клавиши
ClientBtn.Caption:="Отключиться";
end
else
Begin
// клавишу ServerBtn и поля HostEdit, PortEdit разблокируем
ServerBtn.Enabled:=True;
HostEdit.Enabled:=True;
PortEdit.Enabled:=True;
// закрываем клиента
ClientSocket.Active:=False;
// выводим сообщение в ChatMemo
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Сессия закрыта.");
// возвращаем тэгу исходное значение
ClientBtn.Tag:=0;
// возвращаем исходную надпись клавиши
ClientBtn.Caption:="Подключиться";
end;
end;
Остается прописать процедуры на OnConnect , OnDisconnect , OnRead клиента ClientSocket . Сначала на чтение сообщения с сервера (OnRead ):
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo пришедшее сообщение
ChatMemo.Lines.Add(Socket.ReceiveText());
end;
procedure TForm1.ClientSocketConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo сообщение о соединении с сервером
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Подключение к серверу.");
end;
procedure TForm1.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo сообщение о потере связи
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Сервер не найден.");
end;
Вот тот минимум, который нужно проделать. Остается внести еще некоторый ряд алгоритмов для проверки правильности ввода некоторых значений.
Возникает вопрос: а если нужно передать данные и совсем не строковые, а какой-нибудь массив? Для этого есть специальные команды. Давайте попробуем написать алгоритм отправки массива, как команды для некоторой игры.
Отправка массива данных
Воспользуемся той же формой чата, только добавим несколько компонентов чуть ниже. Пусть задача - управлять объектом типа Shape
, менять тип геометрической фигуры, цвет, размеры. Поместим в форму компонент GroupBox
, а в него Shape
, их имена будут такими же. Для изменения типа геометрической фигуры используем список ComboBox
, назовем его ShapeCBox
. Сразу заполнять не будем, это сделаем в OnCreate
формы. Далее понадобится такой же ComboBox
для выбора цвета, и два Edit
"а для указания размера фигуры (в случае с прямоугольником имеем два значения, на круг будем использовать одно первое). Назовем их ColorCBox
, Value1Edit
, Value2Edit
, соответственно. Последним кинем в форму компонент Button
, назовем его SendBufBtn
, Caption
изменим на "Отправить буфер
".
Немного о том, как представить вводимые данные в виде буфера данных. Нужно сразу определиться в последовательности, какое значение, за каким следует в буфере. Пусть первым будет тип фигуры, за ним цвет, а следом оба значения размера. Для этих целей следует использовать массив длиной 4
и типом Byte
. Добавим в раздел var
массив:
Buf: array of Byte;
С размерами фигуры все понятно, а вот для типа и цвета нужна "таблица истинности". Представим ее следующим образом:
параметр код
прямоугольник 0
круг 1
-------------------
красный 0
зеленый 1
синий 2
Этого вполне хватит для демонстрации. По желанию круг параметров можно расширить, ввести тип заливки, тип контура, смещение, или воспользоваться примером для других целей.
Пропишем заполнение списков в OnCreate
формы:
procedure TForm1.FormCreate(Sender: TObject);
begin
// ...часть чата...
// заполнение списков
ShapeCBox.Items.Add("прямоугольник");
ShapeCBox.Items.Add("круг");
ColorCBox.Items.Add("красный");
ColorCBox.Items.Add("зеленый");
ColorCBox.Items.Add("синий");
end;
При нажатии клавиши "Отправить буфер " будем собирать данные с полей и формировать массив известной длины, а затем проверять на режим сервер/клиент и отправлять. Вот процедура SendBufBtn (OnClick) :
procedure TForm1.SendBufBtnClick(Sender: TObject);
begin
// соберем данные для отправки
Buf:=ShapeCBox.ItemIndex;
Buf:=ColorCBox.ItemIndex;
Buf:=StrToInt(Value1Edit.Text);
Buf:=StrToInt(Value2Edit.Text);
// проверяем режим программы
If ServerSocket.Active=True then
// отправим буфер с сервера (длина известна - 4)
ServerSocket.Socket.Connections.SendBuf(Buf,4)
else
// отправим буфер с клиента
ClientSocket.Socket.SendBuf(Buf,4);
// добавим в ChatMemo сообщение о передачи данных
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Данные переданы.");
Shape.Height:=Buf;
Shape.Width:=Buf;
If Buf>
Case Buf of
0: Shape.Brush.Color:=clRed;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf;
ColorCBox.ItemIndex:=Buf;
end;
Немного изменим процедуру на чтение сообщения от клиента, выключим возможность приема сообщений и настроем на прием буфера (OnClientRead ):
procedure TForm1.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
len: Byte;
begin
// добавим в ChatMemo клиентское сообщение
len:=Socket.ReceiveLength;
Socket.ReceiveBuf(Buf,len);
// применим изменения к своему Shape
Shape.Height:=Buf;
Shape.Width:=Buf;
If Buf>0 then Shape.Shape:=stCircle {круг}
else Shape.Shape:=stRectangle; {прямоуголник}
// выбор цвета по таблице истинности
Case Buf of
0: Shape.Brush.Color:=clRed;
1: Shape.Brush.Color:=clGreen;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf;
ColorCBox.ItemIndex:=Buf;
Value1Edit.Text:=IntToStr(Buf);
Value2Edit.Text:=IntToStr(Buf);
end;
Осталось аналогичным образом изменить процедуру на чтение клиентом сообщения от сервера (OnRead ):
procedure TForm1.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
var
len: Byte;
begin
// добавим в ChatMemo сообщение с сервера
// ChatMemo.Lines.Add(Socket.ReceiveText());
// принимаем буфер неизвестного размера
len:=Socket.ReceiveLength;
Socket.ReceiveBuf(Buf,len);
// применим изменения к своему Shape
Shape.Height:=Buf;
Shape.Width:=Buf;
If Buf>0 then Shape.Shape:=stCircle {круг}
else Shape.Shape:=stRectangle; {прямоуголник}
// выбор цвета по таблице истинности
Case Buf of
0: Shape.Brush.Color:=clRed;
1: Shape.Brush.Color:=clGreen;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf;
ColorCBox.ItemIndex:=Buf;
Value1Edit.Text:=IntToStr(Buf);
Value2Edit.Text:=IntToStr(Buf);
// добавим в ChatMemo сообщение о приходе данных
ChatMemo.Lines.Add("["+TimeToStr(Time)+"] Пришли данные.");
end;
Это и все, что нужно сделать. Обратите внимание на то, что если не отключать принятие сообщения (Socket.ReceiveText() ) и попытаться одновременно принять и сообщение и буфер, то это приведет к потере данных одной из функций. Решить эти проблемы можно за счет перевода сообщения в формат буфера вот так:
For i:=1 to Length(TextEdit.Text) do
Buf:=Copy(TextEdit.Text,i,1);
Очевидно, что массив станет на одну ячейку больше и изменит свой тип на символьный или строковый. Buf при этом будет служить меткой, чем является пришедший буфер: сообщением или данными. В процедурах получения сообщений нужно сделать условие примерно так:
len:=Socket.ReceiveLength;
Socket.ReceiveBuf(Buf,len);
If Buf="t" then
Begin
… делать операцию по соединению в строку (через цикл)
end;
If Buf="c" then
Begin
… делать операцию по изменению параметров Shape
end;
Сокеты (от socket (англ.) - разъём, гнездо) - это программный интерфейс, обеспечивающий обмен информацией между процессами.
Одним из основных достоинств сокетного обмена информацией в сети можно назвать его гибкость. Главный принцип работы с сокетами состоит в отправке последовательности байт другому компьютеру, это может быть простое текстовое сообщение или файл.
Важно различать два типа сокетов: клиентские сокеты , исерверные сокеты .
Для работы с «клиентским» типом сокетов в Delphiсуществует компонентTClientSocket , с «серверными» сокетами можно работать при помощи компонентаTServerSocket.
Установка компонентов
Зачастую компоненты TServerSocket и TClientSocket не входят в стандартный пакет установки Delphi, но их можно установить дополнительно.
Зайдите на вкладку компонентов «Internet», и проверьте присутствуют ли там компоненты TServerSocket и TClientSocket, если нет, то установите их. Зайдите в меню "Component/Install Packages", затем нажмите кнопку "Add". В открывшемся диалоговом окне нужно найти файл "dclsocketsXX.bpl" (он лежит в папке bin, которая находится в папке с Delphi), где XX - это числовой номер версии вашего Delphi. Найдите файл, нажмите "Открыть", а затем в окне "Install Packages" нажмите "OK". Теперь, во вкладке "Internet" появились два компонента - TServerSocket и TClientSocket.
Работа с клиентскими сокетами (tClientSocket)
1) Определение свойств Port и Host. Для успешного соединения свойствамPort иHost компонента TClientSocket необходимо присвоить некоторые значения. В свойстве Port нужно указать номер порта для подключения (1 – 65535, но лучше брать из диапозона 1001 – 65535, потому что номера до 1000 могут оказаться заняты системными службами).
Host - хост-имя или IP-адрес компьютера, с которым требуется соединиться. Например, rus.delphi.com или 192.128.0.0.
2) Открытие сокета. Будем рассматривать сокет как очередь символов, передающихся с одного компьютера на другой. Открыть сокет можно, вызвав методOpen (компонент TClientSocket) или присвоив значениеTrue свойствуActive . Тут нелишним будет поставить обработчик исключения на случай неудавшегося соединения.
3) Отправка/прием данных.
4) Закрытие сокета. По завершению обмена данными нужно закрыть сокет, вызвав методClose компонентаTClientSocket или присвоив значениеFalse свойствуActive .
Основные свойства компонента TClientSocket |
|
Показатель того, открыт или закрыт сокет. Открыт – значение True, закрыт – значение False. Доступно для записи. |
|
Хост-имя, с которым нужно соединиться |
|
IP-адрес компьютера, с которым нужно соединиться. В отличие от Host, здесь может быть указан только IP. Разница состоит в том, что если в Host указано буквенное имя компьютера, то IP запросится у DNS |
|
Номер порта компьютера, с которым нужно соединиться (1-65535) |
|
ClientType |
Содержит тип передачи данных: ctBlocking - синхронная передача (OnRead иOnWrite не работают). Синхронный тип подключения подходит для поточного обмена данными; ctNonBlocking - асинхронная передача (отправка/приём данных может производиться при помощи событийOnRead иOnWrite ) |
Основные методы компонента TClientSocket |
|
Открывает сокет (присвоение свойству Active значения True) |
|
Закрывает сокет (присвоение свойству Active значения False) |
|
Основные события компонента TClientSocket |
|
OnConnect |
Возникает при установке подключения. В обработчике уже можно приступать к авторизации или отправке/приему данных |
OnConnecting |
Также возникает при подключении. Отличается от OnConnect тем, что подключение еще не установлено. Чаще всего используется, например, чтобы обновить статус |
OnDisconnect |
Событие возникает при закрытии сокета вашей программой, удаленным компьютером или из-за сбоя |
Событие возникает при ошибке. Во время открытия сокета это событие не поможет выловить ошибку. Во избежание появления сообщения от Windows об ошибке, лучше позаботиться о внутренней обработке исключений путём помещения операторов открытия в блок «try..except » |
|
OnLookup |
Событие возникает при попытке получить IP-адрес от DNS |
Событие возникает при отправке вам каких-либо данных удалённым компьютером. При вызове OnRead возможна обработка принятых данных |
|
Событие возникает, когда вашей программе разрешено писать данные в сокет |