Delphi: сетевое программирование. Создание клиент-серверного приложения в Delphi

Delphi: сетевое программирование. Создание клиент-серверного приложения в Delphi

25.05.2019

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

Для начала давайте найдем необходимые компоненты TServerSocket и TClientSocket. Чаще всего они не входят в стандартный пакет установки Delphi, но их можно установить дополнительно. Для этого запустим Delphi, зайдем в «Component/Install Packages», затем нажмем кнопку «Add». В открывшемся диалоговом окне нужно найти файл «dclsocketsXX.bpl» (он лежит в папке bin, которая находится в папке с Delphi), где XX — это числовой номер версии вашего Delphi. Находим файл, нажимаем «Открыть», а затем в окне «Install Packages» нажимаем «OK».

Теперь, во вкладке «Internet» появились два компонента — TServerSocket и TClientSocket, работу с которыми мы и будем рассматривать в этом уроке.

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

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

Сервер служит обычно для сетевого взаимодействия клиентов. Таким образом клиент может отсылать какую-либо информацию на сервер, а сервер будет эту информацию отсылать остальным клиентам.

Давайте рассмотрим работу сетевого чата. Предположим, что к серверу чата подключено несколько клиентов. Один из клиентов отправляет новое сообщение на сервер. Сервер принимает сообщение и отправляет его обратно остальным клиентам. Остальные клиенты принимают сообщение с сервера и таким образом и осуществляется передача сообщений чата.

Такие сетевые приложения, в работе которых участвуют клиент и сервер, называются клиент-серверными приложениями.

Теперь попробуем разобраться, как же все таки работать с этими компонентами. Попробуем реализовать аналогичный клиент-серверный чат.

Для начала создадим сервер, который должен принимать сообщение и отсылать его всем клиентам обратно.

Создадим новое приложение в Delphi, поместим на него компонент TServerSocket и в его параметрах укажем следующие значения:

Active = false
Name = srv
Port = 22500
ServerType = stNonBlocking

Параметр Active отвечает за состояние сервера, активен он или не активен на данный момент.

В событие OnCreate формы не забудем добавить srv.active:=true, чтобы сервер запустился.

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

Также поместим на форму компонент Memo1 (дадим ему название log), который нам понадобиться для того, чтобы отслеживать работу чата.

Теперь у srv создадим событие onClientConnect, в котором мы сможем фиксировать подключения клиентов к серверу. В коде самого события напишем:

procedure TForm1.srvClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
log.Lines.Add(‘Подключился клиент с IP адресом ‘+Socket.RemoteAddress);
end;

Таким образом мы можем узнать IP адрес подключившегося к серверу клиента.

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

Обработчик события onClientRead служит для получения и обработки пришедшей от клиента информации. Рассмотрим пример:

procedure TForm1.srvClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
str:string;
i:integer;
begin
str:=Socket.ReceiveText;
log.Lines.Add(str);
for i:= 0 to srv.Socket.ActiveConnections -1 do
srv.Socket.Connections[i].SendText(str);
end;

Переменной str мы присваиваем значение принятого текстового сообщения от клиента и добавляем принятое сообщение в лог. После этого мы отсылаем сообщение всем клиентам. Кол-во подключенных клиентов можно узнать в srv.Socket.ActiveConnections.

Предположим, если мы хотим отправить сообщение конкретному клиенту, то это можно сделать так:

srv.Socket.Connections[a].SendText(‘test message’);

В данном случае в качестве переменной a выступает числовой идентификатор в srv.Socket.Connections. Эти идентификаторы находятся в диапазоне от 0 до srv.Socket.ActiveConnections -1. Но как же, например, отправить сообщение клиенту с определенным IP адресом, который нам извествен заранее? Делается это так:

for i:= 0 to srv.Socket.ActiveConnections -1 do
if srv.Socket.Connections[i].RemoteAddress= ‘123.123.123.123’ then begin
srv.Socket.Connections[i].SendText(‘test message’);
break;
end;

Мы перебираем все соединения на сервере. Если IP адрес клиента совпадает с «123.123.123.123» (может быть и любой другой), то отправляем сообщение этому клиенту и завершаем цикл.

Ну и наконец, рассмотрим еще одно событие у TServerSocket — onClientDisconnect, которое обрабатывает отключение клиента от сервера.

procedure TForm1.srvClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
log.Lines.Add(‘Клиент ‘+socket.RemoteAddress+’ отключился от сервера.’);
end;

Теперь перейдем к клиенту. Создадим новое приложение Delphi и поместим в него компонент TClientSocket и дадим этому компоненту имя «client». Присвоим параметрам компонента client значения:

Active = false
Port = 25500
ClientType = ctNonBlocking
Address = 127.0.0.1

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

Значение Port у клиента необходимо присвоить такое же как и у сервера!

Также добавим компонент TMemo с именем log, чтобы отображать пришедшие от сервера сообщения.

Рассмотрим параметр OnCreate формы, где производиться подключение к серверу.

procedure TForm1.FormCreate(Sender: TObject);
begin
{Пытаемся установить соединение}
ClientSocket1.Open;
end;

Теперь разместим на форме клиента кнопку Button1 и однострочное текстовое поле Edit1. Они нам понадобятся для отправки сообщений на сервер. В событии OnClick у компонента Button1 напишите:

Client.Socket.SendText(Edit1.Text);

Таким образом на сервер будет отправляться сообщение, введенное в Edit1.

После того как сообщение было отослано на сервер, сервер отсылает сообщение всем клиентам, поэтому нужно принять сообщение от сервера. Для этого нам понадобиться событие OnRead:

procedure ClientRead(Sender: TObject; Socket: TCustomWinSocket);
begin
{Если пришло сообщение — добавляем его в log}
Log.Lines.Add(Socket.ReceiveText);
end;

Осталось только написать событие отключения от сервера. Для этого можем создать еще одну кнопку и в ее событии OnClick указать

Client.close;

Запустите клиент и сервер. Попробуйте отослать сообщение на сервер, сервер в ответ должен вернуть его вам. Если возникает ошибка «Asynchronous socket error 10061», то это скорее всего связано с недоступностью сервера. Проверьте соответствие портов, IP адресов и работоспособность сетевого соединения.

Сегодня остается весьма актуальной тема в сфере информационных технологий, посвященная разработке и развертыванию архитектуры «клиент - сервер». Связано это с тем, что информационные системы различных масштабов находят все большее применение в различных областях. Кроме того, структура любой базы данных создается по определенным параметрам, причем таким образом, что работать с ней в штатном режиме возможно только с применением специальных приложений. Поэтому реализация архитектуры «клиент - сервер» - очень важная задача, возникающая перед отделами информационных технологий на различных объектах, успешное решение которой позволит работать предприятию оптимально и бесперебойно.

Сервер на основе сокетов

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

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

Схема работы архитектуры «Клиент - сервер»

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

1. Свойства ServerType и Port. Для того чтобы пользователь имел возможность подключиться к серверу, важно гарантировать, чтобы порт, используемый как сервером, так и клиентом совпадал. Именно назначение данного параметра производится в свойстве Port. Тип подключения определяется параметром ServerType.

2. Процесс открытия сокета. Для начала сессии «клиент - сервер» важно открыть сокет и порт. Используется свойство Listen.

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

Применение TServerSocket в приложениях на языке Delphi «Клиент - сервер»

Для отправления и приема информации от пользователя используются события OnClientWrite и OnClientRead. При этом взаимодействовать с пользователем можно через такой параметр, как ClientSocket. При работе часто используются следующие методы и свойства:

Количество подключенных в текущий момент пользователей;

Число активных процессов;

Число свободных процессов;

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

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

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

Документация

На сегодняшний день литературы по разработке приложений «клиент - сервер» Delphi, связанной с разработкой и применением сокетов, представлено очень мало. Поэтому для детального изучения данного аспекта можно обратиться к документации по Linux и Unix-системам, т.к. в ней достаточно подробно рассматривается технология развертывания приложений с применением сокетов, однако, как правило, на языках Perl или С++.

Итак создадим новую БД, назовем ее Test и сохраним ее в папке C:\ClientServer\Server\Data (процесс создания новой БД в Аксесе я, по понятным, причинам опускаю, и приведу только структуру таблиц - входящих в нашу базу).

1. Таблица первая, назовем ее First

2. Таблица вторая, а назовем ее уже по хитрому - Second

Ну вот, как бы, с базой и все.

Программа

2.1. Приложение сервер.

Созданим новое приложение и сохраним его под именем Server в папке C:\ClientServer\Server. Добавим в приложение Remote Data Module с вкладки Multitier репозитария (Рис. 1).

Рис. 1 Репозитарий с выделенным Remote Data Module.

При добавлении RDM мастер добавления запросит параметры данного модуля - там введем имя нашего сервера "Test".


Все остальное оставим без изменений. После нажатия кнопки "Ок" в проекте появится форма подобная обычному дата модулю с именем Test. Сохраним ее под именем RDMFrm.pas .

Выложим на нее компоненты ADOConnection (одна штука), ADOTable и DataSetProvider (по две штуки). Компоненты ADOTable и DataSetProvider обзовем - adotFirst, adotSecond, dspFirst и dspSecond соответственно. По двойному клику на компоненте ADOConnection вывалится мастер Connection String. Выбераем пункт "Use Connection String" и нажимаем кнопочку Build. В появившемся окне "Свойства связи с данными" выберем пункт "Microsoft Jet 4.0 OLE DB Provider" и нажмем кнопку "Далее". На владке "Подключение" введем путь к БД, нажмем кнопочку "Проверить подключение" и получим сообщение что проверка подключения выполнена. После чего закроем мастер путем нажатия на кнопке ОК. Еще у компонента ADOConnection свойство есть LoginPrompt, которое установим в False и Connected - True. У компонетов ADOTable свойство Connection установим равным ADOConnection путем выбора из выпадающего списка. Свойство TableName установим в First и Second соответсвенно. Свойство CursorType - ctDynamic, свойство TableDirect - True. Вызвать FieldsEditor и добавить туда все поля.

У компонентов DataSetProvider:

Вот на этом процесс создания примитивного MIDAS сервера можно считать завершенным. Чтобы зарегистрировать сервер в подсистеме DCOM нужно запустить приложение с параметром /regserver - Server.exe /regserver.
И последний штрих, это приложение СокетСервер от Борланда, которое находится в папке Delphi?/Bin/scktsvr.exe. Для того чтобы клиент мог видеть сервер нужно запустить scktsvr.exe (если у вас установлена Win NT/w2k/XP то можно это приложение зарегистрировать как службу, для этого его нужно запустить с параметром scktsvr.exe /install)

2.2. Приложение клиент

Создадим новое приложение, и сохраним его в папке C:\ClientServer\Client под именем Client. Разместим на главной форме по два компонента ClientDataSet(обзовем их cdsFirst, cdsSecond), DataSource(обзовем их dsFirst, dsSecond), DBGrid(обзовем их dbgFirst, dbgSecond) один компонент SocetConnection.

SocetConnection

Если вы все сделали правильно, то при установке свойства Connected в True, должно запустится приложение сервер.

ClientDataSet

Запустим Редактор Полей (двойной клик по компоненту) и занесем туда все поля. Для компонентов ClientDataSet напишем обработчик sdsAfterPost

Procedure TMainForm.cdsAfterPost(DataSet: TDataSet);
begin
with (DataSet as TClientDataSet) do begin
if State in then
try
ApplyUpdates(0);
Refresh;
except
raise EDatabaseError.Create("Ошибочка вышла");
end;
end;
end;

И подставим его на обработку события AfterPost обоих ClientDataSet"ов.

DataSource

Свойство Значение
DataSet cdsFirst и cdsSecond соответственно

DBGrid

Свойство Значение
DataSource dsFirst и dsSecond соответственно

Запустим Редактор колонок (двойной клик по компоненту) и занесем туда все поля.
Положим на форме возле Гридов по кнопке и в обработчик onClick запишем:

  • для dbgFirst - cdsFirst.Post
  • dbgSecond - cdsSecond.Post

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


Если статья получит отзывы и заслужит интерес у читателей, то продолжение не заставит себя долго ждать.

выбрать меню: 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;



© 2024 beasthackerz.ru - Браузеры. Аудио. Жесткий диск. Программы. Локальная сеть. Windows