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

01.05.2019

Итак, сначала о компоненте сервера IdTCPServer (закладка Indy Servers ). Для использования возможностей сервера этот компонент нужно поместить на форму (компонент неотображаемыи). При настройке компонента полезными являются следующие его свойства:

  • Active - активизирует или деактивизирует сервер (по умолчанию False );
  • Bindings - настраивает серверные сокеты (присоединяет их к определенно му порту компьютера, позволяет задавать диапазон IP-адресов и портов клиентов при помощи диалогового окна настройки свойства Binding ;
  • ListenQueue - численное значение, ограничивающее максимальное количество запросов на установление соединения от клиентов в очереди;
  • MaxConnections - позволяет ограничить максимальное количество клиентов, присоединенных к серверу;

Рассмотрим несколько подробнее настройку серверных гнезд с использованием свойства Bindings . Так, на рис. 1 показано, как при помощи диалогового окна свойства Binding настроить сервер на обслуживание клиентов с любыми IP-адресами, при этом серверный сокет присоединяется к порту 12340.

Рис. 1. Настройка свойства Binding .

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

Теперь рассмотрим, как сконфигурировать клиент (IdTCPClient - закладка Indy Clients ), чтобы он был способен взаимодействовать с нашим сервером. Чтобы использовать компонент ТСР-клиента, достаточно поместить его на форму (компонент также неотображаемый).

После этого как минимум нужно настроить следующие его свойства (остальные упоминаются по мере необходимости в приведенных далее примерах):

  • Host - имя или IP-адрес компьютера, на котором запущен сервер;
  • Port - номер порта, к которому присоединен серверный сокет.

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

Простой обмен данными

В начале работы с описанными в предыдущем разделе компонентами IdTCPServer и IdTCPClient рассмотрим создание несложного клиент-серверного приложения, клиентская и серверная части которого выполняют следующие функции.

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

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

Рис. 2. Внешний вид простого сервера

Текстовое поле (Edit ) с количеством обработанных запросов имеет имя txtCount , а текстовое поле с адресом последнего обслуженного компьютера названо txtFrom . Вся работа сервера заключается в обработке события Execute для компонента IdTCPServer , помещенного на форму (присоедините этот компонент к порту 12340 и установите значение свойства Active = True ) (листинг 1).

Листинг 1. Реализация простого сервера

Procedure TForm1.FormCreate(Sender: TObject); begin section:= TCriticalSection.Create; end; procedure TForm1.IdTCPServer1Execute(AContext: TIdContext); var strText: String; begin //Принимаем от клиента строку strText:= AContext.Connection.Socket.ReadLn; //Отвечаем AContext.Connection.Socket.WriteLn("Took the line: " + strText); //Обновим сведения на форме сервера (сервер многопоточный, //поэтому используем синхронизацию section.Enter; Inc(processed, 1); txtCount.Text:= IntToStr(processed); txtFrom.Text:= AContext.Connection.Socket.Binding.PeerIP; section.Leave; //Закрываем соединение с пользователем AContext.Connection.Disconnect; end;

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

Анализируя листинг 1 , можно заметить, что даже в рассматриваемом простейшем сервере пришлось применить синхронизацию при обновлении внешнего вида формы при помощи критической секции (необходимо дополнительно добавить имя модуля SyncObjs в секцию uses ).

Теперь рассмотрим реализацию клиентской части (проект SimpleClient ). Внешний вид клиентского приложения приведен на рис. 2.

Рис. 2. Внешний вид клиента

Естественно, что для работы клиентского приложения на форму помещен экземпляр компонента IdTCPClient (его имя - IdTCPClient1 ). Свойству Port этого компонента нужно присвоить значение 12340. Текстовое поле (Edit ) для ввода строки, подлежащей отправке не сервер, имеет имя txtMessage . Текстовое поле (Edit ), в которое вводится имя или адрес сервера, названо txtServer . Поле со строками ответов (Memo ) имеет имя txtResults .

Вся работа клиентского приложения выполняется при нажатии кнопки Обработать . Текст соответствующего обработчика приведен в листинге 2 .

Листинг 2. Реализация простого клиента

Procedure TForm1.Button1Click(Sender: TObject); begin //Соединяемся с сервером и посылаем ему введенную команду IdTCPClient1.Host:= txtServer.Text; IdTCPClient1.Connect; IdTCPClient1.Socket.WriteLn(txtMessage.Text); txtMessage.Text:= ""; //Ожидаем ответ и закрываем соединение txtResults.Lines.Append(IdTCPClient1.Socket.ReadLn); IdTCPClient1.Disconnect; end;

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

Исходный код . Выполнен на Delphi XE.

Слежение за компьютером по сети (IdTCPServer, IdTCPClient)

Теперь рассмотрим более интересный пример использования сетевых компонентов IdTCPServer и IdTCPCLient , который может быть полезен для людей, имеющих отношение к администрированию компьютеров сети.

Серверная программа предварительно запускается на наблюдаемом компьютере. В этом примере программа-сервер позволяет клиентской программе получать следующие сведения о компьютере, на котором она (программа-сервер) запущена:

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

Для получения указанных сведений про грамма-клиент должна послать серверу следующие строковые значения:

  • get_screen_width - для получения ширины и get_screen_height -для получения высоты экрана в пикселах;
  • get_screen_colors - для получения значения установленной для монитора глубины цвета (бит на точку);
  • get_screen - для получения полноразмерной копии экрана;
  • get_screen: X, Y - для получения копии экрана, приведенной к размеру Х х Y .

Сначала рассмотрим реализацию сервера (проект SpyServer ). Весь код, обеспечивающий работу сервера, помещен в модуле Unit1.pas формы Form1 . Обработчик запросов клиентов - главная процедура для сервера - приводится в листинге 3.

Листинг 3. Обработчик клиентских запросов

Procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread); var strText: String; width, height, i: Integer; dc: HDC; begin //принимаем от клиента строку strText:= AThread.Connection.ReadLn; //определяем, что нужно выполнить if strText = "get_screen_height" then //возвратим высоту экрана AThread.Connection.WriteInteger(Screen.Height) else if strText = "get_screen_width" then //возвратим ширину экрана AThread.Connection.WriteInteger(Screen.Width) else if strText = "get_screen_colors" then begin //возвратим количество бит на точку dc:= GetDC(0); AThread.Connection.WriteInteger(GetDeviceCaps(dc, BITSPIXEL)); ReleaseDC(0, dc) end else if strText = "get_screen" then //возвратим полноразмерную копию экрана SendScreen(Screen.Width, Screen.Height, AThread.Connection) else begin //строка вида "get_screen:x, y" //Определим значени высоты и ширины переданные пользователем strText:= Copy(strText, 12, Length(strText) - 11); i:= Pos(",", strText); //положение запятой width:= StrToInt(Copy(strText, 1, i - 1)); height:= StrToInt(Copy(strText, i+1, Length(strText) - i)); //возвратим копию экрана SendScreen(width, height, AThread.Connection); end; end;

Используемая в листинге 3 процедура SendScreen, отправляющая клиенту копию экрана, приведена в листинге 4.

Листинг 4. Снятие копии экрана

//процедура снимает копию экрана, приводит полученное //изображение к заданному размеру и отправляет //преобразованное изображение клиентской программе procedure SendScreen(width1: Integer; height1: Integer; Connection: TIdTCPServerConnection); var ScreenCopy: TCanvas; gr: TBitmap; stream: TMemoryStream; rcDest, rcSource: TRect; begin rcDest:= Rect(0,0,width1,height1); //конечный размер изображения rcSource:= Screen.DesktopRect; //исходный размер изображения //создаем канву и присоединяем ее к контексту Рабочего стола ScreenCopy:= TCanvas.Create; ScreenCopy.Handle:= GetDC(0); //создаем объект для хранения копии экрана и копируем изображение gr:= TBitmap.Create; gr.Height:= height1; gr.Width:= width1; gr.Canvas.CopyRect(rcDest, ScreenCopy, rcSource); ReleaseDC(0, ScreenCopy.Handle); //сохраняем изображение в поток данных stream:= TMemoryStream.Create; gr.SaveToStream(stream); //отправляем изображение клиенту Connection.WriteStream(stream, true, true); stream.Clear; stream.Free; gr.Free; end;

Как можно увидеть, даже самая сложная операция рассматриваемого сервера - копирование изображения - реализуется довольно просто благодаря наличию такого стандартного класса, как TMemoryStream .

Компонент IdTCPServer (с именем IdTCPServer1 ) в этом примере присоединен к порту 12341 (не забудьте также установить свойство Active = True ).

Теперь о реализации клиентского приложения (проект SpyClient ). Внешний вид формы (Form1 ) клиента во время работы приводится на рис. 3 (видно, что пользователь наблюдаемого компьютера только что проиграл в игру Сапер).

Рис. 3

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

Таблица 1. Основные компоненты формы клиента слежения и их свойства

Работа клиентского приложения начинается с соединения с сервером. Код, отвечающий за эту опреацию, приведен в листинге 5.

Листинг 5.Соединение с сервером

Procedure TForm1.cmbConnectClick(Sender: TObject); begin if cmbConnect.Caption = "Подключиться" then begin if txtServer.Text = "" then //не введено имя сервера MessageDlg("Введите имя машины-сервера в текстовом поле", mtInformation, , 0) else begin //подключаемся к серверу IdTCPClient1.Host:= txtServer.Text; try IdTCPClient1.Connect; except MessageDlg("Не удается соединиться с указанным сервером", mtError, , 0); Exit; end; end end else //отключаемся от сервера IdTCPClient1.Disconnect; end;

Если соединение с сервером произошло успешно, то выполняется обработчик TForm1.IdTCPClient1Connected , подготавливающий приложение-клиент к периодическим запросам данных с сервера (листинг 6).

Листинг 6. Действия выполняемые при соединении с сервером

Procedure TForm1.IdTCPClient1Connected(Sender: TObject); begin txtServer.Enabled:= False; cmbConnect.Caption:= "Отключиться"; //начинаем периодически запрашивать данные с сервера Timer1.Enabled:= True; //выполним первый запрос, не дожидаясь срабатыввания таймера Timer1Timer(nil); end;

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

Листинг 7. Действия при отсоединении от сервера

Procedure TForm1.IdTCPClient1Disconnected(Sender: TObject); begin txtServer.Enabled:= True; cmbConnect.Caption:= "Подключиться"; Timer1.Enabled:= False; end;

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

Procedure TForm1.Timer1Timer(Sender: TObject); var stream: TMemoryStream; begin //запрашаваем у сервера данные о наблюдаемом компьютере with (IdTCPClient1) do begin //...разрешение WriteLn("get_screen_width"); WriteLn("get_screen_height"); lblResolution.Caption:= IntToStr(ReadInteger) + "x" + IntToStr(ReadInteger); //...глубина цвета WriteLn("get_screen_colors"); lblColors.Caption:= IntToStr(ReadInteger); //...копия экрана //.....1-й вариант - копирование экрана без сжатия //WriteLn("get_screen"); //.....2-й вариант - сжатие на стороне сервера WriteLn("get_screen:" + IntToStr(imgScreen.Width) + "," + IntToStr(imgScreen.Height)); //....получаем данные stream:= TMemoryStream.Create; ReadStream(stream); stream.Position:= 0; //....формируем изображение imgScreen.Picture.Bitmap.LoadFromStream(stream); stream.Clear; stream.Free; end; end;

В тексте листинга 8 создано большое количество комментариев, поэтому дополнительно пояснять его нет смысла. Остановимся лишь на том, зачем в процедуре TForm1.Timer1Timer предусмотрено два варианта получения изображения с сервера.

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

Если же не использовать сжатие изображения на сервере, возрастает нагрузка на сеть при передаче полноразмерной копии экрана, а вся работа по сжатию изображения возлагается на компонент imgScreen (то есть дополнительно тратится процессорное время на компьютере клиента). При большом разрешении экрана наблюдаемого компьютера (или при наблюдении сразу за несколькими компьютерами) машина клиента, если она недостаточно мощная, может начать весьма ощутимо "тормозить". Качество сжатого изображения при этом получается более высоким.

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

Исходный код . Выполнен на Delphi 7.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

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

Размещено на http://www.allbest.ru/

КОНТРОЛЬНАЯ РАБОТА

Разработка приложения «клиент-сервер» в D elphi

Обзор компонент

Для работы с ADO на вкладке компонентов ADO есть шесть компонентов: TADOConnection, TADOCommand, TADODataSet, TADOTable, TADOQuery, TADOStoredProc.

Рис. 1. Палитра компонент ADO

TADOConnection используется для указания базы данных и работы транзакциями.

TADOTable - таблица доступная через ADO.

TADOQuery - запрос к базе данных. Это может быть как запрос, в результате которого возвращаются данные и базы (например, SELECT), так и запрос, не возвращающий данных (например, INSERT).

TADOStoredProc - вызов хранимой процедуры.

TADOCommand и TADODataSet являются наиболее общими компонентами для работы с ADO, но и наиболее сложными в работе. Оба компонента позволяют выполнять команды на языке провайдера данных (так в ADO называется драйвер базы данных).

Разница между ними в том, что команда, исполняемая через TADODataSet, должна возвращать набор данных и этот компонент позволяет работать с ними средствами Delphi (например, привязать компонент типа TDataSource). А компонент TADOCommand позволяет исполнять команды не возвращающие набор данных, но не имеет штатных средств Delphi для последующего использования возвращенного набора данных.

Очевидно, что все компоненты должны связываться с базой данных. Делается это двумя способами либо через компонент TADOConnection либо прямым указанием базы данных в остальных компонентах. К TADOConnection остальные компоненты привязываются с помощью свойства Connection, к базе данных напрямую через свойство ConnectionString.

программирование база данные алгоритм

Таблица 1. Основные компоненты вкладки ADO

Название

Основные свойства

Комментарии

Отвечает за связь с БД

ConnectionString

Содержит настройки для соединения с БД.

используется для указания того, нужно ли выводить диалоговое окно, предназначенное для ввода идентификатора пользователя и пароля при соединении с базой данных;

указывает активность соединения: True означает соединение открыто, False-закрыто;

Основные методы

Комментарии

используется для запуска новой транзакции и возвращает уровень вложенных транзакций;

закрывает открытое в текущий момент соединение

записывает в базу данных все изменения, произведенные текущей транзакцией, и завершает ее

используется для выполнения запросов или хранимых процедур. Исполняемый оператор передается данному методу через параметр;

открывает соединение с источником данных, заданным в ConnnectionString

отменяет все изменения, произведенные со времени последнего вызова метода BeginTrans, и завершает транзакцию без сохранения изменений.

Основные свойства

Комментарии

TADOQuery Предназначен для работы с запросами

Предназначен для работы с хранимыми процедурами

Cодержит имя компонента ADOConnection

Содержит имя хранимой процедуры

Алгоритм работы

1. Создаем новый DataModule, Выбираем пункт меню File\New\DataModule. Называем его DM.

Кидаем на DM компоненты ADOConnection, ADOQuery и три штуки ADOStoredProcedure. Как это выглядит смотри на рис.2

Рис.2 Вид DM

2. Настроить свойства компонентов

ADOConnection - смотри методичку.

ADOQuery 1 переименовываем в QVrash и в свойстве SQL пишем запрос:

Select * from Vrash

Свойство Connection - ADOConnectionl.

Создать статические поля для QVrash.

Для компонента ADOStoredProcl устанавливаем следующие свойства

* Name - ASPInsVrash

* Connection - ADOConnection 1

* ProcedureName - MainInsert; 1

Если щелкнешь на свойстве Parameters, то увидишь те параметры, которые мы будем передавать в процедуру.

Для компонента ADOStoredProc2 устанавливаем следующие свойства

* Name - ASPEdVrash

* Connection - ADOConnectionl

* ProcedureName - MainEdit; 1

Для компонента ADOStoredProc3 устанавливаем следующие свойства

* Name - ASPDelVrash

* Connection - ADOConnectionl

* ProcedureName - MainDelete; 1

На форму 1 разместить компоненты DBGrid, два Button, DataSource, MainMenu

Рис3. Форма 1

Кнопки переименовать в соответствии с рисунком

Для кнопки Обновить код

DM.QVrash.Close; //обновить н/д

Создаём меню

Рис. 3. Меню «Правка»

Создаём форму для добавления/редактирования: Компоненты EDIT

Программный код для пункта меню на форме 1 - ДОБАВЛЕНИЕ

Form3.Edit1.text:="";

Form3.Edit2.text:="";

Form3.Edit3.text:="";

Form3.Edit4.text:="";

Form3.showmodal;

Программный код для кнопки на форме 3 - ВСТАВКА

B egin

showmessage("Заполнены не все поля");

E nd;

T ry

DM.ADOConnection1.BeginTrans;

with DM.ASPInsVrash do

Parameters.ParamByName("@ ID_Physician").Value:=StrToInt(Edit1.text);

Parameters.ParamByName("@ YYYY`).Value:=Edit2.Text;

Parameters.ParamByName("@YYYY ").Value:=Edit3.Text;

Parameters.ParamByName("@YYYY ").Value:=Edit4.text;

DM. ASPInsVrash.ExecProc;

E xcept

DM. QVrash.Close;

DM. QVrash.Open;

Размещено на http://www.allbest.ru/

Программный код для пункта меню на форме 1 - РЕДАКТИРОВАНИЕ

B egin //Редактирование

n:=DM. QVrash.XXXXX.Value;

DM. QVrash.Close;

DM. QVrash.Open;

DM. QVrash.Locate(XXXX,n,);

Form3.Label6.Caption:=IntToStr(DM.QVrash.XXXXXvalue);

Form3.Edit1.text:=inttostr(DM.QVrashYYYYYY.Value);

Form3.Edit2.text:=inttostr(DM.QVrashYYYYY.Value);

Form3.Edit3.text:=floattostr(DM.QVrashYYYYY.Value);

Form3.Edit4.text:=DateToStr(DM.QVrash.YYYY.Value);

Form3.Showmodal;

DM. QVrash.Close;

DM. QVrash.Open;

Программный код для кнопки на форме 3 - РЕДАКТИРОВАНИЕ

B egin

If (Edit1.text="") or (Edit2.Text="")or (Edit3.Text="") or (Edit4.Text="") then

B egin

ShowMessage("Заполнены не все поля");

E nd ;

T ry

DM.ADOConnection1.BeginTrans;

with DM.ASPEdVrash do

Parameters.ParamByName("@XXXX).Value:= DM.a QVrash.XXXXX.value;

Parameters.ParamByName("@YYYY).Value:=Edit1.text;

Parameters.ParamByName("@YYYYY).Value:=Edit2.Text;

Parameters.ParamByName("@YYYY").Value:=Edit3.Text;

Parameters.ParamByName("@YYYY).Value:=StrToDate(Edit4.text);

DM.ASPEd.ExecProc;

DM.ADOConnection1.CommitTrans;

E xcept

DM.ADOConnection1.RollbackTrans;

ShowMessage("Невозможно выполнить. Повторите.");

DM. QVrash.Close;

Программный код для пункта меню на форме 1 - УДАЛЕНИЕ

Begin // Удаление

if MessageDlg("Вы уверены что хотите удалить запись?", mtConfirmation,,0)=mrYes then

DM.ASPDel.Parameters.ParamByName("@XXXX).Value:=DM.ADOQuery1XXXXX.Value;

T ry

DM.ADOConnection1.BeginTrans;

DM.ASPDelVrash.ExecProc;

DM.ADOConnection1.CommitTrans;

E xcept

ShowMessage("Удаление не прошло!"+#13+"Запись заблокирована,либо уже удалена!");

DM.ADOConnection1.RollbackTrans;

E xit;

DM. QVrash.Close;

DM. QVrash.Open;

Размещено на Allbest.ru

Подобные документы

    Разработка информационной системы административного управления. Выбор языка и среды программирования. Структура взаимодействия информации. Требования к программно-аппаратному окружению. Создание программы в Delphi и связывание ее с базой данных.

    курсовая работа , добавлен 08.10.2015

    Borland Delphi 7 как универсальный инструмент разработки, применяемый во многих областях программирования, функции: добавление информации об абитуриентах в базу данных, формирование отчетов. Рассмотрение и характеристика основных компонентов Delphi.

    контрольная работа , добавлен 18.10.2012

    Разработка программного обеспечения для работы с информацией и ее обработкой на языке программирования Delphi. Описание алгоритмов процедуры работы со стеком - добавление, удаление элементов, редактирование записи. Инструкция по использованию программы.

    курсовая работа , добавлен 06.02.2013

    Рассмотрение особенностей среды программирования Delphi, анализ клиент-серверной версии. Знакомство с библиотекой визуальных компонентов. Основные функции интеллектуального редактора. Характеристика требований к базам данных. Функции программы "Магистр".

    дипломная работа , добавлен 10.03.2013

    Характеристика системы программирования. Главные составные части Delphi. Интерфейс программного приложения. Результаты работы программы. Руководство системного программиста и оператора. Язык программирования Delphi, среда компилятора Borland 7.0.

    курсовая работа , добавлен 29.05.2013

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

    курсовая работа , добавлен 21.09.2010

    Общая характеристика системы программирования Delphi, а также принципы создания ее компонентов. Описание значений файлов приложения, созданного с помощью Delphi. Структура и свойства библиотеки визуальных компонентов (Visual Component Library или VCL).

    отчет по практике , добавлен 07.12.2010

    Проектирование и создание пользовательского интерфейса и визуального программирования в среде Delphi. Система управления базой данных. Локальные и глобальное пользовательские представления. Анализ предметной области. Назначение форм и компонентов.

    курсовая работа , добавлен 07.03.2014

    История интегрированной среды разработки, версии Delphi. Организация библиотеки компонентов. Страница Additional, ряд часто используемых компонентов общего назначения. Исполняемый файл программы "Архиватор текстовых файлов", интерфейс приложения.

    курсовая работа , добавлен 16.05.2017

    Разработка программных продуктов на языке программирования Borland Delphi. Применяемые таблицы и связи между ними. Пользовательский интерфейс работы с базой данных. Алгоритм работы программы "Футбольные команды и игроки". Защита от ввода неверных данных.

В этом уроке я покажу вам, как можно создавать различные сетевые приложения на 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 адресов и работоспособность сетевого соединения.