Перевірка з'єднання сокетів у делфі. Delphi. Програмування сокетів у Дельфі. Посилання та прийом складних даних

Сокети (від socket (англ.) – роз'єм, гніздо) – це програмний інтерфейс, Забезпечує обмін інформацією між процесами.

Однією з основних переваг сокетного обміну інформацією мережі можна назвати його гнучкість. Головний принцип роботи із сокетами полягає у надсиланні послідовності байт іншому комп'ютеру, це може бути просте текстове повідомлення або файл.

Важливо розрізняти два типи сокетів: клієнтські сокети , і серверні сокети .

Для роботи з «клієнтським» типом сокетів у Delphi існує компонент TClientSocket, з «серверними» сокетами можна працювати за допомогою компонента TServerSocket.

Встановлення компонентів

Найчастіше компоненти TServerSocket та TClientSocket не входять до стандартного пакета установки Delphi, але їх можна встановити додатково.

Зайдіть на вкладку компонентів «Internet», і перевірте, чи є там компоненти TServerSocket і TClientSocket, якщо ні, то встановіть їх. Зайдіть в меню "Component/Install Packages", потім натисніть кнопку "Add". У діалоговому вікні, що відкрилося, потрібно знайти файл "dclsocketsXX.bpl" (він лежить в папці bin, яка знаходиться в папці з Delphi), де XX - це числовий номер версії вашого Delphi. Знайдіть файл, натисніть "Відкрити", а потім у вікні "Install Packages" натисніть "OK". Тепер, у вкладці "Internet" з'явилися два компоненти - TServerSocket та TClientSocket.

Робота з клієнтськими сокетами (tClientSocket)

1) Визначення властивостей Port та Host.Для успішного з'єднання властивостей Portі Hostкомпоненту TClientSocket необхідно надати деякі значення. В якості Port потрібно вказати номер порту для підключення (1 - 65535, але краще брати з діапазону 1001 - 65535, тому що номери до 1000 можуть бути зайняті системними службами).

Host- хост-ім'я або IP-адреса комп'ютера, з яким потрібно з'єднатися. Наприклад, rus.delphi.com чи 192.128.0.0.

2) Відкриття сокету.Розглянемо сокет як чергу символів, що передаються з одного комп'ютера на інший. Відкрити сокет можна, викликавши метод Open(компонент TClientSocket) або надавши значення Trueвластивості Active. Тут не зайвим буде поставити обробник виключення у разі невдалого з'єднання.

3) Відправлення/прийом даних.

4) Закриття сокету.Після завершення обміну даними потрібно закрити сокет, викликавши метод Closeкомпонента TClientSocketабо надавши значення Falseвластивості Active.

Основні властивості компонента TClientSocket

Показник того, чи відкритий закритий сокет. Відкритий – значення True, закритий – значення False. Доступний для запису.

Хост-ім'я, з яким потрібно з'єднатися

IP-адреса комп'ютера, з якою потрібно з'єднатися. На відміну від Host, тут може бути лише IP. Різниця полягає в тому, що якщо в Host вказано літерне ім'я комп'ютера, IP запитається у DNS

Номер порту комп'ютера, з яким потрібно з'єднатися (1-65535)

ClientType

Містить тип передачі:

ctBlocking- синхронна передача ( OnReadі OnWriteне працюють). Синхронний тип підключення підходить для потокового обміну даними;

ctNonBlocking- асинхронна передача (надсилання/прийом даних може здійснюватися за допомогою подій OnReadі OnWrite)

Основні методи компонента TClientSocket

Відкриває сокет (присвоєння властивості Active значення True)

Закриває сокет (присвоєння властивості Active значення False)

Основні події компонента TClientSocket

OnConnect

Виникає під час встановлення підключення. В обробнику вже можна приступати до авторизації або надсилання/прийому даних

OnConnecting

Також виникає під час підключення. Відрізняється від OnConnect тим, що з'єднання ще не встановлено. Найчастіше використовується, наприклад, щоб оновити статус

OnDisconnect

Подія виникає при закритті сокету вашою програмою, віддаленим комп'ютером або через збій

Подія виникає за помилки. Під час відкриття сокету ця подія не допоможе виловити помилку. Щоб уникнути появи повідомлення від Windows про помилку, краще подбати про внутрішню обробку виключень шляхом розміщення операторів відкриття в блок « try..except »

OnLookup

Подія виникає при спробі отримати IP-адресу від DNS

Подія виникає при надсиланні вам будь-яких даних віддаленим комп'ютером. При виклику OnRead можливе оброблення прийнятих даних

Подія виникає, коли вашій програмі можна писати дані в сокет

У цій статті будуть розглянуті базові властивості та функції компонентів Delphi: TClientSocket і TServerSocket – використовуються для роботи з мережею за протоколом TCP IP.

Увага!Якщо ви використовуєте версію Delphi вище 6.0, попередньо вам необхідно встановити Sockets Components, у всіх версіях Delphi це робиться таким чином:

  • Заходимо до діалогу Install Packages… : (Головне меню) Component -> Install Packages;
  • Клацаємо по кнопці Add… , після чого знаходимо папку Bin вашого Delphi (н-р: C: Program Files Borland Delphi 7, або C: Program Files Embarcadero RAD Studio 7.0 Vin);
  • У знайденій папці Bin вже шукаємо файлик dclsockets [тут циферки].bpl, тиснемо ОК;
  • Радіємо, тому що тепер на вкладці панелі компонентів на вкладці Internet у нас з'явилися два чудові компоненти TServerSocket та TClientSocket.

При розробці будь-яких мережевих програм зазвичай розробку завжди починають із сервера (звичайно якщо у вас є команда, дизайнери інтерфейсів можуть приступати до роботи над клієнтом). Для реалізації сервера, на диво, необхідно використовувати TServerSocket.

Основні властивості:

  • Active- поле типу boolean, при встановленні в true - сервер запускається, можна використовувати як присвоєння конкретних значень, так і викликаючи функції ServerSocket1.Open (Active:=true;) або ServerSocket1.Close (Active:=false).
  • Port– порт на якому прослуховуватиме (прийматиме клієнтів) сервер, будь-яке не зайняте іншими серверами в системі значення в межах діапазону integer.

Основні події:

  • OnListen– викликається при встановленні сервера в режим прослуховування, може використовуватись, коли нам потрібно визначити час реального старту сервера.
  • OnClientRead- Викликається в момент прийняття даних від клієнта.
  • OnClientError
  • OnClientConnect- Викликається під час приєднання нового клієнта до сервера.
  • OnClientDisconnect- Зворотна подія події, OnClientConnect

кожна функція події має атрибут Socket: TCustomWinSocket, в нього передається покажчик на об'єкт сокету з яким ми Наразіпрацюємо, якщо нам потрібно щось відповісти або щось зробити з клієнтом, який викликав подію, нам необхідно використовувати саме цей об'єкт, у всіх інших випадках ми використовуємо ServerSocket1.Socket, аналогічна ситуація з клієнтським компонентом.

Readonly властивості та функції:

  • – повертає кількість активних з'єднань.
  • ServerSocket1.Socket.Connections- масив об'єктів типу TCustomWinSocket, масив всіх об'єктів пов'язаних з клієнтами, рахунок індексів починається з 0, довжина масиву дорівнює ServerSocket1.Socket.ActiveConnections.
  • Функції та властивості, що застосовуються до елементів масиву ServerSocket1.Socket.Connections і атрибуту Socket, що передається, в функцію події сервера:
  • Socket.LocalHost
  • Socket.LocalAdress- Повертає IP сервера.
  • Socket.RemoteHost
  • Socket.RemoteAdress- Повертає IP клієнта.
  • Socket.ReceiveText- Повертає прийняте від клієнта текстове повідомлення, після чого очищає буфер, можна використовувати лише 1 раз, за ​​1 прийом.
  • Socket.SendText(Text)- Надсилає клієнту текстове повідомлення Text типу string.

Для компонента TClientSocket все практично те саме, тільки навпаки + основна візуальна відмінність сервера від клієнта, сервер у системі може бути запущений 1 на 1 значення порту, у кількості клієнтів вас обмежує лише оперативна пам'ять.

Основні властивості:

  • Active- поле типу boolean, при встановленні в true - клієнт намагається з'єднатися з сервером, можна використовувати як присвоєння конкретних значень, так і викликаючи функції ClientSocket1.Open (Active:=true;) або ClientSocket1.Close (Active:=false) .
  • Port– порт яким клієнт зможе приєднатися до серверу, будь-яке значення не більше діапазону integer.
  • Adress– IPv4 адреса сервера типу stringза шаблоном 255.255.255.255, з яким з'єднуватиметься клієнт.

Основні події:

  • OnRead- Викликається в момент прийняття даних від півночі.
  • OnError- Викликається при виникненні помилки в передачі даних.
  • OnConnecting- Викликається під час приєднання клієнта до сервера.
  • OnDisconnect- Зворотна подія події, OnConnecting, що викликається під час від'єднання від сервера клієнта.

Readonly властивості та функції:

  • ClientSocket1.Socket.SendText() string
  • Socket.LocalHost– повертає ім'я клієнта у мережі.
  • Socket.LocalAdress- Повертає IP клієнта.
  • Socket.RemoteHost– повертає ім'я сервера у мережі.
  • Socket.RemoteAdress- Повертає IP сервера.
  • Socket.ReceiveText– повертає прийняте від сервера текстове повідомлення, після чого очищає буфер, можна використовувати лише 1 раз за 1 прийом.
  • Socket.SendText(Text)– надсилає серверу текстове повідомлення Text типу string.

Наданої інформації цілком достатньо, щоб реалізувати невеликий серверний чат, що задовольняє технічним завданням: internet_sockets.doc (Word Doc 97-2003, 26.5 Kb).

Ця стаття написана в Неділю, Жовтень 10th, 2010 at 1:24 в розділі . Ви можете передплатити оновлення коментарів до статті - . Ви можете


Вступ


Ця стаття присвячена створенню додатків архітектури клієнт/сервер у Borland Delphi на основі сокетів ("sockets" - гнізда). На відміну від попередньої статті на тему сокетів, ми розберемо створення серверних додатків.

Слід зазначити, що з співіснування окремих додатків клієнта і сервера необов'язково мати кілька комп'ютерів. Достатньо мати лише один, на якому Ви одночасно запустите і сервер, і клієнт. При цьому потрібно як ім'я комп'ютера, до якого треба підключитися, використовувати хост-ім'я localhostабо IP-адреса - 127.0.0.1 .

Отже, почнемо з теорії. Якщо Ви переконаний практик (і у вічі не можете бачити будь-яких алгоритмів), то Вам слід пропустити цей розділ.

Алгоритм роботи сокетного сервера


Що ж дозволяє робити сокетний сервер? За яким принципом він працює?.. Сервер, заснований на сокетному протоколі, дозволяє обслуговувати відразу безліч клієнтів. Причому обмеження на їх кількість Ви можете вказати самі (або взагалі прибрати це обмеження, як це зроблено за умовчанням). Для кожного підключеного клієнта сервер відкриває окремий сокет, яким Ви можете обмінюватися даними з клієнтом. Також відмінним рішенням є створення кожного підключення окремого процесу (Thread).

Нижче слідує зразкова схема роботи сокетного сервера в Дельфі-додатках:

Розберемо схему докладніше:

  • Визначення св-в Port та ServerType - щоб до сервера могли нормально підключатися клієнти, потрібно, щоб порт, що використовується сервером, точно збігався з портом, що використовується клієнтом (і навпаки). Властивість ServerType визначає тип підключення (докладніше див. нижче);
  • Відкриття сокету - Відкриття сокету та вказаного порту. Тут виконується автоматичний початок очікування приєднання клієнтів ( Listen);
  • Підключення клієнта та обмін даними з ним - тут підключається клієнт та йде обмін даними з ним. Докладніше про цей етап можна дізнатися нижче у цій статті та у статті про сокети (клієнтська частина);
  • Відключення клієнта - Тут клієнт відключається та закривається його сокетне з'єднання з сервером;
  • Закриття сервера та сокету - За командою адміністратора сервер завершує свою роботу, закриваючи всі відкриті сокетні канали та припиняючи очікування на підключення клієнтів.

Слід зазначити, пункти 3-4 повторюються багаторазово, тобто. ці пункти виконуються кожного нового підключення клієнта.

Примітка : Документації по сокетам у Дельфі на даний момент дуже мало, так що якщо Ви хочете максимально глибоко вивчити цю тему, то раджу переглянути літературу та електронну документацію по Unix/Linux-системам - там дужедобре описано теорію роботи із сокетами. Крім того, для цих ОС є безліч прикладів сокетних додатків (щоправда, переважно на C/C++ та Perl).

Короткий опис компоненту TServerSocket


Тут ми познайомимося з основнимивластивостями, методами та подіями компонента TServerSocket.

Властивості

Socket - клас TServerWinSocket, через який Ви маєте доступ до відкритих сокетних каналів. Далі ми розглянемо цю властивість докладніше, т.к. воно, власне, і є одним з головних. Тип: TServerWinSocket ;
ServerType - Тип сервера. Може набувати одне з двох значень: stNonBlocking- Синхронна робота з клієнтськими сокетами. За такого типу сервера Ви можете працювати з клієнтами через події OnClientReadі OnClientWrite. stThreadBlocking- Асинхронний тип. До кожного клієнтського сокетного каналу створюється окремий процес (Thread). Тип: TServerType ;
ThreadCacheSize - кількість клієнтських процесів (Thread), які кешуватимуться сервером. Тут необхідно підбирати середнє значення в залежності від завантаженості сервера. Кешування відбувається для того, щоб не створювати щоразу окремий процес і не вбивати закритий сокет, а залишити їх для подальшого використання. Тип: Integer ;
Active - Показник того, активний в даний момент сервер, чи ні. Тобто, фактично, значення Trueвказує на те, що сервер працює та готовий до прийому клієнтів, а False- сервер вимкнено. Щоб запустити сервер, потрібно просто привласнити цій властивості значення True. Тип: Boolean ;
Port - Номер порту для встановлення з'єднань з клієнтами. Порт у сервера та у клієнтів мають бути однаковими. Рекомендуються значення від 1025 до 65 535, т.к. від 1 до 1024 р. - можуть бути зайняті системою. Тип: Integer ;
Service - рядок, що визначає службу ( ftp, http, pop, і т.д.), порт якої буде використано. Це своєрідний довідник відповідності номерів портів різним стандартним протоколам. Тип: string ;

Open - Запускає сервер. По суті, ця команда ідентична присвоєння значення Trueвластивості Active;
Close - Зупиняє сервер. По суті, ця команда ідентична присвоєння значення Falseвластивості Active.

OnClientConnect - виникає, коли клієнт встановив сокетне з'єднання і чекає на відповідь сервера ( OnAccept);
OnClientDisconnect - З'являється, коли клієнт від'єднався від сокетного каналу;
OnClientError - з'являється, коли поточна операція завершилася невдало, тобто. Виникла помилка;
OnClientRead - виникає, коли клієнт передав серверу будь-які дані. Доступ до цих даних можна отримати через параметр, що передається Socket: TCustomWinSocket;
OnClientWrite - виникає, коли сервер може надсилати дані клієнту за сокетом;
OnGetSocket - в обробнику цієї події можна відредагувати параметр ClientSocket;
OnGetThread - в обробнику цієї події Ви можете визначити унікальний процес (Thread) для кожного окремого клієнтського каналу, надавши параметру SocketThreadпотрібне підзавдання TServerClientThread;
OnThreadStart , OnThreadEnd - виникає, коли підзавдання (процес, Thread) запускається чи зупиняється відповідно;
OnAccept - виникає, коли сервер приймає клієнта чи відмовляє йому з'єднання;
OnListen - виникає, коли сервер переходить у режим очікування приєднання клієнтів.


TServerSocket.Socket (TServerWinSocket)


Отже, як сервер може відсилати дані клієнту? А чи приймати дані? В основному, якщо Ви працюєте через події OnClientReadі OnClientWriteто спілкуватися з клієнтом можна через параметр ClientSocket (TCustomWinSocket). Про роботу з цим класом можна прочитати у статті про клієнтські сокети, т.к. відправка/надсилання даних через цей клас аналогічна - методи (Send/Receive) (Text, Buffer, Stream). Також і під час роботи з TServerSocket.Socket. Проте, т.к. тут ми розглядаємо сервер, то слід виділити деякі корисні властивості та методи:

  • ActiveConnections (Integer) - кількість підключених клієнтів;
  • ActiveThreads (Integer) - кількість працюючих процесів;
  • Connections (array) - масив, що складається з окремих класів TClientWinSocket для кожного підключеного клієнта. Наприклад, така команда:
    ServerSocket1.Socket.Connections.SendText("Hello!");
    надсилає першому підключеному клієнту повідомлення "Hello!". Команди до роботи з елементами цього масиву - також (Send/Receive)(Text,Buffer, Stream);
  • IdleThreads (Integer) - кількість вільних процесів. Такі процеси кешуються сервером (див. ThreadCacheSize);
  • LocalAddress, LocalHost, LocalPort- відповідно - локальна IP-адреса, хост-ім'я, порт;
  • RemoteAddress, RemoteHost, RemotePort- відповідно - віддалена IP-адреса, хост-ім'я, порт;
  • Методи Lockі UnLock- відповідно, блокування та розблокування сокету.

Практика та приклади


А тепер розглянемо вищенаведене на конкретному прикладі. Завантажити вже готові вихідники можна, натиснувши .

Отже, розглянемо дуже хороший приклад роботи з TServerSocket (цей приклад - найбільш наочний посібник вивчення цього компонента). У наведених нижче вихідниках демонструється протоколювання всіх важливих подій сервера плюс можливість приймати і відсилати текстові повідомлення:

приклад 1.Протоколювання та вивчення роботи сервера, посилка/прийом повідомлень через сокети

(... Тут йде заголовок файлу та визначення форми TForm1 та її екземпляра Form1)
(Повний вихідник дивись )
procedure TForm1.Button1Click(Sender: TObject); begin (Визначаємо порт та запускаємо сервер) ServerSocket1.Port: = 1025; (Метод Insert вставляє рядок у масив у вказану позицію) Memo2.Lines.Insert(0,"Server starting"); ServerSocket1.Open; end; procedure TForm1.Button2Click(Sender: TObject); begin (Зупиняємо сервер) ServerSocket1.Active:= False; Memo2.Lines.Insert(0,"Server stopped"); end; procedure TForm1.ServerSocket1Listen(Sender: TObject; Socket: TCustomWinSocket); begin (Тут сервер "прослуховує" сокет на наявність клієнтів) Memo2.Lines.Insert(0,"Listening on port" +IntToStr(ServerSocket1.Port)); end; procedure TForm1.ServerSocket1Accept(Sender: TObject; Socket: TCustomWinSocket); begin (Тут сервер приймає клієнта) Memo2.Lines.Insert(0,"Client connection accepted"); end; procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin (Тут клієнт приєднується) Memo2.Lines.Insert(0,"Client connected"); end; procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin (Тут клієнт від'єднується) Memo2.Lines.Insert(0,"Client disconnected"); end; procedure TForm1.ServerSocket1ClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin (Відбулася помилка – виводимо її код) Memo2.Lines.Insert(0,"Client error. Code = "+IntToStr(ErrorCode)); end; procedure TForm1.ServerSocket1ClientRead(Sender: TObject; Socket: TCustomWinSocket); begin (Від клієнта отримано повідомлення - виводимо його в Memo1) Memo2.Lines.Insert(0,"Message received from client"); Memo1.Lines.Insert(0,"> "+Socket.ReceiveText); end; procedure TForm1.ServerSocket1ClientWrite(Sender: TObject; Socket: TCustomWinSocket); begin (Тепер можна надсилати дані в сокет) Memo2.Lines.Insert(0,"Now can write to socket"); end; procedure TForm1.ServerSocket1GetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); begin Memo2.Lines.Insert(0,"Get socket"); end; procedure TForm1.ServerSocket1GetThread(Sender: TObject; ClientSocket: TServerClientWinSocket; var SocketThread: TServerClientThread); begin Memo2.Lines.Insert(0,"Get Thread"); end; procedure TForm1.ServerSocket1ThreadEnd(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0,"Thread end"); end; procedure TForm1.ServerSocket1ThreadStart(Sender: TObject; Thread: TServerClientThread); begin Memo2.Lines.Insert(0,"Thread start"); end; procedure TForm1.Button3Click(Sender: TObject); var i: Integer; begin (Посилаємо ВСІМ клієнтам повідомлення з Edit1) for i:= 0 до ServerSocket1.Socket.ActiveConnections-1 do begin ServerSocket1.Socket.Connections[i].SendText(Edit1.Text); end; Memo1.Lines.Insert(0,"

Прийоми роботи з TServerSocket (і просто із сокетами)


Зберігання унікальних даних кожного клієнта.


Напевно, якщо Ваш сервер обслуговуватиме безліч клієнтів, то Вам потрібно буде зберігати будь-яку інформацію для кожного клієнта (ім'я та ін.), причому з прив'язкою цієї інформації до сокету даного клієнта. У деяких випадках робити все це вручну (прив'язка до handle сокету, масиви клієнтів і т.д.) не дуже зручно. Тому для кожного сокету існує спеціальна властивість - Data. Насправді, Data - це всього лише покажчик. Тому, записуючи дані клієнта в цю властивість будьте уважні і дотримуйтесь правил роботи з покажчиками (виділення пам'яті, визначення типу тощо)!

Надсилання файлів через сокет.


Тут ми розглянемо посилку файлів через сокет (на прохання JINX-а) :-). Отже, як надіслати файл по сокету? Дуже просто! Достатньо лише відкрити цей файл як файловий потік (TFileStream) та відправити його через сокет (SendStream)! Розглянемо це з прикладу:

Слід зазначити, що метод SendStreamвикористовується як сервером, а й клієнтом ( ClientSocket1.Socket.SendStream(srcfile))

Чому кілька блоків під час передачі можуть об'єднуватися в один


Це теж на прохання JINX-а:-). За це йому велике спасибі! Отже, по-перше, треба помітити, що дані, що посилаються через сокет, можуть не тільки об'єднуватися в один блок, але і роз'єднуватися по кількох блоках. Справа в тому, що сокет – звичайний потік, але на відміну, скажімо, від файлового (TFileStream), він передає дані повільніше (самі розумієте – мережу, обмежений трафік, і т.д.). Саме тому дві команди:
ServerSocket1.Socket.Connections.SendText("Hello, ");
ServerSocket1.Socket.Connections.SendText("world!");
абсолютно ідентичні одній команді:
ServerSocket1.Socket.Connections.SendText("Hello, world!");

І саме тому, якщо Ви відправите через сокет файл, скажімо, в 100 Кб, то тому, кому Ви посилали цей блок, прийде кілька блоків із розмірами, які залежать від трафіку та завантаженості лінії. Причому розміри не обов'язково будуть однаковими. Звідси випливає, що для того, щоб прийняти файл чи будь-які інші дані великого розміру, Вам слід приймати блоки даних, а потім об'єднувати їх в одне ціле (і зберігати, наприклад, файл). Відмінним рішенням цієї завдання є той самий файловий потік - TFileStream (чи потік у пам'яті - TMemoryStream). Приймати частинки даних із сокету можна через подію OnRead (OnClientRead), використовуючи універсальний метод ReceiveBuf. Визначити розмір отриманого блоку можна методом ReceiveLength. Також можна скористатися сокетним потоком (див. статтю про TClientSocket). А ось і невеликий приклад (приблизний):

Як стежити за сокетом


Це питання складне і потребує тривалого розгляду. Поки лише зауважу, що створений програмою сокет Ви можете промоніторити завжди:-). Сокети (як і більшість об'єктів у Windows) мають свій дескриптор (handle), записаний як Handle. Так ось, дізнавшись цей дескриптор, Ви вільно зможете керувати будь-яким сокетом (навіть створеним чужою програмою)! Однак, швидше за все, щоб стежити за чужим сокетом, Вам доведеться використовувати виключно функції WinAPI Sockets.


У цій статті відображено основні прийоми роботи з компонентом TServerSocket у Дельфі та кілька загальних прийомів для обміну даними по сокетам. Якщо у Вас є питання – скидайте їх мені на E-mail: [email protected], а ще краще – пишіть у конференції цього сайту (Delphi. Загальні питання), щоб та інші користувачі змогли побачити Ваше запитання та спробувати на нього відповісти!

Карих Микола ( Nitro). Московська область, м. Жуковський

Ця стаття присвячена створенню програм архітектури клієнт/сервер в Borland Delphi на основі сокетів ("sockets" - гнізда). На відміну від попередньої статті на тему сокетів, ми розберемо створення серверних додатків.
Слід зазначити, що з співіснування окремих додатків клієнта і сервера необов'язково мати кілька комп'ютерів. Достатньо мати лише один, на якому Ви одночасно запустите і сервер, і клієнт. При цьому потрібно як ім'я комп'ютера, до якого треба підключитися, використовувати хост-ім'я localhost або IP-адресу - 127.0.0.1.
Отже, почнемо з теорії. Якщо Ви переконаний практик (і у вічі не можете бачити будь-яких алгоритмів), то Вам слід пропустити цей розділ.
Алгоритм роботи сокетного сервера
Що ж дозволяє робити сокетний сервер? За яким принципом він працює?.. Сервер, заснований на сокетному протоколі, дозволяє обслуговувати відразу безліч клієнтів. Причому обмеження на їх кількість Ви можете вказати самі (або взагалі прибрати це обмеження, як це зроблено за умовчанням). Для кожного підключеного клієнта сервер відкриває окремий сокет, яким Ви можете обмінюватися даними з клієнтом. Також відмінним рішенням є створення кожного підключення окремого процесу (Thread).
Нижче слідує зразкова схема роботи сокетного сервера в Дельфі-додатках:

Розберемо схему докладніше: · Визначення св-в Port і ServerType - щоб до сервера могли нормально підключатися клієнти, потрібно, щоб порт, який використовує сервер точно співпадав з портом, що використовується клієнтом (і навпаки). Властивість ServerType визначає тип підключення (докладніше див. нижче); В· Відкриття сокета - відкриття сокету та зазначеного порту. Тут виконується автоматичний початок очікування приєднання клієнтів (Listen); В· Підключення клієнта та обмін даними з ним - тут підключається клієнт і йде обмін даними з ним. Докладніше про цей етап можна дізнатися нижче у цій статті та у статті про сокети (клієнтська частина); В· відключення клієнта - Тут клієнт відключається і закривається його сокетне з'єднання з сервером; В· Закриття сервера та сокета - За командою адміністратора сервер завершує свою роботу, закриваючи всі відкриті сокетні канали та припиняючи очікування підключень клієнтів.
Слід зазначити, пункти 3-4 повторюються багаторазово, тобто. ці пункти виконуються кожного нового підключення клієнта.
Примітка: Документації про сокети в Дельфі на даний момент дуже мало, так що якщо Ви хочете максимально глибоко вивчити цю тему, то раджу переглянути літературу та електронну документацію по Unix/Linux-системам - там дуже добре описана теорія роботи з сокетами. Крім того, для цих ОС є безліч прикладів сокетних додатків (щоправда, переважно на C/C++ та Perl).
Короткий опис компоненту TServerSocket
Тут ми познайомимося з основними властивостями, методами та подіями компонента
Властивості
Socket – клас TServerWinSocket, через який Ви маєте доступ до відкритих сокетних каналів. Далі ми розглянемо цю властивість докладніше, т.к. воно, власне, і є одним з головних. Тип: TServerWinSocket;
ServerType – тип сервера. Може приймати одне з двох значень: stNonBlocking – синхронна робота з клієнтськими сокетами. За такого типу сервера Ви можете працювати з клієнтами через події OnClientRead та OnClientWrite. stThreadBlocking - асинхронний тип. До кожного клієнтського сокетного каналу створюється окремий процес (Thread). Тип: TServerType;
ThreadCacheSize - кількість клієнтських процесів (Thread), які кешуватимуться сервером. Тут необхідно підбирати середнє значення в залежності від завантаженості сервера. Кешування відбувається для того, щоб не створювати щоразу окремий процес і не вбивати закритий сокет, а залишити їх для подальшого використання. Тип: Integer;
Active - показник того, чи активний в даний момент сервер, чи ні. Тобто фактично значення True вказує на те, що сервер працює і готовий до прийому клієнтів, а False - сервер вимкнений. Щоб запустити сервер, потрібно просто надати цій властивості значення True. Тип: Boolean;
Port – номер порту для встановлення з'єднань з клієнтами. Порт у сервера та у клієнтів мають бути однаковими. Рекомендуються значення від 1025 до 65 535, т.к. від 1 до 1024 р. - можуть бути зайняті системою. Тип: Integer;
Service - рядок, визначальний службу (ftp, http, pop, тощо.), порт якої буде використано. Це своєрідний довідник відповідності номерів портів різним стандартним протоколам. Тип: string;
Методи
Open – Запускає сервер. Власне, ця команда ідентична присвоєння значення True властивості Active;
Close - Зупиняє сервер. По суті, ця команда ідентична надання значення False властивості Active.
Події
OnClientConnect - виникає, коли клієнт встановив сокетне з'єднання і чекає на відповідь сервера (OnAccept);
OnClientDisconnect – виникає, коли клієнт від'єднався від сокетного каналу;
OnClientError - з'являється, коли поточна операція завершилася невдало, тобто. Виникла помилка;
OnClientRead - виникає, коли клієнт передав бервер будь-які дані. Доступ до цих даних можна отримати через параметр, що передається, Socket: TCustomWinSocket;
OnClientWrite - виникає, коли сервер може надсилати дані клієнту за сокетом;
OnGetSocket - в обробнику цієї події можна відредагувати параметр ClientSocket;
OnGetThread - в обробнику цієї події Ви можете визначити унікальний процес (Thread) для кожного окремого клієнтського каналу, надавши параметру SocketThread потрібне підзавдання TServerClientThread;
OnThreadStart, OnThreadEnd - виникає, коли підзавдання (процес, Thread) запускається чи зупиняється, відповідно;
OnAccept - виникає, коли сервер приймає клієнта чи відмовляє йому з'єднанні;
OnListen - виникає, коли сервер перетворюється на режим очікування приєднання клієнтів.
TServerSocket.Socket (TServerWinSocket)
Отже, як сервер може відсилати дані клієнту? А чи приймати дані? В основному, якщо Ви працюєте через події OnClientRead та OnClientWrite, то спілкуватися з клієнтом можна через параметр ClientSocket (TCustomWinSocket). Про роботу з цим класом можна прочитати у статті про клієнтські сокети, т.к. відправка/надсилання даних через цей клас аналогічна - методи (Send/Receive) (Text, Buffer, Stream). Також і під час роботи з TServerSocket.Socket. Проте, т.к. Тут ми розглядаємо сервер, то слід виділити деякі корисні властивості та методи: ActiveConnections (Integer) - кількість підключених клієнтів; · ActiveThreads (Integеr) - кількість працюючих процесів; В·Connections (array) - масив, що складається з окремих класів TClientWinSocket для кожного підключеного клієнта. Наприклад, така команда: ServerSocket1.Socket.Connections.SendText("Hello!"); Відсилає першому підключеному клієнту повідомлення "Hello!". Команди до роботи з елементами цього масиву - також (Send/Receive)(Text,Buffer, Stream); ·IdleThreads (Integer) - кількість вільних процесів. Такі процеси кешуються сервером (див. ThreadCacheSize); · LocalAddress, LocalHost, LocalPort - відповідно - локальна IP-адреса, хост-ім'я, порт; · RemoteAddress, RemoteHost, RemotePort - відповідно - віддалена IP-адреса, хост-ім'я, порт; ·Методи Lock і UnLock - відповідно, блокування та розблокування сокету.
Практика та приклади
А тепер розглянемо наведене вище на конкретному прикладі. Завантажити готові вихідники можна, клацнувши тут.
Отже, розглянемо дуже хороший приклад роботи з TServerSocket (цей приклад - найбільш наочний посібник вивчення цього компонента). У наведених нижче вихідниках демонструється протоколювання всіх важливих подій сервера, плюс можливість приймати та надсилати текстові повідомлення:
Приклад 1. Протоколування та вивчення роботи сервера, надсилання/прийом повідомлень через сокети.

(... Тут йде заголовок файлу та визначення форми TForm1 та її екземпляра Form1)

(Повний вихідник дивись тут)

procedure TForm1.Button1Click (Sender: TObject);

begin

(Визначаємо порт та запускаємо сервер)

ServerSocket1.Port := 1025;

(Метод Insert вставляє рядок у масив у вказану позицію)

Memo2.Lines .Insert (0, "Server starting");

ServerSocket1.Open;

end;

procedure TForm1.Button2Click (Sender: TObject);

begin

(Зупиняємо сервер)

ServerSocket1.Active := False ;

Memo2.Lines .Insert (0, "Server stopped");

end;

procedure TForm1.ServerSocket1Listen (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Тут сервер "прослуховує" сокет на наявність клієнтів)

Memo2.Lines .Insert (0, "Listening on port" + IntToStr (ServerSocket1.Port));

end;

procedure TForm1.ServerSocket1Accept (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Тут сервер приймає клієнта)

Memo2.Lines .Insert (0, "Client connection accepted");

end;

procedure TForm1.ServerSocket1ClientConnect (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Тут клієнт приєднується)

Memo2.Lines .Insert (0, "Client connected");

end;

procedure TForm1.ServerSocket1ClientDisconnect (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Тут клієнт від'єднується)

Memo2.Lines .Insert (0, "Client disconnected");

end;

procedure TForm1.ServerSocket1ClientError (Sender: TObject ;

Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;

var ErrorCode: Integer);

begin

(Відбулася помилка – виводимо її код)

Memo2.Lines .Insert (0 ,"Client error. Code = " +IntToStr (ErrorCode) ) ;

end;

procedure TForm1.ServerSocket1ClientRead (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Від клієнта отримано повідомлення - виводимо його в Memo1)

Memo2.Lines .Insert (0 , "Message received from client") ;

Memo1.Lines .Insert (0 ,"> " +Socket.ReceiveText ) ;

end;

procedure TForm1.ServerSocket1ClientWrite (Sender: TObject ;

Socket: TCustomWinSocket);

begin

(Тепер можна надсилати дані в сокет)

Memo2.Lines .Insert (0 ,"Now can write to socket" ) ;

end;

procedure TForm1.ServerSocket1GetSocket (Sender: TObject ; Socket: Integer ;

var ClientSocket: TServerClientWinSocket) ;

begin

Memo2.Lines .Insert (0, "Get socket");

end;

procedure TForm1.ServerSocket1GetThread (Sender: TObject ;

ClientSocket: TServerClientWinSocket;

var SocketThread: TServerClientThread) ;

begin

Memo2.Lines .Insert (0, "Get Thread");

end;

procedure TForm1.ServerSocket1ThreadEnd (Sender: TObject ;

begin

Memo2.Lines .Insert (0, "Thread end");

end;

procedure TForm1.ServerSocket1ThreadStart (Sender: TObject ;

Thread: TServerClientThread);

begin

Memo2.Lines .Insert (0, "Thread start");

end;

procedure TForm1.Button3Click (Sender: TObject);

var i: Integer;

begin

(Посилаємо ВСІМ клієнтам повідомлення з Edit1)

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

ServerSocket1.Socket .Connections [ i] .SendText (Edit1.Text );

end;

Memo1.Lines .Insert (0 ,"< " +Edit1.Text ) ;

end;

Далі ми розглядатимемо вже не приклади, а прийоми роботи з TServerSocket.
Прийоми роботи з TServerSocket (і просто із сокетами)
Зберігання унікальних даних кожного клієнта.
Напевно, якщо Ваш сервер обслуговуватиме безліч клієнтів, то Вам потрібно буде зберігати будь-яку інформацію для кожного клієнта (ім'я та ін.), причому з прив'язкою цієї інформації до сокету даного клієнта. У деяких випадках робити все це вручну (прив'язка до handle сокету, масиви клієнтів і т.д.) не дуже зручно. Тому для кожного сокету існує спеціальна властивість – Data. Насправді, Data - це всього лише покажчик. Тому, записуючи дані клієнта в цю властивість будьте уважні і дотримуйтесь правил роботи з покажчиками (виділення пам'яті, визначення типу тощо)!
Надсилання файлів через сокет.
Тут ми розглянемо посилку файлів через сокет (на прохання JINX-а) :-). Отже, як надіслати файл по сокету? Дуже просто! Достатньо лише відкрити цей файл як файловий потік (TFileStream) та відправити його через сокет (SendStream)! Розглянемо це з прикладу:

(Посилання файлу через сокет)

procedure SendFileBySocket(filename: string );

var srcfile: TFileStream;

begin

(Відкриваємо файл filename)

Srcfile:= TFileStream.Create (filename, fmOpenRead) ;

(Посилаємо його першому підключеному клієнту)

ServerSocket1.Socket .Connections [0]. SendStream (srcfile);

(Закриваємо файл)

Srcfile.Free;

end;

Слід зазначити, що метод SendStream використовується як сервером, а й клієнтом (ClientSocket1.Socket.SendStream(srcfile))
Чому кілька блоків під час передачі можуть об'єднуватися в один
Це теж на прохання JINX-а:-). За це йому велике спасибі! Отже, по-перше, треба помітити, що дані, що посилаються через сокет, можуть не тільки об'єднуватися в один блок, але і роз'єднуватися по кількох блоках. Справа в тому, що сокет – звичайний потік, але на відміну, скажімо, від файлового (TFileStream), він передає дані повільніше (самі розумієте – мережу, обмежений трафік, і т.д.). Саме тому дві команди:
ServerSocket1.Socket.Connections.SendText("Hello, ");
ServerSocket1.Socket.Connections.SendText("world!");
абсолютно ідентичні одній команді:
ServerSocket1.Socket.Connections.SendText("Hello, world!");
І саме тому, якщо Ви відправите через сокет файл, скажімо, в 100 Кб, то тому, кому Ви посилали цей блок, прийде кілька блоків із розмірами, які залежать від трафіку та завантаженості лінії. Причому розміри не обов'язково будуть однаковими. Звідси випливає, що для того, щоб прийняти файл або будь-які інші дані великого розміру, Вам слід приймати блоки даних, а потім поєднувати їх в одне ціле (і зберігати, наприклад, файл). Відмінним рішенням цієї завдання є той самий файловий потік - TFileStream (чи потік у пам'яті - TMemoryStream). Приймати частинки даних із сокету можна через подію OnRead (OnClientRead), використовуючи універсальний метод ReceiveBuf. Визначити розмір отриманого блоку можна шляхом ReceiveLength. Також можна скористатися сокетним потоком (див. статтю про TClientSocket). А ось і невеликий приклад (приблизний):

(Прийом файлу через сокет)

procedure TForm1.ClientSocket1Read (Sender: TObject ;

Socket: TCustomWinSocket);

var l: Integer;

Buf: PChar;

Src: TFileStream;

begin

(Записуємо в l розмір отриманого блоку)

L:= Socket.ReceiveLength;

(Замовляємо пам'ять для буфера)

GetMem (buf, l+1);

(Записуємо в буфер отриманий блок)

Socket.ReceiveBuf (buf,l);

(Відкриваємо тимчасовий файл для запису)

Src:= TFileStream.Create ("myfile.tmp", fmOpenReadWrite);

(Ставимо позицію в кінець файлу)

Src.Seek (0 ,soFromEnd) ;

(Записуємо буфер у файл)

Src.WriteBuffer (buf,l);

(Закриваємо файл)

Src.Free;

(Звільняємо пам'ять)

FreeMem (buf);

end;

Як стежити за сокетом
Це питання складне і потребує тривалого розгляду. Поки лише зауважу, що створений програмою сокет Ви можете промоніторити завжди:-). Сокети (як і більшість об'єктів у Windows) мають свій дескриптор (handle), записаний як Handle. Так ось, дізнавшись цей дескриптор, Ви вільно зможете керувати будь-яким сокетом (навіть створеним чужою програмою)! Однак, швидше за все, щоб стежити за чужим сокетом, Вам доведеться використовувати виключно функції WinAPI Sockets.
Епілог
У цій статті відображено основні прийоми роботи з компонентом TServerSocket у Дельфі та кілька загальних прийомів для обміну даними по сокетам. Якщо у Вас є питання – скидайте їх мені на E-mail: [email protected], а ще краще – пишіть у конференції цього сайту (Delphi. Загальні питання), щоб та інші користувачі змогли побачити Ваше запитання та спробувати на нього відповісти!
Карих Миколай (Nitro). Московська область, м. Жуковський

Я додав мережеву підтримку. Тобто створив окремий сервер та окремий клієнт. Сенс полягає в тому, що працює сервер програми, користувач запускається клієнт і користувач вводить запит: Москва Тверська 6. Потім сервер обробляє запит, отримує результати пошуку з Яндекс.Карт і відправляє отриману картинку клієнту, потім вже в клієнті в компоненті TMap відображається частина даної карти, яка відповідає запиту користувача. У результаті користувач може масштабувати, зберігати і так далі.

Тому в цій статті я хочу розповісти, як я реалізовував клієнта та сервер. Це я робив за допомогою TClientSocket і TServerSocket, в даній статті ми і розглянемо докладно ті методи, які я використовував у своєму проекті.

Спочатку давайте подивимося, як ці компоненти можна встановити собі в IDE. Якщо Ви використовуєте IDE Delphi 7, то в ній за замовчуванням дані компоненти є, але вони, на жаль, не встановлені, але це не проблема. Нам достатньо відкрити Delphi та встановити.

Для цього виконаємо команду Component-Install Packages ... і в вікні необхідно натиснути на кнопку Add. Після цього необхідно вказати шлях до файлу dclsockets70.bpl, який, як правило, знаходиться в папці BIN. Після цього необхідно натиснути кнопку Ок. Усі компоненти у Вас повинні з'явитися на вкладці Internet (TClientSocket та TServerSocket).

У проекті я починав всю роботу, з мінімальної розробки сервера. Спочатку встановив компонент TServerSocket на форму. І після натискання на кнопку Запустити сервер задав початкові налаштуваннядля його ініціалізації:

Server. Port: = FormServerSetting. SpinEditPort. Value; //Вказуємо порт сервера Server. Active: = True; //активуємо його Server. Open; if Server. Active then begin //виводимо повідомлення, що сервер запущений і працює end; …….. //виводимо помилку, якщо сервер не запустився

Для ініціалізації сервера на своїй машині, я задавав лише вільний порт (який не зайнятий іншими додатками) і активував його.

В принципі і все, для роботи мені достатньо було того, щоб сервер був запущений, і я зміг обробляти запити клієнтів, які вони надсилають.

Для того, щоб отримати список клієнтів, які підключаються до сервера і подальшої роботи з ними, я встановив компонент TCheckListBox на форму і на подію OnclientConnect компонента TServerSocket, написав наступний код:

procedure TFormServer. ServerClientConnect (Sender: TObject ; Socket: TCustomWinSocket) ; begin // відстежуємо підключення клієнта RichEditLog. SelAttributes. Color: = clGreen; RichEditLog. SelAttributes. Style: = [fsBold]; CheckListClient. Items. Add (Socket. RemoteHost); RichEditLog. Лінії . Add ("[" + TimeToStr (Time ) + "] Client connected: " + Socket. RemoteHost ) ; //додаємо до списку клієнта, який підключився RichEditLog. Perform (WM_VSCROLL, SB_BOTTOM, 0); end;

Тобто я до списку додаю імена тих клієнтів, які підключаються до сервера, для подальшого отримання інформації про них.

Наприклад, можна отримати детальну інформаціюпро клієнта:

procedure TFormInfoClient. FormShow (Sender: TObject); begin //виводимо інормацію про клієнта Caption: = "Інформація про клієнта:"+ FormServer. CheckListClient. Items [FormServer. CheckListClient. ItemIndex]; LocalName. Caption: = FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. LocalHost; LocalHost. Caption: = FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. LocalAddress; LocalPort. Caption: = IntToStr (FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. LocalPort); RemoteName. Caption: = FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. RemoteHost; RemoteHost. Caption: = FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. RemoteAddress; RemotePort. Caption: = IntToStr (FormServer. Server. Socket. Connections [FormServer. CheckListClient. ItemIndex]. RemotePort); end;

Можна отримати такі дані:

  • Локальне ім'я
  • Локальна адреса
  • Локальний порт
  • Віддалене ім'я
  • Віддалена адреса
  • Віддалений порт

Інформацію я отримую клієнта, за допомогою даного коду, якого я виділив у списку компонента TCheckListBox.

Нічого складного, як бачите, ні, щоб надіслати повідомлення клієнту, можна скористатися наступним кодом:

У квадратних дужках, я вказую, якому клієнту ми надсилатимемо повідомлення (воно дорівнює виділеному клієнту в компоненті TCheckListBox), у повідомлення я вказую #message# — що означає, що це звичайне повідомлення від сервера, яке слід просто вивести у вікні.

Для того щоб отримати повідомлення від клієнта, сервера, нам знадобиться подія OnClientRead компонента TServerSocket і текстова змінна, в яку ми будемо записувати запит, який надсилає клієнт.

procedure TFormServer. ServerClientRead (Sender: TObject ; Socket: TCustomWinSocket) ; var query: String; begin //отримуємо запит від клієнта на карту try query: = Socket. ReceiveText; if pos ("query", query)<>0 then begin //запитуємо у Яндекса або Google координатикартки, що відповідають запиту клієнта end; / / Якщо просто повідомлення від клієнта, то виводимо його if pos ("#message#", query)<>0 then begin end ; ……

З цього коду можна побачити, що клієнт може надсилати як звичайне повідомлення серверу, і запит на отримання карти, виду: Москва, Тверская, 6.

Для цього мені треба визначити, де звичайне повідомлення, а де саме запит, на отримання карти, щоб сервер надалі зміг його обробити. У цьому випадку я до повідомлень клієнта на самому початку додаю такі ідентифікатори:

  • #message#
  • #query#

Якщо спочатку повідомлення клієнта є ідентифікатор #message#, то сервер розпізнає, як звичайне повідомлення від клієнта. Якщо спочатку присутня ідентифікатор #query#, це означає, що клієнт надіслав запит на отримання карти.

Також клієнт, у будь-який час може відключити від сервера, нам також необхідно відстежити, щоб видалити його із загального списку клієнтів, підключених до сервера. Для цього виділяємо компонент TServerSocket і на подію OnClientDisconnect пишемо наступний код:

procedure TFormServer. ServerClientDisconnect (Sender: TObject ; Socket: TCustomWinSocket) ; var i: integer; begin try // відстежуємо відключення клієнта RichEditLog. SelAttributes. Color: = clRed; RichEditLog. SelAttributes. Style: = [fsBold]; for i : = 0 to Server. Socket. ActiveConnections - 1 do begin if Server. Socket. Connections [i] . Handle = Server. Socket. Connections [i] . Handle then begin RichEditLog. Лінії . Add ("[" + TimeToStr (Time ) + "] Client disconnected: " + Socket. RemoteHost ) ; CheckListClient. Items. Delete (i); RichEditLog. Perform (WM_VSCROLL, SB_BOTTOM, 0); end; end; finally //-//-//-//-//-// end ; end;

Ми проходимо по всіх клієнтах, які у нас є у списку і якщо якогось не знаходимо, то видаляємо його з компонента TCheckListBox, це означає, що клієнт у своїй програмі натиснув кнопку Відключитися.




Top