Я додав мережеву підтримку. Тобто створив окремий сервер та окремий клієнт. Сенс полягає в тому, що працює сервер програми, користувач запускається клієнт і користувач вводить запит: Москва Тверська 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, це означає, що клієнт у своїй програмі натиснув кнопку Відключитися.

Доброго часу доби, шановні!
Питання у мене трохи поверхове, але важливе (принаймні, для мене:)), більше схоже на "порадьте" -).

Є завдання написати аналог HTTP(S) проксі-сервера, але з невеликою специфікою: запити деяких підключених клієнтів будуть оброблятися по-іншому і обмін з такими клієнтами відбуватиметься маленькими пакетами. Іншим клієнтам ("не особливо обдарованим") має бути так само добре, як і зі звичайним проксі:). Основні вимоги, що пред'являються до такого сервера - бути стійким до примхливих клієнтів (це ті, хто маленькими пакетами буде кулятися) та решти клієнтів, і тримати максимально можливу (наскільки дозволять ОС+hardware та ін.) кількість одночасних з'єднань (наприклад, 100: )). У той же час сервер не повинен вийти громіздким і надто складним.

З арсеналу є лише стандартні засоби Delphi (Indy та інші бібліотеки відкидаємо).

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

Перше, що спадає на думку - TTCP, TSocket або голе API of WinSock.

З TSocket працював давно (останній раз років 6-7 тому) - тоді дуже не сподобалося. Деякі пакети пропускалися (хоча TCP вважають "гарантованим"). Коли тут на форумі запитав у чому річ, мені тоді натякнули, якщо мені не зраджує пам'ять, на метод Nagle. Але проблему так і не вирішив (на той час).

Вчора "тест-драйв" спробував влаштувати TCPServer`у. Написав на базі нього простенький проксі для обробки GET запитів, який, загалом, навіть працював, але меееееедленно. Реагував на подію OnAccept, режим блокування - bmThreadBlocking, читав і писав із сокети використовуючи ReceiveBuf і SendBuf.

Загалом, шановні, як краще організувати роботу сервера (прийом з'єднань на сервері та створення тимчасових підключень усередині потоку для отримання даних з іншого сервера та віддачі їх назад клієнту) і за допомогою чого:
* Писати на голому WinSock API, потоки створювати самому, працювати в блокувальному режимі
* Писати на голому WinSock API, працювати в неблокуючому режимі, тоді як я розумію, потоки створювати не потрібно буде
* Аналогічно описаному вище, тільки з використанням як опорну точку один з компонентів\класів (наприклад, (T)TCP, (T)Socket)
* Застосовувати "фішку" Delphi - режим ThreadBlocking
* щось інше

Заздалегідь дякую всім, хто дочитав до кінця і відповів!


DVM (2010-05-11 18:43)

чим Indy щось не догодив?


kernel ( 2010-05-11 18:50 )


> DVM © (11.05.10 18:43)
> чим Indy щось не догодив?

Ліцензією, вагою виправлення (у тому сенсі, що виправляти багато доводиться, щоб щось додати\змінити на нижчому рівні), потім, пам'ятаю, були проблеми з убиванням потоків (http://сайт/view/6-1236577141/) .


DVM (2010-05-11 19:34)


> kernel © (11.05.10 18:50)

Якщо хочеш максимум контролю над сервером, роби все сам на WinSock. Я б напевно вибрав блокуючий режим і щось на кшталт пулу потоків.


Slym (2010-05-11 20:37)

kernel © (11.05.10 18:07)
Написав на базі нього простенький проксі для обробки GET запитів

Твоє завдання зводиться
1. прийняти заголовок у клієнта, видерти від туди host:port замовленого сервера
2. сконектуватись по host:port і цілком можливо з мінімальними змінами відправити прийняті в п1. від клієнта дані
3. забезпечити прозоре тунелювання трафіку клієнт/сервер
4. (ОПЦІЯ) загальмувати особливо завзяті конекти наример тупим sleep (при буфері сокету в 8кб + sleep(1000) отримаємо 8кб/сек максимум)


Сергій М. (2010-05-11 22:02)


> пакети пропускалися (хоча TCP вважають "гарантованим").
> Коли тут на форумі запитав у чому справа, мені тоді натякнули,
> якщо мені не змінює пам'ять, на метод Nagle

Схоже, ти вже й сам не пам'ятаєш про що було твоє питання.
Використовується або не використовується Nagle – на "пропуск пакетів" він вплинути ніяк не може в принципі.


kernel ( 2010-05-13 14:08 )

Дякую всім за відповіді!


> DVM © (11.05.10 19:34)

Все що потрібно з контролю - спілкування з клієнтом із заданими розмірами пакетів (маленькими пакетами для шкідливих клієнтів та великими - для інших) з можливістю їх "переробки" та підрахунок відправлених отриманих даних. Все це, якщо я не помиляюся, успішно виконують і звичайні сокетоподібні компоненти. Чи все одно краще застосувати чистий WinSock? :) Тут, напевно, більше цікавить у кого з них (сік. компоненти vs WinSock API) продуктивність буде більшою. Чи в цьому плані різниці жодної немає?

> Slym © (11.05.10 20:37)

Як працює проксі мені відомо. Робив саме так, як ви описали.

> Сергій М. © (11.05.10 22:02)

Справді призабув. Знайшов ті старі вихідні засоби на цих компонентах, там швидше за все була проблема в тому, що в короткий проміжок часу сервер сам чіплявся до клієнтів і відправляв усім клієнтам команди. Для того, щоб усім все успішно вирушило, необхідно було ставити затримку близько 1 сек. (принаймні у мене працювало тільки не< ~1 сек.) после отсылки команды каждому ПК. Вот тут мне про Nagle скорее всего и намекнули.


DVM (2010-05-13 16:25)


> kernel © (13.05.10 14:08)


> Тут, напевно, більше цікавить у когось із них (сік. компоненти
> vs WinSock API) продуктивність буде більшою. Або
> у цьому плані різниці немає?

Компоненти вони на базі WinSock побудовані. Різниці між ними та чистим Winsock практично немає. Принаймні, це не те місце, де слід шукати різницю.


DVM (2010-05-13 16:26)


> kernel © (13.05.10 14:08)

Інша справа, що навколо WinSock можна написати свої простенькі обгортки, що більше підходять до конкретного завдання - у цьому їхня зручність.


kernel ( 2010-05-14 21:46 )

Дякую за відповіді, DVM!
Зробив свій клас на основі ServerSocket, все працює швидко і чудово [тьху-тьху-тьху], контролю цілком вистачає.
PS: собст-но це повідомлення і відправив через пробний проксі:)


kernel ( 2010-05-18 17:36 )

Всім привіт! Щоб не створювати окремий топік, запитаю тут (оскільки розмова пов'язана з цією темою).

Організував потрібний мені функціонал з урахуванням TServerSocket (режим - stThreadBlocking) зі створенням окремих потоків кожному за встановленого з'єднання. Дійшов до моменту, де мені потрібно робити тунелювання між [клієнт]<->["проксі-сервер"]<->[запрошуваний сервер] для роботи SSL. Механізм такий: клієнт надсилає мені запит CONNECT (HTTP/1.0), потім після того, як я (проксі) відправляю заголовок "200 OK - з'єднання встановлено", в циклі, поки будь-яке з двох з'єднань не розірветься (клієнт або TargetHost), ловлю дані в буфер від клієнта і відразу передаю запитуваному серверу, поки від клієнта не почне приходити нуль байт. Потім те саме роблю, але навпаки, ловлю дані в буфер від запитуваного сервера і відразу передаю клієнту. І це все повторюється до тих пір, поки хтось із них не розірве з'єднання.

...
const BufSize = 128;
...
ClientStream: TWinSocketStream;
TargetConnection: TTcpClient;
Buf: array of Byte;
...
ClientStream:= TWinSocketStream.Create(ClientSocket, 60000);
...
while (КлієнтІПунктПризначенняПідключені) do begin
ClientWait:= (ClientStream.WaitForData(100));
if ClientWait then begin
while (ClientSocket.Connected) and (TargetConnection.Connected) and (RecLen > 0) do begin
TargetConnection.SendBuf(Buf, RecLen);
if (not ClientStream.WaitForData(10)) then Break;
RecLen:= ClientStream.Read(Buf, BufSize);
end;
end;

ServerWait:= TargetConnection.WaitForData(100);
if ServerWait then begin
while (ClientSocket.Connected) and TargetConnection.Connected) and (RecLen > 0) do begin
ClientStream.Write(Buf, RecLen);
if (not ClientSocket.Connected) or (not TargetConnection.Connected) then Break;
if (not TargetConnection.WaitForData(10)) then Break;
RecLen:= TargetConnection.ReceiveBuf(Buf, BufSize);
end;
end;
end;

Все б добре (і навіть відпрацьовуються кілька проходів загального циклу вірно), але практично відразу загальний цикл завмирає (точніше, не завмирає, а нескінченно крутиться) і спостерігається дивна картина: у той час як кількість прийнятих байт ( RecLen:= ClientStream.Read ...) з цього сокету стає весь час нулю. При цьому, обидва з'єднання не дисконнектяться (хоча з чого б їм розриватися, якщо дані "непередалися").

Запитання: Що я роблю не так? :)


kernel ( 2010-05-18 17:40 )

Тобто, простіше, через деякий час цикл "крутиться", а дані не хоче віддавати жоден сокет:/

Вступ

Ця стаття присвячена створенню програм архітектури клієнт/сервер в Borland Delphi на основі сокетів ("sockets" - гнізда). А написав я цю статтю не просто так, а тому що останнім часом це питання дуже багато чого стало цікавити. Поки що торкнемося лише створення клієнтської частини сокетного додатка.
Вперше я познайомився із сокетами, якщо не помиляюся, рік чи півтора тому. Тоді стояло завдання розробити прикладний протокол, який передавав би на серверну машину (працюючу на ОС Unix/Linux) запит і отримував відповідь по сокетному каналу. Слід зазначити, що на відміну будь-яких інших протоколів (FTP, POP, SMTP, HTTP, тощо.), сокети - це база цих протоколів. Таким чином, користуючись сокетами, можна самому створити (симітувати) і FTP, і POP, будь-який інший протокол, причому не обов'язково вже створений, а навіть свій власний!

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

Алгоритм роботи із сокетними протоколами

Так що ж дозволяють нам робити сокети? Та все що завгодно! І в цьому одна з головних переваг цього способу обміну даними в мережі. Справа в тому, що при роботі з сокетом Ви просто надсилаєте іншому комп'ютеру послідовність символів. Так що цим методом Ви можете надсилати як прості повідомлення, так і цілі файли! Причому контролювати правильність передачі Вам не потрібно (як це було під час роботи з COM-портами)!
Нижче слідує зразкова схема роботи з сокетами в Дельфі-додатках:

Визначення св-в Host та Port >>> Запуск Сокету (ClientSocket1.Open) >>> Авторизація >>> Посилання/прийом даних >>> Закриття Сокету

Розберемо схему докладніше:
Визначення св-в Host і Port - щоб успішно встановити з'єднання, потрібно надати властивостям Host і Port компонента TClientSocket необхідні значення. Host - це хост-ім'я (наприклад: nitro.borland.com) або IP-адреса (наприклад: 192.168.0.88) комп'ютера, з яким треба з'єднатися. Port – номер порту (від 1 до 65535) для встановлення з'єднання. Зазвичай номери портів беруться, починаючи з 1001 – т.к. номери менше 1000 можуть бути зайняті системними службами (наприклад, POP – 110). Докладніше про практичну частину див.нижче;
Відкриття сокета - після того, як Ви призначили властивостям Host і Port відповідні значення, можна приступити безпосередньо до відкриття сокета (сокет тут розглядається як черга, в якій містяться символи, що передаються від одного комп'ютера до іншого). Для цього можна викликати метод Open компонента TClientSocket, або надати властивості Active значення True. Тут корисно ставити обробник виняткову ситуацію на той випадок, якщо з'єднатися не вдалося. Докладніше про це можна прочитати нижче, у практичній частині;
Авторизація - цей пункт можна пропустити, якщо сервер не вимагає введення будь-яких логінів та паролів. На цьому етапі Ви надсилаєте серверу свій логін (ім'я користувача) та пароль. Але механізм авторизації залежить від конкретного сервера;
Посилання / прийом даних - це, власне, і є те, навіщо відкривалося сокетне з'єднання. Протокол обміну даними також залежить від сервера;
Закриття сокета - після всіх виконаних операцій необхідно закрити сокет за допомогою методу Close компонента TClientSocket (або надати властивості Active значення False).

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

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

Властивості
Active – показує, відкритий сокет чи ні. Тип: Boolean. Відповідно, True – відкритий, а False – закритий. Ця властивість є для запису;
Host - рядок (Тип: string), що вказує на хост-ім'я комп'ютера, якого слід підключитися;
Address - рядок (Тип: string), що вказує на IP-адресу комп'ютера, до якого слід підключитися. На відміну від Host, тут може бути лише IP. Відмінність в тому, що якщо Ви вкажете в Host символьне ім'я комп'ютера, то IP-адреса, що відповідає цьому імені буде запитана у DNS;
Port – номер порту (Тип: Integer (Word)), до якого слід підключитися. Допустимі значення - від 1 до 65535;
Service - рядок (Тип: string), визначальний службу (ftp, http, pop, тощо.), до порту якої відбудеться підключення. Це своєрідний довідник відповідності номерів портів різним стандартним протоколам;
ClientType – тип з'єднання. ctNonBlocking – асинхронна передача даних, тобто. посилати та приймати дані по сокету можна одночасно за допомогою OnRead та OnWrite. ctBlocking – синхронна передача даних. Події OnRead та OnWrite не працюють. Цей тип з'єднання корисний для організації обміну даними за допомогою потоків (тобто робота з сокетом як з файлом);

Методи
Open - відкриття сокета (аналогічно присвоєння значення True якості Active);
Close - закриття сокета (аналогічно присвоєння значення False властивості Active);

У цьому всі методи компонента TClientSocket вичерпуються. А Ви запитаєте: "А як же працювати із сокетом? Як тоді пересилати дані?". Про це Ви дізнаєтесь трохи далі.

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

Найлегше (і корисніше) вивчати будь-які методи програмування на практиці. Тому наведено приклади з деякими коментарями:

Приклад 1. Найпростіша сокетна програма

(У форму потрібно помістити кнопку TButton і два TEdit. При натисканні на кнопку викликається обробник події OnClick - Button1Click. Перед цим у перший з TEdit-ів потрібно ввести хост-ім'я, а в другий - порт віддаленого комп'ютера. НЕ ЗАБУДЬТЕ ПОМІСТИТИ У ФОРМУ КОМПОНЕНТ TClientSocket !}


begin
(Привласнюємо властивостям Host та Port потрібні значення)
ClientSocket1.Host:= Edit1.Text;
ClientSocket1.Port:= StrToInt(Edit2.Text);
(Намагаємося відкрити сокет та встановити з'єднання)
ClientSocket1.Open;
end;


begin
(Щойно сталося з'єднання - закриваємо сокет і перериваємо зв'язок)
ClientSocket1.Close;
end;

Якщо Ви думаєте, що цей приклад програми абсолютно непотрібний і не може принести жодної користі, то глибоко помиляєтеся. Наведений код – найпростіший приклад сканера портів (PortScanner). Суть такої утиліти в тому, щоб перевіряти, чи вказаний порт і готовий до прийому/передачі даних. Саме на такому принципі заснований PortScanner із програми NetTools Pro.

Приклад 2. Посилання/прийом текстових повідомлень за сокетами

(У форму потрібно помістити дві кнопки TButton і три TEdit. При натисканні на першу кнопку викликається обробник події OnClick - Button1Click. Перед цим у перший з TEdit-ів потрібно ввести хост-ім'я, а в другій - порт віддаленого комп'ютера. Після встановлення з'єднання можна посилати текстові повідомлення, вводячи текст у третій TEdit і натискаючи другу кнопку TButton. Щоб від'єднатися, потрібно ще раз натиснути першу TButton. !}

procedure Button1Click(Sender: TObject);
begin
(Якщо з'єднання вже встановлено – перериваємо його.)
if ClientSocket1.Active then begin
ClientSocket1.Close;
Exit; (...і виходимо з оброблювача)
end;
(Привласнюємо властивостям Host та Port потрібні значення)
ClientSocket1.Host:= Edit1.Text;
ClientSocket1.Port:= StrToInt(Edit2.Text);
(Намагаємося відкрити сокет та встановити з'єднання)
ClientSocket1.Open;
end;


begin
(Щойно сталося з'єднання - посилаємо вітання)
Socket.SendText('Hello!');
ListBox1.Items.Add(′< Hello!′);
end;

procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
begin
(Якщо надійшло повідомлення - додаємо його в ListBox)
ListBox1.Items.Add(′>′+Socket.ReceiveText);
end;

procedure Button2Click(Sender: TObject);
begin
(Натиснута кнопка - надсилаємо текст із третього TEdit)
ClientSocket1.Socket.SendText(Edit3.Text);
ListBox1.Items.Add(′< ′+Edit3.Text);
end;

ПРИМІТКА: У деяких випадках (залежних від сервера) потрібно після кожного повідомлення надсилати переклад рядка:
ClientSocket1.Socket.SendText(Edit3.Text+#10);

Робота із сокетним потоком

"А як ще можна працювати із сокетом?", - Запитаєте Ви. Звичайно, наведений вище спосіб - не найкраще рішення. Самих способів організації роботи з сокетами дуже багато. Я наведу лише один додатковий - робота через потік. Напевно, багато хто з Вас вже має досвід роботи, якщо не з потоками (stream), то з файлами - точно. Для тих, хто не знає, потік – це канал для обміну даними, робота з яким аналогічна роботі зі звичайним файлом. Нижченаведений приклад показує, як організувати потік для роботи із сокетом:

Приклад 3. Потік для роботи із сокетом

procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var з: Char;
MySocket: TWinSocketStream;
begin
(Щойно сталося з'єднання - створюємо потік і асоціюємо його з сокетом (60000 - тайм-аут в мсек))
MySocket: = TWinSocketStream.Create (Socket, 60000);
(Оператор WaitForData чекає на дані з потоку вказаний час в мсек (в даному прикладі - 100) і повертає True, якщо отримано хоча б один байт даних, False - якщо немає жодних даних з потоку.)
while not MySocket.WaitForData(100) do
Application.ProcessMessages;
(Application.ProcessMessages дозволяє Windows перемалювати потрібні елементи вікна і дає час іншим програмам. Якби цього оператора не було і дані досить довго не надходили, то система б злегка "підвисла".)
MySocket.Read(c,1);
(Оператор Read читає вказану кількість байт із потоку (в даному прикладі - 1) у вказану змінну певного типу (у прикладі - у змінну з типу Char). Зверніть увагу на те, що Read, на відміну від ReadBuffer, не встановлює строгих обмежень на кількість прийнятої інформації, тобто Read читає не більше n байтів із потоку (де n - вказане число).Ця функція повертає кількість отриманих байтів даних.
MySocket.Write(c,1);
(Оператор Write аналогічний оператору Read, за тим лише винятком, що Write пише дані у потік.)
MySocket.Free;
(Не забудемо звільнити пам'ять, виділену під потік)
end;

ПРИМІТКА: Для використання потоку не забудьте встановити властивість ClientType у ctBlocking.

Посилання/прийом складних даних

Іноді необхідно пересилати по мережі як прості текстові повідомлення, а й складні структури (тип record в Паскалі), і навіть файли. І тоді Вам необхідно використати спеціальні оператори. Деякі з них перераховані нижче:

Методи TClientSocket.Socket (TCustomWinSocket, TClientWinSocket):
SendBuf(var Buf; Count: Integer) - Посилання буфера через сокет. Буфером може бути будь-який тип, чи то структура (record), чи простий Integer. Буфер вказується параметром Buf, другим параметром Ви повинні вказати розмір даних, що пересилаються в байтах (Count);
SendText(const S: string) - Посилання текстового рядка через сокет. Цей метод розглядався на прикладі 2 (див. вище);
SendStream(AStream: TStream) - Здійснення вмісту зазначеного потоку через сокет. Потік, що пересилається, повинен бути відкритий. Потік може бути будь-якого типу - файловий, із ОЗУ, і т.д. Опис роботи безпосередньо з потоками виходить за рамки цієї статті;
Всім перерахованим методам відповідають методи Receive... Їх опис можна переглянути у довідковому файлі Дельфі (VCL help).

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

(У даному прикладі потрібно додати у форму ще два TEdit - Edit3 та Edit4 для введення логіну та паролю)

procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket);
var з: Char;
MySocket: TWinSocketStream;
login,password: string;
begin
MySocket: = TWinSocketStream.Create (Socket, 60000);
(Додаємо до логіну та паролю символ перекладу рядка, щоб сервер зміг відокремити логін та пароль.)
login:= Edit3.Text+#10;
password:= Edit4.Text+#10;
MySocket.Write(login,Length(Edit3.Text)+1);
MySocket.Write(password,Length(Edit4.Text)+1);
while not MySocket.WaitForData(100) do
Application.ProcessMessages;
MySocket.Read(c,1);
(Тут сервер посилає нам один байт, значення 1 якого відповідає підтвердженню успішної авторизації, а 0 - помилку (це лише приклад). Далі ми виконуємо потрібні дії (прийом/пересилання даних) та закриваємо потік.)
MySocket.Free;
end

Ця стаття присвячена створенню програм архітектури клієнт/сервер в 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). Московська область, м.Жуковський

Вступ

Ця стаття присвячена створенню додатків архітектури клієнт/сервер у 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,"< "+Edit1.Text); end;

Прийоми роботи з 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). Московська область, м.Жуковський