» Создание клиент-сервера Borland Delphi. Программирование сетей. . Блог программистов


Блог программистов






20072 Окт

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

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

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

Исходник

Комментарии

  1. 29 сентября, 2014 | 05:48

    outlet woolrich padova…

    Troppi schedule di allenamento sono proprio questo: Program. Si solleva pesi, fare un po ‘cardio e magari aggiungere in yoga o stretching. Per alcuni, questo regolare, approccio segmentato funziona. Per il resto di noi: boor ring.Here la buona notizia…

  2. 29 сентября, 2014 | 05:48

    woolrich outlet bologna online…

    four Il tuo nome Definisce tuo percorso…

  3. 29 сентября, 2014 | 05:48

    woolrich modelli…

    Come l’articolo dal ragazzo Microsoft prova, gli sviluppatori sanno che stanno utilizzando questi oggetti come pellet in una scatola di Skinner. A quel punto 猫 tutta una questione….

  4. 29 сентября, 2014 | 05:49

    woolrich bimbo…

    Io non so di questo, pXr14. Che cosa succede se ci beccano?…

  5. 29 сентября, 2014 | 05:49

    woolrich america…

    Spoiler alert: Non stai trovando in un negozio….

  6. 29 сентября, 2014 | 05:49

    ebay woolrich…

    Ecco come hanno capito la correlazione: Whiteys sono stati mostrati forty colpi alla testa degli uomini neri, donne bianche e uomini bianchi che erano attuali o ex amministratori delegati delle aziende Fortune 500. Poi i soggetti 猫 stato chiesto di g…

  7. 29 сентября, 2014 | 05:49

    woolrich piumini…

    Ho custoditi un ramo della Federal Reserve Bank, un obiettivo popolare in film come Die Very difficult con una vendetta, e vi posso dire con un buon grado di certezza che Ray Bannington il Terzo non avrebbe a che spiaggia di Tahiti. Non avrebbe nemmeno…

  8. 29 сентября, 2014 | 05:50

    outlet woolrich cadriano orari…

    Sdraiatevi sulla schiena, in possesso di un manico di scopa, o qualcos’altro che 猫 dritto, solido e leggero, direttamente su sopra il mento. I tuoi piedi sono fuori dal pavimento con le ginocchia piegate a circa un angolo di 90 gradi. Arrotolare i f…

  9. 29 сентября, 2014 | 05:50

    store house woolrich…

    Non avevi thought che la vita avesse una grafica migliore disponibile. Hai giocato il mondo a sixteen bit per tutto questo tempo, mentre tutti gli altri era in esecuzione realt脿 quad core collegato a uno schermo HD. L’unico lato negativo di questa e…

  10. 29 сентября, 2014 | 05:50

    outlet parka woolrich…

    Come tutti gli studenti universitari, major filologia germanica Kari Ellen Gade e Jana Schulman erano alla ricerca di un buon tempo durante il loro tempo libero; a differenza di tutti gli studenti universitari, hanno pensato intaglio invocazioni norveg…

  11. 29 сентября, 2014 | 05:50

    catalogo woolrich…

    Ma non ho visto perch茅 ero fuori. E quando sei fuori, il lavoro rimane al lavoro. Quella stessa concept 猫 la differenza tra una carriera rispetto a solo lavorando un lavoro per pagare le bollette. Non so esattamente quanto su esso 猫 situato, ma u…

  12. 29 сентября, 2014 | 05:51

    woolrich beige…

    Angel Herrero de Frutos / iStock / Getty Photographs…

  13. 29 сентября, 2014 | 05:51

    woolrich milano…

    Ci va il vostro colpo a Harvard. E non ci va di Yale. Volete sparare per Eastern Illinois University?…

  14. 29 сентября, 2014 | 05:51

    woolrich online shop…

    E una volta che questi non sono pi霉 impegnativi, avviare T flessioni rullo, come quelli presenti nella sezione Risorse. Flessioni T rotolo coprono la plancia frontale, plancia laterale e manopola tutto in un esercizio. Inoltre costruiscono controllo …

  15. 29 сентября, 2014 | 05:52

    woolrich uomo 2013…

    4. Movie ottenere le parti di Scary WrongRemember quella scena in Requiem to get a Dream, quando il braccio di Jared Leto viene infettato e il suo amico si comporta come se fosse la cosa pi霉 pazza che abbia mai visto? Quella scena 猫 ridicolo, non p…

  16. 29 сентября, 2014 | 05:52

    woolrich torino…

    5. I clienti non apprezzano Good Issues…

  17. 29 сентября, 2014 | 15:58

    Chanel Embreagem Sacos 35488 Vermelho Grained Couro…

    Juiz Sherry Stephens j谩 concedido outro continuidade desde Arias que est谩 tentando agir como sua pr贸pria assessoria jur铆dica est谩 tendo dificuldade em alinhar suas testemunhas e recebendo todo o seu movimento est谩 pronto para o tribunal. N

  18. 29 сентября, 2014 | 17:42

    latin nobis…

    Quanti soldi Techs chirurgici Fai in North Carolina?…

  19. 29 сентября, 2014 | 18:37

    miserere nobis translation…

    Inscatolamento di frutta, verdura e altri alimenti consente di memorizzare la vostra bont脿 giardino per un anno o pi霉 senza refrigerazione. Barattoli….