Написание клиента и сервера при помощи компонентов Indy TIdTCPServer и TIdTCPClient на Delphi
Всё сказанное здесь справедливо для версии 7 борландовского Delphi и компонентов Indy версии 9.00.10.
Для создания клиент-серверного звена при помощи компонентов Indy нам понадобятся компоненты TIdTCPServer (находится на странице «Indy Servers») и TIdTCPClient на странице «Indy Clients». Таким образом, у нас получится две программы: приложение-клиент, построенное на базе компонента TIdTCPClient и приложение-сервер на базе компонента TIdTCPServer.
Теория взаимодействия клиента и сервера простая: сервер открывает ТСР-соединение (или сокет) на определенном ТСР-порту и «слушает» его (ожидает запрос на соединение от клиента). Клиент выполняет свою задачу тем, что соединяется к серверу. После установления соединения клиент и сервер могут взаимодействовать между собой, обмениваясь данными(файлами, текстовыми сообщениями и т.д.).
Настройка
Ниже указаны сервер и клиент со значениями их свойств, необходимых для успешного соединения между собой.
Со стороны сервера необходимо создать объект типа TidSocketHandle и настроить его свойства IP и Port, т.е. задать IP-адрес клиента и порт, на котором сервер будет его «ждать». Со стороны клиента нужно настроить также два свойства: порт и IP адрес сервера – Host и IP. Обычно сервер и клиент тестируются на одном компьютере, поэтому в примере указан локальный IP-адрес компьютера: ‘127.0.0.1', значение номера порта выбрано произвольно и задано заведомо большое значение для того чтобы избежать риска дублирования уже использующихся портов другими службами.
Соединение
Теперь, когда клиент и сервер готовы для предстоящей серьёзной работы, опишем их поведение до и после соединения.
Запуск сервера осуществляется установкой свойства Active в True:
TCPServer1.Active:= True;
После запуска сервер «слушает» назначенный порт, т.е. «ждёт» соединения от клиента по заданному порту и IP-адресу. После установления соединения с клиентом, сервером генерируются события OnConnect и OnExecute. Сервер обслуживает клиента отдельным от основного приложения потоком, который описывается в методе IdTCPServer1Execute :
procedure TForm1.TCPServer1Execute(AThread: TIdPeerThread);
Этот метод вызывается событием OnExecute сервера и именно ему передается аргумент потока AThread. Другими словами, при подключении каждого клиента создаётся отдельный поток, который и обслуживает каждого клиента в отдельности. В дальнейшем работа сервера с клиентом сводится к использованию набора методов и свойств потока AThread.
Более активную роль играет клиент. Для соединения с сервером клиент использует метод TCPClient1.Connect, для разрыва соединения применяется метод TCPClient1.Disconnect; После установления соединения с сервером клиентом генерируется событие OnConnected, при разрыве - OnDisconnected.
Отправка сообщения от сервера к клиенту
Отправка сообщения производится сервером в обслуживающем клиента потоке:
procedure TForm1.TCPServer1Execute(AThread: TIdPeerThread);
begin
AThread.Connection.Writeln('Это сообщение сервера');
end;
на стороне клиента принять это сообщение можно так:
procedure TForm1.TCPClient1Connected(Sender: TObject);
begin
ShowMessage(TCPClient1.Readln);
end;
т.е. запись сообщения в поток и считывание его происходит методами, схожими при работе с консолью или текстовыми файлами: Write, WriteLn, Read, ReadLn .
Отправка файла от сервера к клиенту
Для отправки файла на стороне сервера создаём следующий код:
procedure TForm1.TCPServer1Execute(AThread: TIdPeerThread);
var
MemStream:TMemoryStream;
FileName:string ;
SizeFile:integer;
begin
MemStream:=TMemoryStream.Create; // создаём поток памяти
MemStream.LoadFromFile('c:\example.txt'); // загружаем в поток памяти файл
SizeFile:=MemStream.Size; // определяем размер файла
AThread.Connection.Writeln(FileName); // отсылаем название файла
AThread.Connection.Writeln(SizeFile); // отсылаем размер файла AThread.Connection.OpenWriteBuffer; // открываем буфер записи
AThread.Connection.WriteStream(MemStream); // посылаем файл AThread.Connection.CloseWriteBuffer; // закрываем буфер записи
MemStream.Free; // уничтожаем поток памяти
end;
на стороне сервера сначала создаём поток памяти, загружаем в него файл и отсылаем поток клиенту. Теперь осталось принять файл на стороне клиента:
procedure TForm1.TCPClient1Connected(Sender: TObject);
var
s:string;
FileName,SizeFile: string;
MemStr:TMemoryStream;
begin
while TCPClient1.Connected do
begin
FileName:=TCPClient1.Readln;
s:=TCPClient1.Readln;
SizeFile:=StrToInt(s);
MemStr:=TMemoryStream.Create;
TCPClient1.ReadStream(MemStr,SizeFile,False);
MemStr.SaveToFile('d:\'+FileName);
MemStr.Free;
end;
end;
Теперь поподробнее распишем методы.
Поток данных отправляется методом
procedure WriteStream(AStream: TStream; const AAll: Boolean = True; const AWriteByteCount: Boolean = False; const ASize: Integer = 0); virtual;
с параметрами:
AStream : TStream -
собственно поток данных, в нашем случае это поток памяти
const AAll:Boolean=True -
константа булева типа, показывающая, производить ли запись с начальной позиции потока Astream или нет, по умолчанию имеет значение True , т.е. запись производится с начальной позиции потока.
const AWriteByteCount: Boolean = False -
константа булева типа, означающая, отсылать число битов, записанных в поток на приёмную сторону или нет, по умолчанию имеет значение False, т.е. не надо. Используется в тех случаях, когда поток отправляется не полностью, а с какой-то позиции. В этом случае AAll:=False, и для отправки количества отсылаемых бит применяется метод WriteInteger или любой другой.
В нашем случае мы применили метод как
AThread.Connection.WriteStream(MemStream); // посылаем файл
Т.е. поток оправляем с начальной позиции и полностью.
Приём файла делаем вызовом метода
procedure ReadStream (AStream : TStream; AByteCount:LongInt=-1; const AReadUntilDisconnect: boolean = false);
где:
AStream:TStream - как вы уже догадались поток приёма информации,
AByteCount: LongInt = -1 - число байтов, которое нужно принять.
const AReadUntilDisconnect: boolean = false - логическая константа, показывающая, принимать поток данных до разрыва соединения или нет. По умолчанию имеет значение False и метод работает до конца приёма потока из стека. Если установить значение в True, то приём данных методом будет осуществлять до тех пор, пока свойство Connected будет не равно False, т.е. до разрыва соединения.
В нашем примере мы применили метод как
TCPClient1.ReadStream(MemStr,SizeFile,False);
т.е. читаем поток в поток памяти MemStr, размер принимаемого потока данных SizeFile, который мы предварительно получаем с сервера.
Таким образом, сказанного здесь хватит для написания простого клиента и сервера для приёма и отправки файлов и сообщений.
Рекомендуемая литература и источники:
«Indy in Depth» Chad Z. Hower (Kudzu) перевод Анатолия Подгорецкого
М. Кэнту.«Delphi7.Дляпрофессионалов».Глава 19. Интернет-программирование: сокеты и Indy
|