СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты...

13
1 СОКЕТЫ В DELPHI 7. СОЗДАНИЕ МНОГОПОЛЬЗОВАТЕЛЬСКОГО ЧАТА Составитель: Коробецкая А.А. Общее представление о сокетах Сокет (socket разъем, розетка) это программная структура, предназначенная для организации соединения на транспортном уровне модели OSI. Каждый сокет ассоциируется с определенным портом (port) уникальным номером от 1 до 65535 (=2 16 –1), который позволяет определить сокет и программу, его использующую. Проводя аналогию, порт можно сравнить с номером квартиры. Сама квартира – это программа. А сокет – это дверь, через которую мы можем выходить во внешний мир (сеть) или впускать кого-то. Если у нашей квартиры нет двери, то вы никуда не выйдете. Кроме того, дверь может быть открыта или заперта. Продолжая аналогию, улица и номер дома – это IP-адрес (адрес сети + адрес хоста), а дом – это хост (компьютер), в котором есть много программ (квартир). Чтобы попасть в какую-то квартиру, вы сначала находите дом с нужным адресом, потом в этом доме квартиру по номеру и стучитесь в дверь. Только после того как вам открыли, вы можете поговорить с обитателями квартиры. Эта аналогия является достаточно грубой. Например, в отличие от номера квартиры номер порта можно спокойно поменять. Достаточно закрыть дверь. Дверь можно спокойно выломать и перенести в другую квартиру. Да и сами квартиры (программы) в доме то появляются, то исчезают. А еще сокеты организованы таким образом, что вам придется завести отдельную дверь для входа и отдельную дверь для выхода. Каждая со своим номером. Здесь мы подошли к важной особенности сокетов – они бывают серверными и клиентскими. Клиентский сокет отправляет сообщения от программы серверу. Он всегда первым начинает общение по сети. Сервер это сокет, который ждет входящих сообщений от какого- нибудь клиента (слушает порт). Получив сообщение, сервер может отправить ответ клиенту, но он никогда не сможет начать разговор первым. Клиент не может подключиться к клиенту, сервер не может подключиться вообще ни к кому.

Transcript of СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты...

Page 1: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

1

СОКЕТЫ В DELPHI 7.

СОЗДАНИЕ МНОГОПОЛЬЗОВАТЕЛЬСКОГО ЧАТА

Составитель: Коробецкая А.А.

Общее представление о сокетах

Сокет (socket – разъем, розетка) – это программная структура,

предназначенная для организации соединения на транспортном уровне

модели OSI.

Каждый сокет ассоциируется с определенным портом (port) –

уникальным номером от 1 до 65535 (=216

–1), который позволяет определить

сокет и программу, его использующую.

Проводя аналогию, порт можно сравнить с номером квартиры. Сама

квартира – это программа. А сокет – это дверь, через которую мы можем

выходить во внешний мир (сеть) или впускать кого-то. Если у нашей

квартиры нет двери, то вы никуда не выйдете. Кроме того, дверь может быть

открыта или заперта.

Продолжая аналогию, улица и номер дома – это IP-адрес (адрес сети +

адрес хоста), а дом – это хост (компьютер), в котором есть много программ

(квартир). Чтобы попасть в какую-то квартиру, вы сначала находите дом с

нужным адресом, потом в этом доме квартиру по номеру и стучитесь в дверь.

Только после того как вам открыли, вы можете поговорить с обитателями

квартиры.

Эта аналогия является достаточно грубой. Например, в отличие от

номера квартиры номер порта можно спокойно поменять. Достаточно

закрыть дверь. Дверь можно спокойно выломать и перенести в другую

квартиру. Да и сами квартиры (программы) в доме то появляются, то

исчезают.

А еще сокеты организованы таким образом, что вам придется завести

отдельную дверь для входа и отдельную дверь для выхода. Каждая со своим

номером.

Здесь мы подошли к важной особенности сокетов – они бывают

серверными и клиентскими.

Клиентский сокет отправляет сообщения от программы серверу. Он

всегда первым начинает общение по сети.

Сервер – это сокет, который ждет входящих сообщений от какого-

нибудь клиента (слушает порт). Получив сообщение, сервер может отправить

ответ клиенту, но он никогда не сможет начать разговор первым.

Клиент не может подключиться к клиенту, сервер не может

подключиться вообще ни к кому.

Page 2: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

2

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

получать сообщения, то потребуется два отдельных сокета (клиент и сервер).

Поскольку сокеты относятся к транспортному уровню, они могут

использовать UDP или TCP. Мы будем рассматривать TCP-сокеты.

Рассмотрим процесс подключения через TCP-сокеты.

Клиент Сервер

1. Указать адрес и порт сервера.

2. Подключиться к серверу. Если

надо, пройти аутентификацию

(логин-проль).

3. Отправить или запросить данные.

4. Получить запрошенные данные.

5. После того, как все отправлено и

получено, завершить соединение.

1. Слушает порт.

2. Получает от клиента запрос на

подключение.

3. Подсоединяет клиента.

4. Получает от клиента данные или

запросы.

5. Отправляет клиенту нужные

данные.

6. Завершает соединение.

Сокеты в Delphi 7

В Delphi сокеты можно использовать с помощью стандартных

компонентов или через системные API-функции.

Мы будем использовать компоненты TClientSocket и TServerSocket.

По умолчанию в версиях выше Delphi 6 они отключены. Необходимо в меню

выбрать Component→Install Packages... В появившемся коне нажать кнопку

Add... и в папке, куда установлен Delphi, в подпапке bin найти

dclsockets70.bpl (в версиях Delphi выше 7 число может быть не «70», а

другим).

Component→Install Packages...→Add→$(DELPHI)\bin\dclsockets70.bpl

После этого он отобразится в списке библиотек компонентов, а на

вкладке компонентов Internet появятся TClientSocket и TServerSocket:

Клиент Сервер

Клиент Клиент

Сервер Сервер/Клиент

Page 3: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

3

На этой же вкладке находятся TTCPServer, TTCPClient и TUDPClient.

TTCPServer и TTCPClient могут использоваться также, как TClientSocket и

TServerSocket, а TUDPClient в Delphi 7 содержит ошибку. Если вам

потребуется UDP-соединение, придется воспользоваться сторонними

компонентами (например, Indy) или API-функциями.

Page 4: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

4

Многопользовательский чат

Мы создадим проект MyChat, состоящий из двух приложений –

клиента и сервера. На самом деле, это гораздо проще, чем создавать одно

приложение, являющееся одновременно клиентом и сервером, потому что

четко ясно, кто и за что отвечает.

Сервер будет вести список пользователей, получать от них сообщения

и передавать эти сообщения всем остальным. Клиентам при этом достаточно

знать адрес сервера и отправлять сообщения ему, и не нужны адреса всех

пользователей, участвующих в беседе.

Заметим, что по тому же принципу обычно организовывается и

отправка сообщений для приватной беседы (один на один), но тогда сервер

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

Дополнительные задания

Мы реализовали лишь минимальный набор функций. В качестве

дополнительного задания, попробуйте реализовать следующее. Приводятся

лишь краткие указания, как можно выполнить эти задания. Подробности

можно легко найти в Интернете.

1. Сохранение последнего адреса и порта сервера в файл и чтение их

из файла при запуске клиента. Лучше всего использовать Ini-файл

(TIniFile).

2. Проверка сообщений перед отправкой на сервер. Не отправлять

пустые сообщения. В том числе, состоящие из одних пробелов

(используйте функцию Trim).

3. Отправка сообщения по Enter или другой горячей клавише.

Используйте OnKeyPress для mmMessage.

Клиент 5

Сервер Клиент 1

Клиент 4

Клиент 3

Клиент 2

Page 5: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

5

4. Настраиваемая ширина лога и чата на сервере. Добавьте компонент

TSplitter со вкладки Additional.

5. Кнопки «Очистка» для чата и лога на сервере и клиенте.

6. При подключении нового клиента сервер отправляет ему 10

последних сообщений чата, если они есть. Отправляйте по одной 10

последних строк из mmChat.Lines. Проверьте, сколько всего там

строк.

7. При подключении нового клиента, об этом выводится сообщение в

чате.

8. Список клиентов на сервере. Запретить подключение для клиентов с

такими же именами. Список проще всего организовать в виде

TStringList. При подключении клиента, он отправляет серверу

сообщение формата ‘My name is Nickname’. Получив такое

сообщение, сервер не отображает его в чате, а проверяет, нет ли уже

такого имени в списке. Если есть, то сервер отправляет клиенту

сообщение об этом и обрывает соединение. Если нет, то его нужно

добавить. При остановке сервера список очищается.

Внимание!

Реализация основного набора функций – это 5 баллов. Дополнительные

задания 1-7 – по 1 баллу каждое, задание 8 – 2 балла.

Всего за работу: 14 баллов.

Page 6: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

6

Указания к выполнению

В Delphi есть удобный способ для работы с несколькими связанными

приложениями – группы проектов. В более новых версиях оно включено по

умолчанию, а в Delphi 7 его требуется отобразить. Выберите в меню View→

Project Manager. Появится окно вида:

Группа проектов объединяет несколько программ и позволяет быстро

переключаться между ними.

Сохраните свою группу проектов под именем GrpMyChat.grp или

другим, понятным вам (правый клик по ProjectGroup1→Save Project Group

As...). Обратите внимание, что сначала потребуется сохранить форму и

проект (USrv_Main.pas и MyChatServer.dpr).

Перейдем непосредственно к созданию сервера.

Создание приложения-сервера

Разместите на форме сервера следующие компоненты, переименуйте и

задайте их параметры:

форма FMyChatServer:

o Caption := ‘MyChat Server’;

серверный сокет SctServer (TServerSocket, вкладка Internet);

панель pnlTools (TPanel, вкладка Standard) для размещения на

ней кнопок:

o Caption := ‘’;

o BevelOuter := bvNone;

o Align = alTop;

поле mmLog (TMemo, вкладка Standard) для ведения лога

подключений:

o Align = alLeft;

o ReadOnly := True;

o очистить Lines;

поле mmChat (TMemo) для отображения самого чата:

Page 7: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

7

o Align = alClient;

o ReadOnly := True;

o очистить Lines;

Свойство Align позволяет разместить компоненты на форме так, чтобы

они изменяли свой размер вместе с формой.

На панели pnlTools (внутри нее) разместите:

поле для ввода EdLocalPort (TSpinEdit, вкладка Samples):

o MinValue := 1;

o MaxValue := 65535;

кнопка BtnOpenClose (TButton, вкладка Standard):

o Caption := ‘Запустить’;

метки с пояснениями.

Должно получиться что-то вроде следующего:

Прежде, чем перейти к программному коду, коротко опишем

TServerSocket.

Его основным параметром является Port – тот порт, который он

прослушивает. Соединение открывается командой Open и закрывается

командой Close. Узнать, работает ли сервер, можно через свойство Active.

Составной частью TServerSocket является Socket (TServerWinSoket).

Серверный сокет поддерживает не одно, а много подключений для

каждого из клиентов. Все они сосредоточены в списке

Socket.Connections[Index]. Например, SctServer.Socket.Connections[0] – это

подключение первого клиента к нашему серверу.

SctServer.Socket.Connections[0].SendText(const S: Stirng) //отправить текст S

Начнем писать программный код. Он будет не очень большим.

Кнопка BtnOpenClose будет отвечать за запуск и остановку сервера.

Page 8: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

8

procedure TFMyChatServer.BtnOpenCloseClick(Sender: TObject);

begin

if SctServer.Active then // если сервер уже запущен

begin

SctServer.Close; //остановить его

BtnOpenClose.Caption := 'Запустить'; //переименовать кнопку

mmLog.Lines.Add(TimeToStr(Time) + ' Сервер остановлен'); //лог

end

else // иначе - сервер еще не работает

begin

SctServer.Port := EdLocalPort.Value; //вписать порт

SctServer.Open; //запустить

BtnOpenClose.Caption := 'Остановить'; //переименовать кнопку

mmLog.Lines.Add(TimeToStr(Time) + ' Сервер '

+ SctServer.Socket.LocalHost + '['

+ SctServer.Socket.LocalAddress + ':'

+ IntToStr(SctServer.Socket.LocalPort) +'] запущен'); //лог

end;

end;

Когда к серверу подключаются клиенты, срабатывает событие

OnClientConnect, когда отключаются – OnClientDisconnect. При этом

необходимо добавить запись в лог. В чате данные события отображать не

будем.

procedure TFMyChatServer.SctServerClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

// Socket - это индивидуальное подключение для нового пользователя

begin

//лог

mmLog.Lines.Add('[' + TimeToStr(Time) + '] Подключился '

+ Socket.RemoteHost + ':' + IntToStr(Socket.RemotePort));

end;

procedure TFMyChatServer.SctServerClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

// Socket - это разрываемое индивидуальное подключение

begin

//лог

mmLog.Lines.Add('[' + TimeToStr(Time) + '] Отключился '

+ Socket.RemoteHost + ':' + IntToStr(Socket.RemotePort));

end;

Ну и наконец, собственно получение и отправка сообщений. Как

только сервер получает информацию от пользователя, срабатывает

OnClientRead. Напишем в его обработчике отправку полученного

сообщения всем, кроме того, от кого оно пришло:

procedure TFMyChatServer.SctServerClientRead(Sender: TObject;

Socket: TCustomWinSocket);

// Socket - индивидуальное подключение клиента, от которого пришло сообщение

var

Msg: String; //полученное сообщение

i: Integer; //счетчик для перебора всех остальных пользователей

begin

Msg := Socket.ReceiveText; //считываем сообщение

mmChat.Lines.Add(Msg); //добавляем в чат

//рассылаем остальным пользователям

for i := 0 to SctServer.Socket.ActiveConnections - 1 do

if SctServer.Socket.Connections[i] <> Socket then // кроме источника

SctServer.Socket.Connections[i].SendText(Msg);

Page 9: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

9

end;

Теперь можно запустить наш сервер, но пока мы не можем проверить

его работу, поскольку у нас еще нет клиентов.

В данном случае IP-адрес сервера отображается как 0.0.0.0, потому что

он является динамическим. «Анастасия-ББ» – это имя компьютера.

Создание приложения-клиента

Чтобы создать новый проект в той же группе, щелкните по кнопке New

в Менеджере проектов. Создайте новое приложение (Application) и сохраните

файл формы как UClt_Main, файл проекта как MyChatClient.

Клиент должен сначала подключиться к серверу, а уже потом

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

которых поместим в TGroupBox (вкладка Standard): gbConnection (Align :=

alTop; Caption := ‘Подключение’) и gbChat (Align := alClient; Caption :=

‘Чат’).

Добавьте сокет-клиент SctClient (TClientSocket, вкладка Internet).

Внутрь gbConnection поместите:

EdServerHost (TEdit);

EdServerPort (TSpinEdit) MinValue := 1; MaxValue := 65535;;

EdNickName (TEdit);

btnConnect (TButton).

Внутрь gbChat:

mmMessage (TMemo) с Align := alTop;

pnlTools (TPanel) с Align := alTop;

btnSend (TButton) внутрь pnlTools;

mmChat (TMemo) с Align := alClient; ReadOnly := True.

Page 10: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

10

Добавьте метки и настройте созданные элементы, чтобы получилось

нечто подобное:

Чтобы сообщения нельзя было отправлять до подключения к серверу,

установите для btnSend свойство Enabled := False.

Займемся программным кодом.

Кнопка btnConnect будет отвечать за подключение и отключение

сервера.

procedure TFMyChatClient.btnConnectClick(Sender: TObject);

begin

if SctClient.Active then //если клиент уже подключен

SctClient.Close //отключить

else //иначе - клиент еще не подключен

begin

SctClient.Host := EdServerHost.Text;

SctClient.Port := EdServerPort.Value;

SctClient.Open; //подключить

end;

end;

При подключении нужно отобразить чат и запретить редактировать

настройки сервера и имя пользователя. При отключении – наоборот. Также

не мешает отобразить это в окне чата.

После успешного подключения у TClientServer срабатывает событие

OnConnect, после отключения OnDisconnect. Внесем в них

соответствующий код.

procedure TFMyChatClient.SctClientConnect(Sender: TObject;

Socket: TCustomWinSocket);

// Socket - подключение к серверу

begin

//во время подключения нельзя менять настройки

EdServerHost.Enabled := False;

Page 11: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

11

EdServerPort.Enabled := False;

EdNickName.Enabled := False;

btnConnect.Caption := 'Отключиться';

// в чате

btnSend.Enabled := True;

mmChat.Lines.Add('------------------------');

mmChat.Lines.Add(TimeToStr(Time) + ' Подключен сервер '

+ SctClient.Socket.LocalHost + ' ['

+ SctClient.Socket.LocalAddress + ':'

+ IntToStr(SctClient.Socket.LocalPort) + ']');

mmChat.Lines.Add('');

end;

procedure TFMyChatClient.SctClientDisconnect(Sender: TObject;

Socket: TCustomWinSocket);

begin

//разрешаем изменить хост сервера, порт и имя пользователя

EdServerHost.Enabled := True;

EdServerPort.Enabled := True;

EdNickName.Enabled := True;

btnConnect.Caption := 'Подключиться';

// в чате

btnSend.Enabled := False;

mmChat.Lines.Add('');

mmChat.Lines.Add(TimeToStr(Time) + ' Соединение с сервером разорвано');

end;

Однако соединение может закончиться и неудачей, если мы указали

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

в этой ситуации пользователю будет выдано системное сообщение с кодом

ошибки. Возьмем отлов ошибок подключения в свои руки. Для этого служит

событие OnError сокета SctClient:

procedure TFMyChatClient.SctClientError(Sender: TObject;

Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;

var ErrorCode: Integer);

begin

//как при отключении

EdServerHost.Enabled := True;

EdServerPort.Enabled := True;

EdNickName.Enabled := True;

btnConnect.Caption := 'Подключиться';

// в чате

btnSend.Enabled := False;

mmChat.Lines.Add('');

mmChat.Lines.Add(TimeToStr(Time) + ' Ошибка соединения с сервером');

end;

Отправка сообщения по кнопке btnSend:

procedure TFMyChatClient.btnSendClick(Sender: TObject);

var Msg: String; //сообщение

begin

Msg := TimeToStr(Time) + ' ' + EdNickName.Text + ': '

+ mmMessage.Text; //составляем сообщение

mmChat.Lines.Add(Msg); //в чат

SctClient.Socket.SendText(Msg); //отправляем на сервер

mmMessage.Lines.Clear;//очистка сообщения

end;

Получение сообщения от сервера по событию OnRead сокета SctClient:

procedure TFMyChatClient.SctClientRead(Sender: TObject;

Socket: TCustomWinSocket);

Page 12: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

12

// Socket - подключение к серверу

var Msg: String; //сообщение

begin

Msg := SctClient.Socket.ReceiveText; //получаем сообщение

mmChat.Lines.Add(Msg); //в чат

end;

Этого достаточно для базовой работы чата. Протестируем его.

Запустите сервер и два клиента. Спровоцируйте ошибку подключения к

серверу. Затем подключитесь без ошибок. Обменяйтесь несколькими

сообщениями и отключите соединения в следующем порядке: первый

клиент, сервер, второй клиент.

Сервер:

Первый клиент:

Page 13: СОКЕТЫ В DELPHI 7. СОЗДАНИЕ … · Сокеты в Delphi 7 В Delphi сокеты можно использовать с помощью стандартных компонентов

13

Второй клиент:

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