(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Создание клиент-сервера в Delphi

Источник: pblog

В этой статье хочу поделиться опытом в создании клиент-сервера, который может быть использован, как для реализации сетевого чата, так и для применения в играх. Основой служат два компонента из стандартного пакета Delphi, это ServerSocket и ClientSocket. Они не всегда могут быть отображены в палитре Internet, и их нужно загрузить следующим образом:

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

procedure TForm1.FormCreate(Sender: TObject);
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
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):

procedure TForm1.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo клиентское сообщение
ChatMemo.Lines.Add(Socket.ReceiveText());
end;

Самое главное - отправка сообщений. У нас она осуществляется нажатием клавиши "Отправить" (SendBtn), но необходима проверка режима программы сервер или клиент. Напишем ее процедуру (OnClick):

procedure TForm1.SendBtnClick(Sender: TObject);
begin
// проверка, в каком режиме находится программа
If ServerSocket.Active=True then
// отправляем сообщение с сервера (он под номером 0, поскольку один)
ServerSocket.Socket.Connections[0].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):

procedure TForm1.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
// добавим в ChatMemo пришедшее сообщение
ChatMemo.Lines.Add(Socket.ReceiveText());
end;

Дальше все просто, обычное добавление в ChatMemo определенного сообщения:

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[0..3] 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[0]:=ShapeCBox.ItemIndex;
Buf[1]:=ColorCBox.ItemIndex;
Buf[2]:=StrToInt(Value1Edit.Text);
Buf[3]:=StrToInt(Value2Edit.Text);
// проверяем режим программы
If ServerSocket.Active=True then
// отправим буфер с сервера (длина известна - 4)
ServerSocket.Socket.Connections[0].SendBuf(Buf,4)
else
// отправим буфер с клиента
ClientSocket.Socket.SendBuf(Buf,4);
// добавим в ChatMemo сообщение о передачи данных
ChatMemo.Lines.Add("['+TimeToStr(Time)+'] Данные переданы.");
// применим изменения к своему Shape
Shape.Height:=Buf[2];
Shape.Width:=Buf[3];

If Buf[0]>0 then Shape.Shape:=stCircle {круг}
else Shape.Shape:=stRectangle; {прямоуголник}
// выбор цвета по таблице истинности
Case Buf[1] of
0: Shape.Brush.Color:=clRed;
1: Shape.Brush.Color:=clGreen;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf[0];
ColorCBox.ItemIndex:=Buf[1];
Value1Edit.Text:=IntToStr(Buf[2]);
Value2Edit.Text:=IntToStr(Buf[3]);
end;

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

procedure TForm1.ServerSocketClientRead(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[2];
Shape.Width:=Buf[3];
If Buf[0]>0 then Shape.Shape:=stCircle {круг}
else Shape.Shape:=stRectangle; {прямоуголник}
// выбор цвета по таблице истинности
Case Buf[1] of
0: Shape.Brush.Color:=clRed;
1: Shape.Brush.Color:=clGreen;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf[0];
ColorCBox.ItemIndex:=Buf[1];
Value1Edit.Text:=IntToStr(Buf[2]);
Value2Edit.Text:=IntToStr(Buf[3]);
// добавим в ChatMemo сообщение о приходе данных
ChatMemo.Lines.Add("['+TimeToStr(Time)+'] Пришли данные.");
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[2];
Shape.Width:=Buf[3];
If Buf[0]>0 then Shape.Shape:=stCircle {круг}
else Shape.Shape:=stRectangle; {прямоуголник}
// выбор цвета по таблице истинности
Case Buf[1] of
0: Shape.Brush.Color:=clRed;
1: Shape.Brush.Color:=clGreen;
2: Shape.Brush.Color:=clBlue;
end;
// изменить данные в полях
ShapeCBox.ItemIndex:=Buf[0];
ColorCBox.ItemIndex:=Buf[1];
Value1Edit.Text:=IntToStr(Buf[2]);
Value2Edit.Text:=IntToStr(Buf[3]);
// добавим в ChatMemo сообщение о приходе данных
ChatMemo.Lines.Add("['+TimeToStr(Time)+'] Пришли данные.");
end;

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

For i:=1 to Length(TextEdit.Text) do
Buf:=Copy(TextEdit.Text,i,1);

Очевидно, что массив станет на одну ячейку больше и изменит свой тип на символьный или строковый. Buf[0] при этом будет служить меткой, чем является пришедший буфер: сообщением или данными. В процедурах получения сообщений нужно сделать условие примерно так:

len:=Socket.ReceiveLength;
Socket.ReceiveBuf(Buf,len);
If Buf[0]="t" then
Begin
… делать операцию по соединению в строку (через цикл)
end;
If Buf[0]="c" then
Begin
… делать операцию по изменению параметров Shape
end;

Вот и все, чем я хотел поделиться, не очень функционально, но довольно просто.

Ссылки по теме

Файлы для загрузки


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 30.07.2009 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Delphi Professional Named User
Enterprise Connectors (1 Year term)
Business Studio 4.2 Enterprise. Конкурентная лицензия + Business Studio Portal 4.2. Пользовательская именная лицензия.
SmartBear AQtime Pro - Node-Locked License (Includes 1 Year Maintenance)
Oracle Database Personal Edition Named User Plus License
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Вопросы и ответы по MS SQL Server
Программирование на Visual Basic/Visual Studio и ASP/ASP.NET
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100