9. Програмування TCP-IP
9.1. Загальні відомості
9.1.1. Протоколи Інтернету
Тут ми подаємо вступ до протоколів зв’язку в Інтернеті, які також називають набором протоколів TCP/IP (Transfer Control Protocol / Internet Protocol) — за назвами двох основних протоколів. Перед тим, як приступити до розробки розподілених додатків, читачеві бажано мати загальне уявлення про функціонування мереж, зокрема про протоколи TCP/IP.
Наведений нижче текст є частковим перекладом уривка з документа «Lan Workplace for Dos — Administrator's Guide» від NOVELL, опублікованого на початку 90-х років.
Загальна концепція створення мережі гетерогенних комп’ютерів походить з досліджень, проведених DARPA (Агентство з перспективних оборонних досліджень) у США. DARPA розробила набір протоколів, відомий під назвою TCP/IP, який дозволяє гетерогенним машинам взаємодіяти між собою. Ці протоколи були протестовані в мережі під назвою ARPAnet, яка згодом перетворилася на мережу INTERNET. Протоколи TCP/IP визначають формати та правила передачі й прийому даних, незалежні від організації мереж та використовуваного обладнання.
Мережа, спроектована за протоколом DARPA та керована протоколами TCP/IP, є мережею з комутацією пакетів. Така мережа передає інформацію по мережі невеликими частинами, які називаються пакетами. Таким чином, якщо комп’ютер передає великий файл, його розбивають на невеликі частини, які надсилаються по мережі для подальшого відновлення у пункті призначення. TCP/IP визначає формат цих пакетів, а саме:
- джерело пакета
- призначення
- довжина
- тип
9.1.2. Модель OSI
Протоколи TCP/IP приблизно відповідають моделі відкритої мережі, яка називається OSI (Open Systems Interconnection Reference Model) і визначена ISO (Міжнародною організацією зі стандартизації). Ця модель описує ідеальну мережу, в якій взаємодію між машинами можна представити у вигляді семирівневої моделі:
![]() |
Кожен рівень отримує послуги від нижчого рівня та надає свої послуги вищому рівню. Припустимо, що два додатки, розташовані на різних машинах A та B, хочуть встановити зв’язок: вони роблять це на рівні Application. Їм не потрібно знати всіх деталей функціонування мережі: кожна програма передає інформацію, яку вона хоче надіслати, на нижній рівень — рівень Présentation. Отже, програмі потрібно знати лише правила взаємодії з рівнем Présentation.
Як тільки інформація потрапляє на рівень Présentation, вона за іншими правилами передається на рівень Session і так далі, доки інформація не потрапить на фізичний носій і не буде фізично передана на машину-адресата. Там вона пройде обробку, зворотну до тієї, яку вона пройшла на комп’ютері-відправнику.
На кожному рівні процес-відправник, відповідальний за надсилання інформації, надсилає її процесу-одержувачу на іншому комп’ютері, що належить до того самого рівня. Це відбувається за певними правилами, які називаються протоколом рівня. Отже, маємо таку кінцеву схему зв’язку:
![]() |
Роль різних рівнів така:
Забезпечує передачу бітів по фізичному носію. На цьому рівні знаходяться кінцеві пристрої обробки даних (E.T.T.D), такі як термінал або комп’ютер, а також пристрої завершення каналів передачі даних (E.T.C.D), такі як модулятор/демодулятор, мультиплексор, концентратор. На цьому рівні слід звернути увагу на такі моменти: . вибір способу кодування інформації (аналоговий або цифровий) . вибір способу передачі (синхронний або асинхронний). | |
Приховує фізичні особливості фізичного рівня. Виявляє та виправляє помилки передачі. | |
Керує маршрутом, яким має пройти інформація, що надсилається в мережі. Це називається routage: визначення маршруту, яким має пройти інформація, щоб дістатися до адресата. | |
Забезпечує взаємодію між двома додатками, тоді як попередні рівні дозволяли лише взаємодію між машинами. Однією зі служб, що надаються цим рівнем, може бути мультиплексування: транспортний рівень може використовувати одне й те саме мережеве з’єднання (від машини до машини) для передачі інформації, що належить кільком додаткам. | |
На цьому рівні знаходяться послуги, що дозволяють додатку відкривати та підтримувати робочу сесію на віддаленому комп’ютері. | |
Вона спрямована на уніфікацію представлення даних на різних машинах. Таким чином, дані, що надходять з машини A, будуть «оформлені» шаром Présentation машини A відповідно до стандартного формату перед відправкою в мережу. Потрапивши на рівень Présentation комп’ютера-одержувача B, який розпізнає їх завдяки стандартному формату, дані будуть «оформлені» іншим чином, щоб програма комп’ютера B могла їх розпізнати. | |
На цьому рівні знаходяться програми, які зазвичай використовуються користувачем, такі як електронна пошта або передача файлів. |
9.1.3. Модель TCP/IP
Модель OSI — це ідеальна модель, яка ще ніколи не була реалізована. Набір протоколів TCP/IP наближається до неї у такому вигляді:
![]() |
Фізичний рівень
У локальній мережі зазвичай використовується технологія Ethernet або Token-Ring. Тут ми розглянемо лише технологію Ethernet.
Ethernet
Ця назва походить від технології локальних мереж з комутацією пакетів, винайденої в компанії Xerox на початку 1970-х років і стандартизованої компаніями Xerox, Intel та Digital Equipment у 1978 році. Фізично мережа складається з коаксіального кабелю діаметром приблизно 1,27 см і довжиною не більше 500 м. Її можна розширити за допомогою répéteurs, причому між двома пристроями може бути не більше двох повторювачів. Кабель є пасивним: усі активні елементи знаходяться на пристроях, підключених до кабелю. Кожен пристрій підключається до кабелю за допомогою мережевої карти, що містить:
- передавач (transceiver), який виявляє наявність сигналів у кабелі та перетворює аналогові сигнали на цифрові і навпаки;
- з'єднувач, який приймає цифрові сигнали від передавача та передає їх на комп'ютер для обробки або навпаки.
Основні характеристики технології Ethernet такі:
- Пропускна здатність 10 мегабіт/секунду.
- Шинна топологія: усі пристрої підключені до одного кабелю
![]() |
- Мережа з розсилковим доступом — комп'ютер-відправник передає інформацію по кабелю, вказуючи адресу комп'ютера-одержувача. Усі підключені комп'ютери отримують цю інформацію, але зберігає її лише той, якому вона призначена.
- Метод доступу такий: передавач, який бажає здійснити передачу, прослуховує кабель — він виявляє наявність або відсутність несучої хвилі, наявність якої означала б, що передача вже відбувається. Це техніка CSMA (Carrier Sense Multiple Access). За відсутності несучої хвилі передавач може вирішити передавати у свою чергу. Таке рішення можуть прийняти одразу кілька передавачів. Випромінювані сигнали змішуються: кажуть, що відбувається колізія. Передавач виявляє цю ситуацію: одночасно з передачею по кабелю він прослуховує, що насправді проходить по ньому. Якщо він виявляє, що інформація, яка проходить по кабелю, не є тією, яку він сам передав, він робить висновок про наявність колізії та припиняє передачу. Інші передавачі, які здійснювали передачу, зроблять те саме. Кожен відновить передачу через випадковий проміжок часу, що залежить від конкретного передавача. Ця техніка називається CD (Collision Detect). Відповідно, метод доступу називається CSMA/CD.
- 48-бітна адресація. Кожна машина має адресу, яку тут називають фізичною адресою, яка записана на платі, що з’єднує її з кабелем. Цю адресу називають адресою Ethernet машини.
Мережевий рівень
На рівні цього рівня ми знаходимо протоколи IP, ICMP, ARP та RARP.
Передає пакети між двома вузлами мережі | |
ICMP забезпечує зв’язок між програмою протоколу IP на одній машині та програмою того ж протоколу на іншій машині. Отже, це протокол обміну повідомленнями всередині самого протоколу IP. | |
здійснює відповідність між інтернет-адресою машини та фізичною адресою машини | |
здійснює відповідність фізичної адреси комп'ютера --> інтернет-адреси комп'ютера |
Транспортний та сесійний рівні
У цьому рівні містяться такі протоколи:
Забезпечує надійну передачу інформації між двома клієнтами | |
Забезпечує ненадійну передачу інформації між двома клієнтами |
Рівні «Додаток/Презентація/Сесія»
Тут містяться різні протоколи:
Емулятор терміналу, що дозволяє машині A підключатися до машини B як термінал | |
дозволяє передавати файли | |
дозволяє передавати файли | |
дозволяє обмінюватися повідомленнями між користувачами мережі | |
перетворює ім'я комп'ютера на його інтернет-адресу | |
створений компанією Sun MicroSystems, він визначає стандартне представлення даних, незалежне від конкретних машин | |
також визначений компанією Sun; це протокол зв’язку між віддаленими додатками, незалежний від транспортного рівня. Цей протокол є важливим: він звільняє програміста від необхідності знати деталі транспортного рівня та робить додатки переносимими. Цей протокол базується на протоколі XDR | |
, який також був розроблений компанією Sun; цей протокол дозволяє одній машині «бачити» файлову систему іншої машини. Він базується на попередньому протоколі RPC |
9.1.4. Принцип роботи інтернет-протоколів
Додатки, розроблені в середовищі TCP/IP, зазвичай використовують кілька протоколів цього середовища. Прикладний додаток взаємодіє з найвищим рівнем протоколів. Цей рівень передає інформацію на нижній рівень і так далі, аж до фізичного носія. Там інформація фізично передається на приймальний пристрій, де вона знову проходить через ті самі рівні, цього разу у зворотному напрямку, аж доки не досягне програми-одержувача надісланої інформації. Наступна схема ілюструє шлях проходження інформації:
![]() |
Розглянемо приклад: додаток FTP, визначений на рівні шару Application, який забезпечує передачу файлів між машинами.
- Додаток передає послідовність байтів, що підлягає передачі, на рівень transport.
- Рівень transport розбиває цей послідовність байтів на segments та TCP і додає на початок кожного сегмента його номер. Сегменти передаються на мережевий рівень, що керується протоколом IP.
- Рівень IP створює пакет, що інкапсулює отриманий сегмент TCP. У заголовку цього пакета вона розміщує інтернет-адреси комп’ютерів-джерел та комп’ютерів-приймачів. Вона також визначає фізичну адресу комп’ютера-приймача. Все це передається на рівень передачі даних та фізичного зв’язку, тобто на мережеву карту, яка з’єднує комп’ютер із фізичною мережею.
- Там пакет IP, у свою чергу, інкапсулюється у фізичну рамку та надсилається адресату по кабелю.
- На комп’ютері-одержувачі рівень «Зв’язок даних та фізичний зв’язок» виконує зворотну операцію: він розпаковує пакет IP з фізичної рамки та передає його на рівень IP.
- Рівень IP перевіряє правильність пакета: він обчислює контрольну суму на основі отриманих бітів (checksum), яку повинен знайти в заголовку пакета. Якщо це не так, пакет відкидається.
- Якщо пакет визнано правильним, рівень IP розпаковує сегмент TCP, що міститься в ньому, і передає його на вищий рівень transport.
- Рівень transport (у нашому прикладі — рівень TCP) перевіряє номер сегмента, щоб відновити правильний порядок сегментів.
- Вона також обчислює контрольну суму для сегмента TCP. Якщо вона визнається правильною, рівень TCP надсилає підтвердження про отримання на джерельний пристрій, інакше сегмент TCP відхиляється.
- Залишається лише шару TCP передати частину даних сегмента до програми-одержувача цих даних у верхньому шарі.
9.1.5. Адресація в Інтернеті
noeud у мережі може бути комп’ютером, інтелектуальним принтером, файловим сервером — фактично будь-чим, що може здійснювати зв’язок за допомогою протоколів TCP/IP. Кожен вузол має фізичну адресу, формат якої залежить від типу мережі. У мережі Ethernet фізична адреса кодується у 6 байтах. Адреса мережі X25 — це 14-значне число.
Інтернет-адреса вузла є логічною адресою: вона не залежить від апаратного забезпечення та використовуваної мережі. Це 4-байтова адреса, яка ідентифікує як локальну мережу, так і вузол цієї мережі. Інтернет-адреса зазвичай подається у вигляді 4 чисел — значень 4 байтів, розділених крапкою. Так, адреса комп’ютера Lagaffe на факультеті природничих наук в Анже записується як 193.49.144.1, а комп’ютера Liny — як 193.49.144.9. Звідси випливає, що інтернет-адреса локальної мережі становить 193.49.144.0. У цій мережі може бути до 254 вузлів.
Оскільки інтернет-адреси або адреси IP не залежать від мережі, комп’ютер із мережі A може спілкуватися з комп’ютером із мережі B, не зважаючи на тип мережі, у якій він знаходиться: достатньо, щоб він знав свою адресу IP. Протокол IP кожної мережі відповідає за перетворення адреси IP <--> фізичної адреси в обох напрямках.
Адреси IP повинні бути всі різними. У Франції саме INRIA відповідає за присвоєння адрес IP. Фактично ця організація видає адресу для вашої локальної мережі, наприклад 193.49.144.0 для мережі факультету природничих наук в Анже. Адміністратор цієї мережі може потім розподіляти адреси IP від 193.49.144.1 до 193.49.144.254 на свій розсуд. Ця адреса зазвичай записується в спеціальний файл на кожному комп’ютері, підключеному до мережі.
9.1.5.1. Класи адрес IP
Адреса IP — це послідовність із 4 байтів, яка часто записується як I1.I2.I3.I4 і фактично містить дві адреси:
- адресу мережі
- адресу вузла цієї мережі
Залежно від розміру цих двох полів адреси IP поділяються на 3 класи: класи A, B та C.
Клас A
Адреса IP: I1.I2.I3.I4 має вигляд R1.N1.N2.N3, де
R1 | — це мережева адреса |
N1.N2.N3 | — адреса комп’ютера в цій мережі |
Точніше кажучи, форма адреси класу A IP має такий вигляд:
![]() |
Мережева адреса займає 7 бітів, а адреса вузла — 24 біти. Отже, може існувати 127 мереж класу A, кожна з яких може містити до 2²⁴ вузлів.
Клас B
Тут адреса IP: I1.I2.I3.I4 має вигляд R1.R2.N1.N2, де
R1.R2 | — це адреса мережі |
N1.N2 | — адреса комп’ютера в цій мережі |
Точніше кажучи, форма адреси класу B IP має такий вигляд:
![]() |
Адреса мережі займає 2 байти (точніше — 14 бітів), так само як і адреса вузла. Отже, може існувати 2¹⁴ мереж класу B, кожна з яких містить до 2¹⁶ вузлів.
Клас C
У цьому класі адреса IP : I1.I2.I3.I4 має вигляд R1.R2.R3.N1, де
R1.R2.R3 | — це адреса мережі |
N1 | — це адреса комп’ютера в цій мережі |
Точніше кажучи, форма адреси класу C IP має такий вигляд:
![]() |
Мережева адреса займає 3 байти (мінус 3 біти), а адреса вузла — 1 байт. Отже, може існувати 221 мережа класу C, що містять до 256 вузлів.
Оскільки адреса комп’ютера Lagaffe на факультеті природничих наук в Анже становить 193.49.144.1, бачимо, що старший байт дорівнює 193, тобто у двійковій системі числення — 11000001. Звідси випливає, що мережа належить до класу C.
Зарезервовані адреси
- Деякі адреси, такі як IP, є адресами мереж, а не адресами вузлів у мережі. Це ті адреси, в яких адреса вузла встановлена на 0. Так, адреса 193.49.144.0 є адресою IP мережі Факультету наук міста Анже. Отже, жоден вузол мережі не може мати нульову адресу.
- Коли в адресі IP адреса вузла складається виключно з цифр 1, маємо адресу розсилки: ця адреса позначає всі вузли мережі.
- У мережі класу C, яка теоретично допускає 28=256 вузлів, якщо виключити дві заборонені адреси, залишається лише 254 дозволених адреси.
9.1.5.2. Протоколи перетворення «Інтернет-адреса <--> фізична адреса»
Ми бачили, що під час передачі інформації від одного комп’ютера до іншого ці дані під час проходження через рівень IP інкапсулюються в пакети. Вони мають такий вигляд:
![]() |
Отже, пакет IP містить інтернет-адреси комп’ютерів-джерел та комп’ютерів-приймачів. Коли цей пакет передається на рівень, відповідальний за його відправлення у фізичну мережу, до нього додається додаткова інформація для формування фізичного кадру, який зрештою буде надіслано в мережу. Наприклад, формат кадру в мережі Ethernet є таким:
![]() |
У кінцевій рамці містяться фізичні адреси комп’ютерів-відправника та комп’ютера-одержувача. Як їх отримують?
Комп’ютер-відправник, знаючи адресу IP комп’ютера, з яким він хоче встановити зв’язок, отримує його фізичну адресу за допомогою спеціального протоколу, який називається ARP (Address Resolution Protocol).
- Вона надсилає пакет особливого типу, який називається пакетом ARP, що містить адресу IP комп’ютера, фізичну адресу якого потрібно знайти. Вона також подбала про те, щоб включити до нього свою власну адресу IP, а також свою фізичну адресу.
- Цей пакет надсилається до всіх вузлів мережі.
- Вони розпізнають особливий характер пакета. Вузол, який розпізнає у пакеті свою адресу IP, відповідає, надсилаючи відправнику пакета свою фізичну адресу. Як це можливо? Він знайшов у пакеті адресу IP та фізичну адресу відправника.
- Отже, відправник отримує фізичну адресу, яку шукав. Він зберігає її в пам’яті, щоб мати змогу використати її згодом, якщо потрібно буде надіслати інші пакети тому самому одержувачу.
Адреса IP комп’ютера зазвичай записана в одному з його файлів, який він може переглянути, щоб дізнатися її. Цю адресу можна змінити: достатньо відредагувати файл. Фізична адреса, у свою чергу, записана в пам’яті мережевої карти і не може бути змінена.
Коли адміністратор бажає організувати свою мережу по-іншому, йому може знадобитися змінити адреси IP усіх вузлів, а отже — редагувати різні конфігураційні файли окремих вузлів. Це може бути трудомістким процесом і спричинити помилки, якщо машин багато. Один із способів полягає в тому, щоб не призначати комп’ютерам адреси IP: у цьому випадку у файл, з якого комп’ютер повинен отримати свою адресу IP, вписується спеціальний код. Виявивши, що у неї немає адреси IP, машина запитує її за протоколом, який називається RARP (Reverse Address Resolution Protocol). Потім вона надсилає в мережу спеціальний пакет, який називається пакетом RARP, аналогічний попередньому пакету ARP, у який вона вказує свою фізичну адресу. Цей пакет надсилається до всіх вузлів, які потім розпізнають пакет RARP. Один із них, який називається сервером RARP, має файл, що містить відповідність «фізична адреса <--> адреса IP» для всіх вузлів. Тоді він відповідає відправнику пакета RARP, надіславши йому свою адресу IP. Отже, адміністратор, який бажає переналаштувати свою мережу, повинен лише відредагувати файл відповідності сервера RARP. Цей сервер зазвичай повинен мати фіксовану адресу IP, яку він повинен знати, не використовуючи сам протокол RARP.
9.1.6. Мережевий рівень, відомий як рівень IP в Інтернеті
Протокол IP (Інтернет-протокол) визначає форму, яку повинні мати пакети, та спосіб їх обробки під час передачі або прийому. Цей конкретний тип пакета називається датаграмою IP. Ми вже розглядали його раніше:
![]() |
Важливо, що крім даних, які потрібно передати, датаграма IP містить інтернет-адреси комп’ютерів-відправника та комп’ютера-одержувача. Таким чином, комп’ютер-одержувач знає, хто надсилає йому повідомлення.
На відміну від мережевої рамки, довжина якої визначається фізичними характеристиками мережі, через яку вона проходить, довжина датаграми IP встановлюється програмним забезпеченням і тому буде однаковою в різних фізичних мережах. Ми бачили, що при переході від мережевого рівня до фізичного рівня датаграма IP інкапсулювалася у фізичний кадр. Ми навели приклад фізичного кадру мережі Ethernet:
![]() |
Фізичні кадри переміщуються від вузла до вузла до місця призначення, яке може не знаходитися в тій самій фізичній мережі, що й відправник. Отже, пакет IP може послідовно інкапсулюватися в різні фізичні кадри на рівні вузлів, що з’єднують дві мережі різних типів. Також може статися так, що пакет IP виявиться занадто великим, щоб його можна було інкапсулювати у фізичний кадр. У такому разі програмне забезпечення IP вузла, де виникає ця проблема, розбиває пакет IP на fragments згідно з чіткими правилами, після чого кожен із них надсилається у фізичну мережу. Вони будуть знову зібрані лише у кінцевому пункті призначення.
9.1.6.1. Маршрутизація
Маршрутизація — це метод доставки пакетів IP до місця призначення. Існує два методи: пряма маршрутизація та непряма маршрутизація.
Пряма маршрутизація
Пряма маршрутизація — це передача пакета IP безпосередньо від відправника до одержувача в межах однієї мережі:
- Комп’ютер-відправник датаграми IP має адресу одержувача IP.
- Вона отримує фізичну адресу одержувача за допомогою протоколу ARP або зі своїх таблиць, якщо ця адреса вже була отримана.
- Вона надсилає пакет у мережу на цю фізичну адресу.
Непряма маршрутизація
Непряма маршрутизація означає передачу пакета IP до пункту призначення, що знаходиться в іншій мережі, ніж та, до якої належить відправник. У цьому випадку мережеві частини адрес IP комп’ютерів-джерел та комп’ютерів-приймачів відрізняються. Комп’ютер-джерело розпізнає цю особливість. Тоді вона надсилає пакет до спеціального вузла, який називається маршрутизатором (router) — вузла, що з’єднує локальну мережу з іншими мережами, і адресу якого (IP) вона знаходить у своїх таблицях; ця адреса була отримана спочатку або з файлу, або з постійної пам’яті, або ж через інформацію, що циркулює в мережі.
Маршрутизатор підключений до двох мереж і має адресу IP у межах цих двох мереж.
![]() |
У нашому прикладі вище:
- Мережа № 1 має інтернет-адресу 193.49.144.0, а мережа № 2 — адресу 193.49.145.0.
- У мережі № 1 маршрутизатор має адресу 193.49.144.6, а в мережі № 2 — адресу 193.49.145.3.
Роль маршрутизатора полягає в тому, щоб перетворити пакет IP, який він отримує і який міститься у фізичній рамці, типовій для мережі № 1, у фізичну рамку, здатну циркулювати в мережі № 2. Якщо адреса IP одержувача пакета знаходиться в мережі № 2, маршрутизатор надішле йому пакет безпосередньо; в іншому випадку він надішле його іншому маршрутизатору, що з’єднує мережу № 2 з мережею № 3, і так далі.
9.1.6.2. Повідомлення про помилки та контроль
Також на мережевому рівні, тобто на тому ж рівні, що й протокол IP, існує протокол ICMP (Internet Control Message Protocol). Він слугує для надсилання повідомлень про внутрішнє функціонування мережі: несправні вузли, затори на маршрутизаторі тощо... Повідомлення ICMP інкапсулюються в пакети IP і надсилаються по мережі. Рівні IP різних вузлів вживають відповідних заходів відповідно до отриманих повідомлень ICMP. Таким чином, сама програма ніколи не бачить цих проблем, властивих мережі. Вузол використовуватиме інформацію ICMP для оновлення своїх таблиць маршрутизації.
9.1.7. Транспортний рівень: протоколи UDP та TCP
9.1.7.1. Протокол UDP: User Datagram Protocol
Протокол UDP забезпечує ненадійний обмін даними між двома точками, тобто правильна доставка пакета до місця призначення не гарантується. Додаток, за бажанням, може самостійно керувати цим процесом, наприклад, очікуючи після відправлення повідомлення підтвердження про отримання, перш ніж відправляти наступне.
Наразі на рівні мережі ми говорили про адреси IP комп’ютерів. Однак на одному комп’ютері можуть одночасно існувати різні процеси, які можуть взаємодіяти між собою. Тому під час надсилання повідомлення потрібно вказувати не лише адресу IP комп’ютера-одержувача, а й «ім’я» процесу-одержувача. Це ім’я насправді є номером, який називається номером порту. Деякі номери зарезервовані для стандартних програм: наприклад, порт 69 для програми tftp (trivial file transfer protocol). Пакети, що обробляються протоколом UDP, також називаються датаграмами. Вони мають такий вигляд:
![]() |
Ці датаграми інкапсулюються в пакети IP, а потім — у фізичні кадри.
9.1.7.2. Протокол TCP: Протокол керування передачею
Для забезпечення безпечного обміну даними протоколу UDP недостатньо: розробник додатків повинен самостійно розробити протокол, який дозволить йому перевіряти правильність маршрутизації пакетів.
Протокол TCP (Transfer Control Protocol) дозволяє уникнути цих проблем. Його особливості такі:
- Процес, який бажає здійснити передачу, спочатку встановлює з’єднання з процесом-одержувачем інформації, яку він збирається передати. Це з’єднання встановлюється між портом машини-відправника та портом машини-одержувача. Між цими двома портами створюється віртуальний шлях, який буде зарезервований виключно для двох процесів, що встановили з’єднання.
- Усі пакети, що надсилаються процесом-джерелом, проходять цим віртуальним шляхом і надходять у тому порядку, в якому вони були відправлені, чого не гарантував протокол UDP, оскільки пакети могли пролягати різними шляхами.
- Інформація, що надсилається, має безперервний характер. Процес-відправник надсилає інформацію у своєму ритмі. Ця інформація не обов’язково надсилається одразу: протокол TCP чекає, поки її накопичиться достатньо для надсилання. Вона зберігається у структурі, що називається сегментом TCP. Цей сегмент, щойно заповнений, буде переданий на рівень IP, де його інкапсулюють у пакет IP.
- Кожен сегмент, надісланий за протоколом TCP, має свій номер. Протокол TCP на стороні одержувача перевіряє, чи сегменти надходять у правильній послідовності. За кожен правильно отриманий сегмент він надсилає підтвердження про отримання відправнику.
- Коли відправник отримує це підтвердження, він повідомляє про це процес-відправник. Таким чином, останній може дізнатися, що сегмент надійшов за призначенням, чого не було можливо за допомогою протоколу UDP.
- Якщо через певний час протокол TCP, який відправив сегмент, не отримає підтвердження про отримання, він повторно надсилає цей сегмент, тим самим гарантуючи якість послуги передачі інформації.
- Віртуальний канал, встановлений між двома процесами, що взаємодіють, має номер full-duplex: це означає, що інформація може передаватися в обох напрямках. Таким чином, процес-приймач може надсилати підтвердження про отримання навіть тоді, коли процес-джерело продовжує надсилати інформацію. Це дозволяє, наприклад, вихідному протоколу TCP надсилати кілька сегментів, не чекаючи на підтвердження отримання. Якщо через певний час він виявляє, що не отримав підтвердження отримання певного сегмента № n, він відновить передачу сегментів з цього моменту.
9.1.8. Рівень додатків
Над протоколами UDP та TCP існують різні стандартні протоколи:
TELNET
Цей протокол дозволяє користувачеві з комп’ютера A в мережі підключитися до комп’ютера B (який часто називають хост-комп’ютером). TELNET емулює на комп’ютері A так званий універсальний термінал. Таким чином, користувач діє так, ніби має термінал, підключений до комп’ютера B. Telnet базується на протоколі TCP.
FTP: (Протокол передачі файлів)
Цей протокол дозволяє обмінюватися файлами між двома віддаленими машинами, а також виконувати операції з файлами, такі як, наприклад, створення каталогів. Він базується на протоколі TCP.
TFTP: (Тривіальний контроль передачі файлів)
Цей протокол є варіантом FTP. Він базується на протоколі UDP і є менш досконалим, ніж FTP.
DNS: (Система доменних імен)
Коли користувач бажає обмінюватися файлами з віддаленим комп’ютером, наприклад, за допомогою FTP, він повинен знати інтернет-адресу цього комп’ютера. Наприклад, щоб виконати FTP на комп’ютері Lagaffe в університеті Анже, потрібно запустити FTP наступним чином: FTP 193.49.144.1
Це вимагає наявності довідника, що встановлює відповідність між машиною та адресою IP. Ймовірно, у цьому довіднику машини позначатимуться символічними іменами, такими як:
машина DPX2/320 Анжерського університету
машина Sun з ISERPA в Анже
Очевидно, що було б зручніше позначати машину іменем, а не її адресою IP. Тоді постає проблема унікальності імені: існують мільйони взаємопов’язаних машин. Можна було б уявити, що імена присвоює централізована організація. Це, безсумнівно, було б досить обтяжливо. Насправді контроль над іменами розподілено між доменами. Кожна доменна зона управляється, як правило, невеликою організацією, яка має повну свободу у виборі імен комп’ютерів. Так, комп’ютери у Франції належать до доменної зони fr, якою керує паризький інститут Inria. Щоб ще більше спростити ситуацію, контроль розподіляють далі: всередині доменної зони fr створюються піддомени. Так, університет Анже належить до піддомену univ-Angers. Служба, що управляє цим доменом, має повну свободу у виборі імен комп’ютерів у мережі Університету Анже. Наразі цей домен не поділений на піддомени. Але у великому університеті, що має багато комп’ютерів у мережі, такий поділ може бути здійснений.
Комп’ютер DPX2/320 Університету Анже отримав ім’я Lagaffe, тоді як PC та 486DX50 отримали ім’я liny. Як посилатися на ці машини ззовні? Вказавши ієрархію доменів, до яких вони належать. Таким чином, повна назва машини Lagaffe буде:
Lagaffe.univ-Angers.fr
У межах доменів можна використовувати відносні імена. Отже, у межах домену fr та поза доменом univ-Angers на комп’ютер Lagaffe можна посилатися як
Lagaffe.univ-Angers
Нарешті, у межах домену univ-Angers на нього можна посилатися просто як
Lagaffe
Отже, програма може посилатися на комп’ютер за його іменем. Зрештою, все одно потрібно отримати інтернет-адресу цього комп’ютера. Як це здійснюється? Припустимо, що з комп’ютера A ми хочемо встановити зв’язок із комп’ютером B.
- якщо машина B належить до того самого домену, що й машина A, її адресу IP, ймовірно, можна буде знайти у файлі на машині A.
- інакше комп’ютер А знайде в іншому файлі або в тому самому, що й раніше, список декількох серверів імен із їхніми адресами IP. Сервер імен відповідає за встановлення відповідності між іменем комп’ютера та його адресою IP. Комп'ютер A надішле спеціальний запит до першого сервера імен зі свого списку, який називається запитом DNS і, отже, містить ім'я шуканого комп'ютера. Якщо запитуваний сервер має це ім'я у своїх таблицях, він надішле комп'ютеру A відповідну адресу IP. В іншому випадку сервер також знайде у своїх файлах список серверів імен, до яких він може звернутися. Тоді він це зробить. Таким чином, буде здійснено запит до певної кількості серверів імен — не хаотично, а таким чином, щоб мінімізувати кількість запитів. Якщо комп’ютер врешті-решт буде знайдено, відповідь надійде назад до комп’ютера А.
XDR: (eXternal — представлення даних)
Цей протокол, розроблений компанією Sun MicroSystems, визначає стандартне, незалежне від конкретних машин представлення даних.
RPC: (Remote Procedure Call)
Цей протокол, також розроблений компанією Sun, призначений для обміну даними між віддаленими додатками та не залежить від транспортного рівня. Цей протокол є важливим: він звільняє програміста від необхідності знати деталі транспортного рівня та забезпечує переносимість додатків. Цей протокол базується на протоколі XDR
NFS: Мережева файлова система
Цей протокол, також розроблений компанією Sun, дозволяє одній машині «бачити» файлову систему іншої машини. Він базується на попередньому протоколі RPC.
9.1.9. Висновок
У цьому вступі ми представили основні положення щодо інтернет-протоколів. Щоб глибше ознайомитися з цією темою, можна прочитати чудову книгу Дугласа Комера:
Назва | TCP/IP: Архітектура, протоколи, додатки. |
Автор | Дуглас COMER |
Видавництво | InterEditions |
9.2. Управління мережевими адресами
Кожен комп'ютер в Інтернеті однозначно визначається адресою IP (Інтернет-протокол) у форматі I1.I2.I3.I4, де In — число від 1 до 254. Його також можна визначити за допомогою унікального імені. Ця назва не є обов’язковою, оскільки програми в кінцевому підсумку завжди використовують IP-адреси пристроїв. Вони існують для того, щоб полегшити життя користувачів. Так, за допомогою браузера простіше звернутися до URL http://www.ibm.com, ніж до URL http://129.42.17.99, хоча обидва способи є можливими. Зв’язок між адресами IP <--> nomMachine забезпечується розподіленою службою Інтернету, яка називається DNS (Domain Name System). Платформа .NET пропонує клас Dns для управління інтернет-адресами:

Більшість методів цього класу є статичними. Розглянемо ті, що нас цікавлять:
повертає адресу IPHostEntry на основі адреси IP у форматі «I1.I2.I3.I4». Генерує виняток, якщо машину address не вдається знайти. | |
повертає адресу IPHostEntry на основі імені машини. Викликає виняток, якщо машину name не вдається знайти. | |
повертає ім’я машини, на якій виконується програма, що виконує цю інструкцію |
Мережеві адреси типу IPHostEntry мають такий вигляд:
![]() |
Властивості, що нас цікавлять:
Список мережевих адрес IP одного комп'ютера. Якщо адреса IP позначає один і тільки один фізичний комп'ютер, то чи може один фізичний комп'ютер мати кілька адрес IP? Так буде, якщо він має кілька мережевих карт, які підключають його до різних мереж. | |
список псевдонімів машини, яку можна позначити основним ім’ям та псевдонімами | |
ім'я машини, якщо воно є |
З класу IPAddress ми виділимо наступні конструктори, властивості та методи:

Об’єкт [IPAddress] можна перетворити на рядок I1.I2.I3.I4 за допомогою методу ToString(). І навпаки, об’єкт IPAddress можна отримати з рядка I1.I2.I3.I4 за допомогою статичного методу IPAddress.Parse("I1.I2.I3.I4"). Розглянемо наступну програму, яка виводить ім’я комп’ютера, на якому вона виконується, а потім у інтерактивному режимі надає відповідності між адресою IP та іменем комп’ютера:
dos>address1
Machine Locale=tahe
Machine recherchée (fin pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (fin pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Alias : 171.146.49.193.in-addr.arpa
Machine recherchée (fin pour arrêter) : www.ibm.com
Machine : www.ibm.com
Adresses IP : 129.42.17.99,129.42.18.99,129.42.19.99,129.42.16.99
Machine recherchée (fin pour arrêter) : 129.42.17.99
Machine : www.ibm.com
Adresses IP : 129.42.17.99
Machine recherchée (fin pour arrêter) : x.y.z
Impossible de trouver la machine [x.y.z]
Machine recherchée (fin pour arrêter) : localhost
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : 127.0.0.1
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : tahe
Machine : tahe
Adresses IP : 127.0.0.1
Machine recherchée (fin pour arrêter) : fin
Програма така:
' параметри
Option Explicit On
Option Strict On
' простори імен
Imports System
Imports System.Net
Imports System.Text.RegularExpressions
' тестовий модуль
Public Module adresses
Sub Main()
' відображає ім’я локального комп’ютера
' потім у інтерактивному режимі надає інформацію про мережеві комп'ютери
' ідентифіковані за іменем або адресою IP
' локальний комп'ютер
Dim localHost As String = Dns.GetHostName()
Console.Out.WriteLine(("Machine Locale=" + localHost))
' інтерактивні запитання-відповіді
Dim machine As String
Dim adresseMachine As IPHostEntry
While True
' введення імені шуканого комп’ютера
Console.Out.Write("Machine recherchée (fin pour arrêter) : ")
machine = Console.In.ReadLine().Trim().ToLower()
' закінчено?
If machine = "fin" Then
Exit While
End If
' адреса I1.I2.I3.I4 чи ім’я комп’ютера?
Dim isIPV4 As Boolean = Regex.IsMatch(machine, "^\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s*$")
' обробка виняткових ситуацій
Try
If isIPV4 Then
adresseMachine = Dns.GetHostByAddress(machine)
Else
adresseMachine = Dns.GetHostByName(machine)
End If
' ім'я
Console.Out.WriteLine(("Machine : " + adresseMachine.HostName))
' адреси IP
Console.Out.Write(("Adresses IP : " + adresseMachine.AddressList(0).ToString))
Dim i As Integer
For i = 1 To adresseMachine.AddressList.Length - 1
Console.Out.Write(("," + adresseMachine.AddressList(i).ToString))
Next i
Console.Out.WriteLine()
' псевдоніми
If adresseMachine.Aliases.Length <> 0 Then
Console.Out.Write(("Alias : " + adresseMachine.Aliases(0)))
For i = 1 To adresseMachine.Aliases.Length - 1
Console.Out.Write(("," + adresseMachine.Aliases(i)))
Next i
Console.Out.WriteLine()
End If
Catch
' машина не існує
Console.Out.WriteLine("Impossible de trouver la machine [" + machine + "]")
End Try
End While
End Sub
End Module
9.3. Розклад занять TCP-IP
9.3.1. Загальні відомості
Розглянемо обмін даними між двома віддаленими машинами A та B:
![]() |
Коли програма AppA на комп’ютері A хоче встановити зв’язок із програмою AppB на комп’ютері B в Інтернеті, їй потрібно знати кілька речей:
- адресу IP або ім’я комп’ютера B
- номер порту, з яким працює програма AppB. Адже комп’ютер B може підтримувати безліч програм, що працюють в Інтернеті. Коли він отримує інформацію з мережі, він повинен знати, для якої саме програми призначена ця інформація. Доступ до мережі додатки на комп’ютері B отримують через «вікна», які також називаються портами зв’язку. Ця інформація міститься в пакеті, отриманому комп’ютером B, щоб його можна було доставити потрібному додатку.
- Протоколи зв’язку, які розуміє машина B. У нашому дослідженні ми використовуватимемо виключно протоколи TCP-IP.
- протокол діалогу, який підтримує програма AppB. Адже машини A та B будуть «спілкуватися» між собою. Те, що вони скажуть, буде інкапсульовано в протоколи TCP-IP. Проте, коли на кінці ланцюга додаток AppB отримає інформацію, надіслану додатком AppA, він повинен бути здатний її інтерпретувати. Це аналогічно ситуації, коли дві особи А та Б спілкуються по телефону: їхній діалог передається через телефон. Слова кодуються телефоном А у вигляді сигналів, передаються по телефонних лініях, досягають телефону Б, де декодуються. Тоді людина Б чує слова. Саме тут виникає поняття протоколу діалогу: якщо А розмовляє французькою, а Б не розуміє цю мову, А і Б не зможуть вести змістовний діалог.
Тому обидва додатки, що спілкуються, повинні домовитися про тип діалогу, який вони використовуватимуть. Наприклад, діалог із сервісом ftp відрізняється від діалогу із сервісом pop: ці два сервіси не приймають однакових команд. Вони мають різний протокол діалогу.
9.3.2. Характеристики протоколу TCP
Тут ми розглянемо лише мережеві комунікації, що використовують транспортний протокол TCP. Нагадаємо його характеристики:
- Процес, який бажає здійснити передачу, спочатку встановлює з’єднання з процесом-одержувачем інформації, яку він збирається передати. Це з’єднання встановлюється між портом машини-відправника та портом машини-одержувача. Між цими двома портами створюється віртуальний шлях, який буде зарезервований виключно для двох процесів, що встановили з’єднання.
- Усі пакети, що надсилаються процесом-джерелом, проходять цим віртуальним шляхом і надходять у тому порядку, в якому вони були відправлені
- Інформація, що надсилається, має безперервний характер. Процес-відправник надсилає інформацію у своєму темпі. Вона не обов’язково надсилається одразу: протокол TCP чекає, поки її накопичиться достатньо для відправлення. Інформація зберігається у структурі, що називається сегментом TCP. Цей сегмент, щойно заповнений, буде переданий на рівень IP, де його інкапсулюють у пакет IP.
- Кожен сегмент, надісланий за протоколом TCP, має свій номер. Протокол TCP одержувача перевіряє, чи сегменти надходять у правильній послідовності. За кожен правильно отриманий сегмент він надсилає підтвердження про отримання відправнику.
- Коли відправник отримує це підтвердження, він повідомляє про це процес-відправник. Таким чином, останній може дізнатися, що сегмент надійшов за призначенням.
- Якщо через певний час протокол TCP, який відправив сегмент, не отримає підтвердження про отримання, він повторно надсилає цей сегмент, тим самим гарантуючи якість послуги передачі інформації.
- Віртуальний канал, встановлений між двома процесами, що взаємодіють, має номер full-duplex: це означає, що інформація може передаватися в обох напрямках. Таким чином, процес-приймач може надсилати підтвердження про отримання навіть тоді, коли процес-джерело продовжує надсилати інформацію. Це дозволяє, наприклад, протоколу-джерелу TCP надсилати кілька сегментів, не чекаючи на підтвердження отримання. Якщо через певний час він виявить, що не отримав підтвердження отримання певного сегмента № n, він відновить передачу сегментів з цієї точки.
9.3.3. Взаємодія «клієнт-сервер»
Часто комунікація в Інтернеті є асиметричною: машина А ініціює з’єднання, щоб звернутися до машини Б із запитом на послугу: вона вказує, що хоче встановити з’єднання зі службою SB1 на машині Б. Остання приймає або відхиляє запит. Якщо вона приймає, машина А може надсилати свої запити до служби SB1. Ці запити повинні відповідати протоколу взаємодії, який розуміє служба SB1. Таким чином між машиною А, яку називають клієнтською машиною, та машиною Б, яку називають серверною машиною, встановлюється діалог «запит-відповідь». Один із двох партнерів закриє з’єднання.
9.3.4. Архітектура клієнта
Архітектура мережевої програми, яка звертається до серверного додатка, матиме такий вигляд:
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la machine B
attendre et récupérer la réponse
la traiter
fin tant que
finsi
9.3.5. Архітектура сервера
Архітектура програми, що надає послуги, буде такою:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
fin tant que
Серверна програма по-різному обробляє початковий запит на з'єднання від клієнта та його подальші запити, спрямовані на отримання послуги. Програма сама не надає послугу. Якби вона це робила, то протягом часу надання послуги вона більше не прислухалася б до запитів на з'єднання, і клієнти не отримували б обслуговування. Тому вона діє інакше: щойно запит на з’єднання надходить на порт прослуховування та приймається, сервер створює завдання, відповідальне за надання послуги, запитуваної клієнтом. Ця послуга надається на іншому порту серверної машини, який називається портом обслуговування. Таким чином можна обслуговувати декількох клієнтів одночасно. Завдання обслуговування матиме таку структуру:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
9.3.6. Клас TcpClient
Клас TcpClient є класом, який підходить для представлення клієнта сервісу TCP. Він визначається наступним чином:

Нас цікавлять такі конструктори, методи та властивості:
створює TCP-з'єднання з сервером, що працює на вказаному порту (port) вказаної машини (hostname). Наприклад, new TcpClient("istia.univ-angers.fr",80) — для підключення до порту 80 машини istia.univ-angers.fr | |
закриває з’єднання з сервером TCP | |
отримує потік NetworkStream для читання та запису на сервер. Саме цей потік забезпечує обмін даними між клієнтом і сервером. |
9.3.7. Клас NetworkStream
Клас NetworkStream представляє мережевий потік між клієнтом і сервером. Клас визначається наступним чином:

Клас NetworkStream є похідним від класу Stream. Багато клієнт-серверних додатків обмінюються рядками тексту, що закінчуються символами кінця рядка «\r\n». Тому доцільно використовувати об’єкти StreamReader та StreamWriter для читання та запису цих рядків у мережевий потік. Коли дві машини взаємодіють, на кожному кінці з’єднання знаходиться об’єкт TcpClient. Метод GetStream цього об’єкта дозволяє отримати доступ до мережевого потоку (NetworkStream), який з’єднує обидві машини. Отже, якщо машина M1 встановила з’єднання з машиною M2 за допомогою об’єкта TcpClient client1, через який вони обмінюються рядками тексту, вона зможе створити свої потоки читання та запису таким чином:
Dim in1 as StreamReader=new StreamReader(client1.GetStream())
Dim out1 as StreamWriter=new StreamWriter(client1.GetStream())
out1.AutoFlush=true
Команда
означає, що потік запису client1 не пройде через проміжний буфер, а піде безпосередньо в мережу. Це важливо. Зазвичай, коли client1 надсилає рядок тексту своєму партнеру, він очікує на відповідь. Ця відповідь ніколи не надійде, якщо рядок насправді був зафіксований у буфері на машині M1 і так і не був надісланий. Щоб надіслати рядок тексту на машину M2, слід написати:
Щоб прочитати відповідь від M2, потрібно написати:
9.3.8. Базова архітектура інтернет-клієнта
Тепер ми маємо всі елементи для опису базової архітектури інтернет-клієнта:
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' здійснюється підключення до сервісу, що працює на порту P машини M
client = New TcpClient(nomServeur, port)
' створюються потоки вводу-виводу клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' цикл «запит — відповідь»
While True
' готуємо запит
demande = ...
' відправляємо його на сервер
OUT.WriteLine(demande)
' зчитування відповіді від сервера
réponse = [IN].ReadLine()
' обробка відповіді
...
End While
' процес завершено
client.Close()
Catch ex As Exception
' обробляється виняток
...
End Try
9.3.9. Клас TcpListener
Клас TcpListener є класом, який підходить для представлення сервісу TCP. Він визначається наступним чином:

Нас цікавлять такі конструктори, методи та властивості:
створює сервіс TCP, який буде очікувати (listen) запити клієнтів на порту, переданому як параметр (port), який називається портом прослуховування локальної машини з адресою IP localadr. | |
приймає запит від клієнта. Повертає у результаті об’єкт TcpClient, пов’язаний з іншим портом, який називається портом обслуговування. | |
запускає прослуховування запитів клієнтів | |
припиняє прослуховування запитів клієнтів |
9.3.10. Базова архітектура інтернет-сервера
З того, що було розглянуто раніше, можна вивести базову структуру сервера:
' створюється служба прослуховування
Dim ecoute As TcpListener = Nothing
Dim port As Integer = ...
Try
' створюється служба
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' запускаємо його
ecoute.Start()
' цикл роботи сервісу
Dim liaisonClient As TcpClient = Nothing
While not fini
' очікування клієнта
liaisonClient = ecoute.AcceptTcpClient()
' сервіс забезпечується іншим завданням
Dim tache As Thread = New Thread(New ThreadStart(AddressOf [méthode]))
tache.Start()
End While
Catch ex As Exception
' повідомляється про помилку
....
End Try
' завершення обслуговування
ecoute.Stop()
Клас Service — це thread, який може мати такий вигляд:
Public Class Service
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' конструктор
Public Sub New(ByVal liaisonClient As TcpClient, ...)
Me.liaisonClient = liaisonClient
...
End Sub
' метод run
Public Sub Run()
' повертає послугу клієнту
Try
' вхідний потік
[IN] = New StreamReader(liaisonClient.GetStream())
' вихідний потік
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' цикл читання запиту/запису відповіді
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' обробка запиту
...
' відправляється відповідь
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' наступний запит
demande = [IN].ReadLine
End While
' завершення з'єднання
liaisonClient.Close()
Catch e As Exception
...
End Try
' завершення обслуговування
End Sub
9.4. Приклади
9.4.1. Сервер-ехо
Ми пропонуємо написати сервер відлуння, який запускатиметься з вікна DOS за допомогою команди:
Сервер працює на порту, переданому як параметр. Він просто повертає клієнту запит, який той йому надіслав. Програма виглядає так:
' опції
Option Explicit On
Option Strict On
' простори імен
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' виклик: serveurEcho порт
' сервер відлуння
' повертає клієнту рядок, який той надіслав
Public Class serveurEcho
Private Shared syntaxe As String = "Syntaxe : serveurEcho port"
' головна програма
Public Shared Sub Main(ByVal args() As String)
' чи є аргумент
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' цей аргумент має бути цілим числом >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' створюється служба прослуховування
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' створюється служба
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' запускаємо його
ecoute.Start()
' моніторинг
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & port))
Console.Out.WriteLine(ecoute.LocalEndpoint)
' цикл роботи служби
Dim liaisonClient As TcpClient = Nothing
While True
' нескінченний цикл — буде зупинено за допомогою Ctrl-C
' очікування клієнта
liaisonClient = ecoute.AcceptTcpClient()
' сервіс обслуговується іншим завданням
nbClients += 1
Dim tache As Thread = New Thread(New ThreadStart(AddressOf New traiteClientEcho(liaisonClient, nbClients).Run))
tache.Start()
End While
' повернення до очікування запитів
Catch ex As Exception
' повідомляється про помилку
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
' завершення обслуговування
ecoute.Stop()
End Sub
' виведення повідомлень про помилки
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
' -------------------------------------------------------
' обслуговує клієнта сервера відлуння
Public Class traiteClientEcho
Private liaisonClient As TcpClient ' liaison avec le client
Private numClient As Integer ' n° de client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
' конструктор
Public Sub New(ByVal liaisonClient As TcpClient, ByVal numClient As Integer)
Me.liaisonClient = liaisonClient
Me.numClient = numClient
End Sub
' метод run
Public Sub Run()
' надає послугу клієнту
Console.Out.WriteLine(("Début de service au client " & numClient))
Try
' вхідний потік
[IN] = New StreamReader(liaisonClient.GetStream())
' вихідний потік
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' цикл читання запиту/запису відповіді
Dim demande As String = Nothing
Dim reponse As String = Nothing
demande = [IN].ReadLine
While Not (demande Is Nothing)
' відстеження
Console.Out.WriteLine(("Client " & numClient & " : " & demande))
' сервіс зупиняється, коли клієнт надсилає маркер кінця файлу
reponse = "[" + demande + "]"
OUT.WriteLine(reponse)
' сервіс зупиняється, коли клієнт надсилає «кінець»
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
' наступний запит
demande = [IN].ReadLine
End While
' завершення з'єднання
liaisonClient.Close()
Catch e As Exception
erreur("Erreur lors de la fermeture de la liaison client (" + e.ToString + ")", 2)
End Try
' завершення роботи служби
Console.Out.WriteLine(("Fin de service au client " & numClient))
End Sub
' відображення помилок
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
Структура сервера відповідає загальній архітектурі TCP-серверів.
9.4.2. Клієнт для сервера-ехо
Тепер ми напишемо клієнт для попереднього сервера. Він буде викликатися наступним чином:
Він підключається до машини nomServeur через порт port, а потім надсилає серверу рядки тексту, які той відбиває у відповідь.
' параметри
Option Explicit On
Option Strict On
' простори імен
Imports System.Net.Sockets
Imports System.Net
Imports System
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class clientEcho
' підключення до сервера відлуння
' кожен рядок, введений з клавіатури, відображається у вигляді відлуння
Public Shared Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg machine port"
' кількість аргументів
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' записуємо ім'я сервера
Dim nomServeur As String = args(0)
' порт повинен бути цілим числом, більшим за 0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' можна працювати
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim demande As String = Nothing ' demande du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' підключаємося до служби, що працює на порту P машини M
client = New TcpClient(nomServeur, port)
' створюються потоки вводу-виводу клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' цикл запит — відповідь
While True
' запит надходить з клавіатури
Console.Out.Write("demande (fin pour arrêter) : ")
demande = Console.In.ReadLine()
' запит надсилається на сервер
OUT.WriteLine(demande)
' зчитується відповідь від сервера
réponse = [IN].ReadLine()
' обробка відповіді
Console.Out.WriteLine(("Réponse : " + réponse))
' завершено?
If demande.Trim().ToLower() = "fin" Then
Exit While
End If
End While
' закінчено
client.Close()
Catch ex As Exception
' обробка винятку
erreur(ex.Message, 3)
End Try
End Sub
' виведення повідомлень про помилки
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
Структура цього клієнта відповідає загальній архітектурі клієнтів tcp.Voici. Результати отримано в такій конфігурації:
- сервер запущено на порту 100 у вікні DOS
- на тій самій машині запущено два клієнти у двох інших вікнах DOS
У вікні клієнта 1 отримано такі результати:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne1
Réponse : [ligne1]
demande (fin pour arrêter) : ligne1B
Réponse : [ligne1B]
demande (fin pour arrêter) : ligne1C
Réponse : [ligne1C]
demande (fin pour arrêter) : fin
Réponse : [fin]
У файлі клієнта 2:
dos>clientEcho localhost 100
demande (fin pour arrêter) : ligne2A
Réponse : [ligne2A]
demande (fin pour arrêter) : ligne2B
Réponse : [ligne2B]
demande (fin pour arrêter) : fin
Réponse : [fin]
У файлі сервера:
dos>serveurEcho 100
Serveur d'écho lancé sur le port 100
0.0.0.0:100
Début de service au client 1
Client 1 : ligne1
Début de service au client 2
Client 2 : ligne2A
Client 2 : ligne2B
Client 1 : ligne1B
Client 1 : ligne1C
Client 2 : fin
Fin de service au client 2
Client 1 : fin
Fin de service au client 1
^C
Слід зазначити, що сервер зміг одночасно обслуговувати двох клієнтів.
9.4.3. Типовий клієнт TCP
Багато служб, створених на початку розвитку Інтернету, працюють за моделлю сервера-відлуння, розглянутого раніше: обмін даними між клієнтом і сервером відбувається шляхом обміну рядками тексту. Ми напишемо загальний TCP-клієнт, який запускатиметься таким чином: cltgen сервер порт
Цей клієнт TCP підключиться до порту port сервера serveur. Після цього він створить два потоки:
- потік, відповідальний за зчитування команд, введених з клавіатури, та їх надсилання на сервер
- потік, відповідальний за зчитування відповідей сервера та їх відображення на екрані
Чому потрібні два потоки, якщо в попередньому додатку такої потреби не виникало? У тому додатку протокол діалогу був відомий: клієнт надсилав один рядок, а сервер відповідав одним рядком. Кожен сервіс має свій особливий протокол, і також трапляються такі ситуації:
- клієнт повинен надіслати кілька рядків тексту, перш ніж отримати відповідь
- відповідь сервера може містити кілька рядків тексту
Тому цикл «відправлення одного рядка на сервер — отримання одного рядка від сервера» не завжди підходить. Тому ми створимо два окремі цикли:
- цикл зчитування команд, введених з клавіатури для відправки на сервер. Користувач позначатиме кінець команд ключовим словом fin.
- цикл прийому та відображення відповідей від сервера. Це буде нескінченний цикл, який перериватиметься лише у разі закриття мережевого потоку сервером або користувачем за допомогою клавіатури, який введе команду fin.
Щоб ці два цикли працювали незалежно один від одного, нам потрібні два незалежні потоки. Розглянемо приклад виконання, у якому наш загальний TCP-клієнт підключається до сервісу SMTP (SendMail Transfer Protocol). Цей сервіс відповідає за доставку електронної пошти адресатам. Він працює на порту 25 і використовує протокол взаємодії типу обміну текстовими рядками.
dos>cltgen istia.univ-angers.fr 25
Commandes :
<-- 220 istia.univ-angers.fr ESMTP Sendmail 8.11.6/8.9.3; Mon, 13 May 2002 08:37:26 +0200
help
<-- 502 5.3.0 Sendmail 8.11.6 -- HELP not implemented
mail from: machin@univ-angers.fr
<-- 250 2.1.0 machin@univ-angers.fr... Sender ok
rcpt to: serge.tahe@istia.univ-angers.fr
<-- 250 2.1.5 serge.tahe@istia.univ-angers.fr... Recipient ok
data
<-- 354 Enter mail, end with "." on a line by itself
Subject: test
ligne1
ligne2
ligne3
.
<-- 250 2.0.0 g4D6bks25951 Message accepted for delivery
quit
<-- 221 2.0.0 istia.univ-angers.fr closing connection
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Проаналізуємо ці обміни даними між клієнтом і сервером:
- сервіс SMTP надсилає привітальне повідомлення, коли клієнт підключається до нього:
- деякі сервіси мають команду help, яка надає інформацію про команди, що можуть використовуватися з сервісом. У цьому випадку це не так. Команди SMTP, що використовуються в прикладі, такі:
- mail from: expéditeur — для вказівки електронної адреси відправника повідомлення
- rcpt to: destinataire — для вказівки електронної адреси одержувача повідомлення. Якщо одержувачів декілька, команду rcpt to: повторюють стільки разів, скільки потрібно, для кожного з них.
- data, що повідомляє серверу SMTP про те, що ми збираємося надіслати повідомлення. Як зазначено у відповіді сервера, це послідовність рядків, що закінчується рядком, який містить лише один символ — крапку. Повідомлення може мати заголовки, відокремлені від тіла повідомлення порожнім рядком. У нашому прикладі ми вказали тему за допомогою ключового слова Subject:
- після відправлення повідомлення можна повідомити серверу про завершення роботи за допомогою команди quit. Тоді сервер закриває з’єднання réseau.Le; потік читання може виявити цю подію та зупинитися.
- Потім користувач вводить «fin» з клавіатури, щоб також зупинити потік читання команд, введених з клавіатури.
Якщо перевірити отриману пошту, ми побачимо таке (Outlook):

Слід зауважити, що служба SMTP не може визначити, чи є відправник дійсним. Тому ніколи не можна довіряти полю from у повідомленні. У цьому випадку відправник machin@univ-angers.fr не існував. Цей загальний TCP-клієнт дозволяє нам виявити протокол взаємодії інтернет-сервісів і на його основі створити спеціалізовані класи для клієнтів цих сервісів. Давайте визначимо протокол взаємодії служби POP (Post Office Protocol), який дозволяє отримувати електронні листи, що зберігаються на сервері. Він працює на порту 110.
dos>cltgen istia.univ-angers.fr 110
Commandes :
<-- +OK Qpopper (version 4.0.3) at istia.univ-angers.fr starting.
help
<-- -ERR Unknown command: "help".
user st
<-- +OK Password required for st.
pass monpassword
<-- +OK st has 157 visible messages (0 hidden) in 11755927 octets.
list
<-- +OK 157 visible messages (11755927 octets)
<-- 1 892847
<-- 2 171661
...
<-- 156 2843
<-- 157 2796
<-- .
retr 157
<-- +OK 2796 octets
<-- Received: from lagaffe.univ-angers.fr (lagaffe.univ-angers.fr [193.49.144.1])
<-- by istia.univ-angers.fr (8.11.6/8.9.3) with ESMTP id g4D6wZs26600;
<-- Mon, 13 May 2002 08:58:35 +0200
<-- Received: from jaume ([193.49.146.242])
<-- by lagaffe.univ-angers.fr (8.11.1/8.11.2/GeO20000215) with SMTP id g4D6wSd37691;
<-- Mon, 13 May 2002 08:58:28 +0200 (CEST)
...
<-- ------------------------------------------------------------------------
<-- NOC-RENATER2 Tl. : 0800 77 47 95
<-- Fax : (+33) 01 40 78 64 00 , Email : noc-r2@cssi.renater.fr
<-- ------------------------------------------------------------------------
<--
<-- .
quit
<-- +OK Pop server at istia.univ-angers.fr signing off.
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Основні команди такі:
- user login, де вказується логін на сервері, на якому зберігаються наші електронні листи
- pass password, де вказується пароль, пов'язаний із попереднім логіном
- list — для отримання списку повідомлень у форматі «номер, розмір у байтах»
- retr i — для читання повідомлення № i
- quit — для завершення діалогу.
Тепер розглянемо протокол взаємодії між клієнтом і веб-сервером, який зазвичай працює на порту 80:
dos>cltgen istia.univ-angers.fr 80
Commandes :
GET /index.html HTTP/1.0
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
<--
<-- <head>
<-- <meta http-equiv="Content-Type"
<-- content="text/html; charset=iso-8859-1">
<-- <meta name="GENERATOR" content="Microsoft FrontPage Express 2.0">
<-- <title>Bienvenue a l'ISTIA - Universite d'Angers</title>
<-- </head>
....
<-- face="Verdana"> - Dernire mise jour le <b>10 janvier 2002</b></font></p>
<-- </body>
<-- </html>
<--
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Веб-клієнт надсилає свої команди на сервер за такою схемою:
Лише після отримання порожнього рядка веб-сервер надає відповідь. У цьому прикладі ми використали лише одну команду:
яка запитує у сервера файл URL /index.html та вказує, що працює з протоколом HTTP версії 1.0. Найновіша версія цього протоколу — 1.1. Приклад показує, що сервер відповів, надіславши вміст файлу index.html, а потім закрив з’єднання, оскільки видно, що потік читання відповідей завершився. Перед надсиланням вмісту файлу index.html веб-сервер надіслав серію заголовків, що закінчувалася порожнім рядком:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Рядок <html> є першим рядком файлу /index.html. Вищезазначене називається заголовками HTTP (HyperText Transfer Protocol). Ми не будемо тут детально розглядати ці заголовки, але нагадаємо, що наш універсальний клієнт надає до них доступ, що може бути корисним для їх розуміння. Наприклад, перший рядок:
вказує, що веб-сервер, до якого звернулися, розуміє протокол HTTP/1.1 і що він успішно знайшов запитуваний файл (200 OK), де 200 — це код відповіді HTTP. Рядки
повідомляють клієнту, що він отримає 11251 байт, які представляють текст HTML (HyperText Markup Language), і що після завершення передачі з'єднання буде закрито. Отже, ми маємо дуже зручний TCP-клієнт. Насправді цей клієнт уже існує на комп’ютерах під назвою telnet, але було цікаво написати його самостійно. Програма загального TCP-клієнта виглядає так:
' простори імен
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
' клас
Public Class clientTcpGénérique
' отримує як параметр характеристики служби у вигляді
' сервер, порт
' підключається до служби
' створює потік для зчитування команд, введених з клавіатури
' ці команди будуть надіслані на сервер
' створює потік для зчитування відповідей від сервера
' вони з’являться на екрані
' усе завершується командою «fin», введеною з клавіатури
Public Shared Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg serveur port"
' кількість аргументів
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' записуємо ім’я сервера
Dim serveur As String = args(0)
' порт повинен бути цілим числом >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(1))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
Dim client As TcpClient = Nothing
' можуть виникнути проблеми
Try
' здійснюється підключення до служби
client = New TcpClient(serveur, port)
Catch ex As Exception
' помилка
Console.Error.WriteLine(("Impossible de se connecter au service (" & serveur & "," & port & "), erreur : " & ex.Message))
' кінець
Return
End Try
' створюються потоки читання/запису
Dim thReceive As New Thread(New ThreadStart(AddressOf New clientReceive(client).Run))
Dim thSend As New Thread(New ThreadStart(AddressOf New clientSend(client).Run))
' запускається виконання обох потоків
thSend.Start()
thReceive.Start()
' завершення роботи головного потоку
Return
End Sub
' виведення повідомлень про помилки
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
Public Class clientSend
' клас, відповідальний за зчитування команд, введених з клавіатури
' та надсилання їх на сервер через TCP-клієнт, переданий у конструктор
Private client As TcpClient ' le client tcp
' конструктор
Public Sub New(ByVal client As TcpClient)
' зазначаємо TCP-клієнт
Me.client = client
End Sub
' метод Run потоку
Public Sub Run()
' локальні дані
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim commande As String = Nothing ' commande lue au clavier
' обробка помилок
Try
' створення потоку запису в мережу
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' цикл введення-відправлення команд
Console.Out.WriteLine("Commandes : ")
While True
' читання команди, введеної з клавіатури
commande = Console.In.ReadLine().Trim()
' завершено?
If commande.ToLower() = "fin" Then
Exit While
End If
' відправлення команди на сервер
OUT.WriteLine(commande)
End While
Catch ex As Exception
' помилка
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' кінець — закриваємо потоки
Try
OUT.Close()
client.Close()
Catch
End Try
' повідомлення про завершення потоку
Console.Out.WriteLine("[fin du thread d'envoi des commandes au serveur]")
End Sub
End Class
Public Class clientReceive
' клас, відповідальний за читання рядків тексту, призначених для
' TCP-клієнта, переданого конструктору
Private client As TcpClient ' le client tcp
' конструктора
Public Sub New(ByVal client As TcpClient)
' фіксується TCP-клієнт
Me.client = client
End Sub
'конструктор
' метод Run потоку
Public Sub Run()
' локальні дані
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' обробка помилок
Try
' створення потоку читання з мережі
[IN] = New StreamReader(client.GetStream())
' цикл читання рядків тексту з потоку IN
While True
' читання мережевого потоку
réponse = [IN].ReadLine()
' потік закритий?
If réponse Is Nothing Then
Exit While
End If
' відображення
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' помилка
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' кінець — закриття потоків
Try
[IN].Close()
client.Close()
Catch
End Try
' повідомлення про завершення потоку
Console.Out.WriteLine("[fin du thread de lecture des réponses du serveur]")
End Sub
End Class
9.4.4. Універсальний TCP-сервер
Тепер ми розглянемо сервер,
- , який виводить на екран команди, надіслані його клієнтами
- і надсилає їм у відповідь рядки тексту, введені користувачем з клавіатури. Отже, саме останній виконує роль сервера.
Програму запускають командою: srvgen portEcoute, де portEcoute — це порт, до якого повинні підключатися клієнти. Обслуговування клієнтів забезпечуватимуть два потоки:
- потік, який присвячений виключно зчитуванню рядків тексту, надісланих клієнтом
- потік, який присвячений виключно зчитуванню відповідей, введених користувачем з клавіатури. Цей потік за допомогою команди fin повідомить про завершення з’єднання з клієнтом.
Сервер створює два потоки на кожного клієнта. Якщо є n клієнтів, одночасно буде активних 2n потоків. Сам сервер ніколи не зупиняється, за винятком випадків, коли користувач натискає комбінацію клавіш Ctrl-C. Розглянемо кілька прикладів.
Сервер запущено на порту 100, і для зв’язку з ним використовується загальний клієнт. Вікно клієнта виглядає так:
dos>cltgen localhost 100
Commandes :
commande 1 du client 1
<-- réponse 1 au client 1
commande 2 du client 1
<-- réponse 2 au client 1
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Рядки, що починаються з <--, — це дані, надіслані сервером клієнту, інші — дані, надіслані клієнтом серверу. Вікно сервера виглядає так:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Рядки, що починаються з <--, — це ті, що надсилаються клієнтом на сервер. Рядки N: — це рядки, що надсилаються сервером клієнту № N. Вищезазначений сервер все ще активний, хоча клієнт 1 завершив роботу. Запускаємо другого клієнта для того самого сервера:
dos>cltgen localhost 100
Commandes :
commande 3 du client 2
<-- réponse 3 au client 2
fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du thread de lecture des réponses du serveur]
[fin du thread d'envoi des commandes au serveur]
Вікно сервера виглядає так:
dos>srvgen 100
Serveur générique lancé sur le port 100
Thread de lecture des réponses du serveur au client 1 lancé
1 : Thread de lecture des demandes du client 1 lancé
<-- commande 1 du client 1
réponse 1 au client 1
1 : <-- commande 2 du client 1
réponse 2 au client 1
1 : [fin du Thread de lecture des demandes du client 1]
fin
[fin du Thread de lecture des réponses du serveur au client 1]
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- commande 3 du client 2
réponse 3 au client 2
2 : [fin du Thread de lecture des demandes du client 2]
fin
[fin du Thread de lecture des réponses du serveur au client 2]
^C
Тепер змоделюємо веб-сервер, запустивши наш універсальний сервер на порту 88:
Тепер відкриємо браузер і введемо адресу http://localhost:88/exemple.html. Браузер підключиться до порту 88 машини localhost, а потім запросить сторінку /exemple.html:

Тепер поглянемо на вікно нашого сервера:
dos>srvgen 88
Serveur générique lancé sur le port 88
Thread de lecture des réponses du serveur au client 2 lancé
2 : Thread de lecture des demandes du client 2 lancé
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Таким чином ми виявляємо заголовки HTTP, надіслані браузером. Це дозволяє нам поступово розібратися з протоколом HTTP. У попередньому прикладі ми створили веб-клієнт, який надсилав лише одну команду GET. Цього було достатньо. Тут ми бачимо, що браузер надсилає серверу й іншу інформацію. Її мета — повідомити серверу, з яким типом клієнта він має справу. Також бачимо, що заголовки HTTP закінчуються порожнім рядком. Складімо відповідь для нашого клієнта. Користувач за клавіатурою тут є справжнім сервером і може скласти відповідь вручну. Згадаймо відповідь, яку надав веб-сервер у попередньому прикладі:
<-- HTTP/1.1 200 OK
<-- Date: Mon, 13 May 2002 07:30:58 GMT
<-- Server: Apache/1.3.12 (Unix) (Red Hat/Linux) PHP/3.0.15 mod_perl/1.21
<-- Last-Modified: Wed, 06 Feb 2002 09:00:58 GMT
<-- ETag: "23432-2bf3-3c60f0ca"
<-- Accept-Ranges: bytes
<-- Content-Length: 11251
<-- Connection: close
<-- Content-Type: text/html
<--
<-- <html>
Спробуємо дати аналогічну відповідь:
...
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
2 : HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
2 : fin
L'erreur suivante s'est produite : Impossible de lire les données de la connexion de transport.
[fin du Thread de lecture des demandes du client 2]
[fin du Thread de lecture des réponses du serveur au client 2]
Рядки, що починаються з 2: надсилаються сервером клієнту № 2. Команда fin завершує з’єднання сервера з клієнтом. У нашій відповіді ми обмежилися такими заголовками HTTP:
HTTP/1.1 200 OK
2 : Server: serveur tcp generique
2 : Connection: close
2 : Content-Type: text/html
2 :
Ми не вказуємо розмір файлу, який збираємося надіслати (Content-Length), а лише повідомляємо, що після його надсилання ми закриємо з’єднання (Connection: close). Цього достатньо для браузера. Побачивши, що з’єднання закрито, браузер зрозуміє, що відповідь сервера завершена, і відобразить сторінку HTML, яку йому надіслали. Остання має такий вигляд:
2 : <html>
2 : <head><title>Serveur generique</title></head>
2 : <body>
2 : <center>
2 : <h2>Reponse du serveur generique</h2>
2 : </center>
2 : </body>
2 : </html>
Потім користувач закриває з'єднання з клієнтом, ввівши команду fin. Після цього браузер розуміє, що відповідь сервера завершена, і може її відобразити:

Якщо ввести команду Affichage/Source, щоб переглянути, що отримав браузер, отримаємо:

тобто саме те, що було надіслано з загального сервера. Код загального сервера TCP такий:
' простори імен
Imports System
Imports System.Net
Imports System.Net.Sockets
Imports System.IO
Imports System.Threading
Imports Microsoft.VisualBasic
Public Class serveurTcpGénérique
' головна програма
Public Shared Sub Main(ByVal args() As String)
' отримує порт для прийому запитів клієнтів
' створює потік для зчитування запитів клієнта
' вони будуть відображатися на екрані
' створює потік для зчитування команд, введених з клавіатури
' вони будуть надіслані клієнту у відповідь
' усе завершується командою «fin», введеною з клавіатури
Const syntaxe As String = "Syntaxe : pg port"
' чи є аргумент
If args.Length <> 1 Then
erreur(syntaxe, 1)
End If
' цей аргумент має бути цілим числом >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' створюємо службу прослуховування
Dim ecoute As TcpListener = Nothing
Dim nbClients As Integer = 0 ' nbre de clients traités
Try
' створюється служба
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), port)
' запускаємо його
ecoute.Start()
' моніторинг
Console.Out.WriteLine(("Serveur générique lancé sur le port " & port))
' цикл обслуговування клієнтів
Dim client As TcpClient = Nothing
While True ' boucle infinie - sera arrêtée par Ctrl-C
' очікування клієнта
client = ecoute.AcceptTcpClient()
' сервіс забезпечується окремими потоками
nbClients += 1
' потік читання запитів клієнтів
Dim thReceive As New Thread(New ThreadStart(AddressOf New serveurReceive(client, nbClients).Run))
' потік читання відповідей, введених користувачем з клавіатури
Dim thSend As New Thread(New ThreadStart(AddressOf New serveurSend(client, nbClients).Run))
' запускається виконання обох потоків
thSend.Start()
thReceive.Start()
End While
' повертаємося до очікування запитів
Catch ex As Exception
' повідомляється про помилку
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' виведення повідомлення про помилку
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення повідомлення про помилку
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
Public Class serveurSend
' клас, відповідальний за зчитування відповідей, введених з клавіатури
' та надсилання їх клієнту через TCP-клієнт, переданий у конструктор
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' конструктор
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' зазначаємо TCP-клієнт
Me.client = client
' та його номер
Me.numClient = numClient
End Sub
' метод Run потоку
Public Sub Run()
' локальні дані
Dim OUT As StreamWriter = Nothing ' flux d'écriture réseau
Dim réponse As String = Nothing ' réponse lue au clavier
' відстеження
Console.Out.WriteLine(("Thread de lecture des réponses du serveur au client " & numClient & " lancé"))
' обробка помилок
Try
' створення потоку запису в мережі
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' цикл введення-відправлення команд
While True
' ідентифікація клієнта
Console.Out.Write((numClient & " : "))
' прочитати відповідь, введену з клавіатури
réponse = Console.In.ReadLine().Trim()
' закінчено?
If réponse.ToLower() = "fin" Then
Exit While
End If
' відправлення відповіді на сервер
OUT.WriteLine(réponse)
End While
' наступна відповідь
Catch ex As Exception
' помилка
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' кінець — закриття потоків
Try
OUT.Close()
client.Close()
Catch
End Try
' повідомлення про завершення потоку
Console.Out.WriteLine(("[fin du Thread de lecture des réponses du serveur au client " & numClient & "]"))
End Sub
End Class
Public Class serveurReceive
' клас, відповідальний за зчитування рядків тексту, надісланих на сервер
' через TCP-клієнт, переданий у конструктор
Private client As TcpClient ' le client tcp
Private numClient As Integer ' n° de client
' конструктор
Public Sub New(ByVal client As TcpClient, ByVal numClient As Integer)
' зафіксовано TCP-клієнт
Me.client = client
' та його номер
Me.numClient = numClient
End Sub
' метод Run потоку
Public Sub Run()
' локальні дані
Dim [IN] As StreamReader = Nothing ' flux lecture réseau
Dim réponse As String = Nothing ' réponse serveur
' відстеження
Console.Out.WriteLine(("Thread de lecture des demandes du client " & numClient & " lancé"))
' обробка помилок
Try
' створення потоку читання з мережі
[IN] = New StreamReader(client.GetStream())
' цикл читання рядків тексту з потоку IN
While True
' читання мережевого потоку
réponse = [IN].ReadLine()
' потік закритий?
If réponse Is Nothing Then
Exit While
End If
' відображення
Console.Out.WriteLine(("<-- " + réponse))
End While
Catch ex As Exception
' помилка
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
' кінець — закриття потоків
Try
[IN].Close()
client.Close()
Catch
End Try
' повідомлення про завершення потоку
Console.Out.WriteLine(("[fin du Thread de lecture des demandes du client " & numClient & "]"))
End Sub
End Class
9.4.5. Веб-клієнт
У попередньому прикладі ми бачили деякі заголовки HTTP, які надсилав браузер:
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/msword, */*
<-- Accept-Language: fr
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.0.3705; .NET CLR 1.0.2
914)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--
Ми напишемо веб-клієнт, якому передаватиметься як параметр URL і який відображатиме на екрані текст, надісланий сервером. Припустимо, що сервер підтримує протокол HTTP 1.1. З наведених заголовків ми використовуватимемо лише такі:
- перший заголовок вказує, яку сторінку ми хочемо
- другий — до якого сервера ми звертаємося
- третій — що ми хочемо, щоб сервер закрив з’єднання після надання відповіді.
Якщо у наведеному вище прикладі замінити GET на HEAD, сервер надішле нам лише заголовки HTTP, а не сторінку HTML.
Наш веб-клієнт буде викликатися наступним чином: clientweb URL cmd, де URL — цеURL, а cmd — одним із двох ключових слів GET або HEAD, що вказує, чи потрібні лише заголовки (HEAD), чи також вміст сторінки (GET). Розглянемо перший приклад. Запускаємо сервер IIS, а потім веб-клієнт на тій самій машині:
dos>clientweb http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:23:37 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=HMFNCCMDECBJJBPPBHAOAJNP; path=/
Cache-control: private
Відповідь
означає, що запитувана сторінка змінила місце розташування (тобто з URL). Нова адреса URL вказана у заголовку Location:
Якщо ми використаємо GET замість HEAD у виклику веб-клієнта:
dos>clientweb http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 09:33:36 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=IMFNCCMDAKPNNGMGMFIHENFE; path=/
Cache-control: private
<head><title>L'objet a changé d'emplacement</title></head>
<body><h1>L'objet a changé d'emplacement</h1>Cet objet peut être trouvé <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
Ми отримуємо той самий результат, що й із HEAD, а також основний текст сторінки HTML. Програма виглядає так:
' простори імен
Imports System
Imports System.Net.Sockets
Imports System.IO
Public Class clientWeb1
' запит на URL
' виводить її вміст на екран
Public Shared Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg URI GET/HEAD"
' кількість аргументів
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' фіксується запитуваний URI
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' перевірка дійсності URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI неправильний
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
' перевірка команди
If commande <> "GET" And commande <> "HEAD" Then
' неправильне замовлення
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' можна працювати
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Try
' здійснюється підключення до сервера
client = New TcpClient(uri.Host, uri.Port)
' створюються потоки вводу-виводу клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' запит на URL — надсилання заголовків HTTP
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' зчитується відповідь
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' обробляємо відповідь
Console.Out.WriteLine(réponse)
' читається відповідь
réponse = [IN].ReadLine()
End While
' процес завершено
client.Close()
Catch e As Exception
' обробка винятку
erreur(e.Message, 4)
End Try
End Sub
' виведення повідомлень про помилки
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
Єдиною новиною в цій програмі є використання класу Uri. Програма отримує URL (Uniform Resource Locator) або URI (Uniform Resource Identifier) у вигляді http://serveur:port/cheminPageHTML?param1=val1;param2=val2;.... Клас Uri дозволяє розкласти рядок URL на окремі елементи. Об’єкт Uri створюється на основі рядка URIstring, отриманого як параметр:
' перевірка дійсності URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI неправильний
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try
Якщо рядок URI, отриманий як параметр, не є дійсним URI (відсутність протоколу, сервера тощо), генерується виняток. Це дозволяє перевірити дійсність отриманого параметра. Після побудови об’єкта URI ми отримуємо доступ до різних елементів цього URI. Отже, якщо об’єкт uri з попереднього коду було побудовано на основі рядка http://serveur:port/cheminPageHTML?param1=val1;param2=val2;..., ми отримаємо:
uri.Host=serveur, uri.Port=port, uri.Path=cheminPageHTML, uri.Query=param1=val1;param2=val2;..., uri.pathAndQuery= cheminPageHTML?param1=val1;param2=val2;..., uri.Scheme=http.
9.4.6. Веб-клієнт, що підтримує перенаправлення
Попередній веб-клієнт не підтримує можливе перенаправлення з URL, яке він сам запросив. Наступний клієнт підтримує його.
- він зчитує перший рядок заголовків HTTP, надісланих сервером, щоб перевірити, чи міститься там рядок «302 Object moved», який вказує на перенаправлення
- він зчитує наступні заголовки. Якщо перенаправлення є, він шукає рядок «Location: url», який вказує нову адресу URL запитуваної сторінки, і записує цю адресу URL.
- він відображає решту відповіді сервера. Якщо є перенаправлення, кроки 1–3 повторюються з новим URL. Програма не допускає більше одного перенаправлення. Це обмеження визначається константою, яку можна змінити.
Ось приклад:
dos>clientweb2 http://localhost GET
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.0
Date: Mon, 13 May 2002 11:38:55 GMT
Connection: close
Location: /IISSamples/Default/welcome.htm
Content-Length: 189
Content-Type: text/html
Set-Cookie: ASPSESSIONIDGQQQGUUY=PDGNCCMDNCAOFDMPHCJNPBAI; path=/
Cache-control: private
<head><title>L'objet a chang d'emplacement</title></head>
<body><h1>L'objet a chang d'emplacement</h1>Cet objet peut tre trouv <a HREF="/IISSamples/Default/we
lcome.htm">ici</a>.</body>
<--Redirection vers l'URL http://localhost:80/IISSamples/Default/welcome.htm-->
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Connection: close
Date: Mon, 13 May 2002 11:38:55 GMT
Content-Type: text/html
Accept-Ranges: bytes
Last-Modified: Mon, 16 Feb 1998 21:16:22 GMT
ETag: "0174e21203bbd1:978"
Content-Length: 4781
<html>
<head>
<title>Bienvenue dans le Serveur Web personnel</title>
</head>
....
</body>
</html>
Програма така:
' простори імен
Imports System
Imports System.Net.Sockets
Imports System.IO
Imports System.Text.RegularExpressions
Imports Microsoft.VisualBasic
' клас веб-клієнта
Public Class clientWeb
' запитує URL і відображає його вміст на екрані
Public Shared Sub Main(ByVal args() As String)
' синтаксис
Const syntaxe As String = "pg URI GET/HEAD"
' кількість аргументів
If args.Length <> 2 Then
erreur(syntaxe, 1)
End If
' записується запитуваний URI
Dim URIstring As String = args(0)
Dim commande As String = args(1).ToUpper()
' перевірка дійсності URI
Dim uri As Uri = Nothing
Try
uri = New Uri(URIstring)
Catch ex As Exception
' URI неправильний
erreur("L'erreur suivante s'est produite : " + ex.Message, 2)
End Try 'catch
' перевірка замовлення
If commande <> "GET" And commande <> "HEAD" Then
' неправильне замовлення
erreur("Le second paramètre doit être GET ou HEAD", 3)
End If
' можна працювати
Dim client As TcpClient = Nothing ' le client
Dim [IN] As StreamReader = Nothing ' le flux de lecture du client
Dim OUT As StreamWriter = Nothing ' le flux d'écriture du client
Dim réponse As String = Nothing ' réponse du serveur
Const nbRedirsMax As Integer = 1 ' pas plus d'une redirection acceptée
Dim nbRedirs As Integer = 0 ' nombre de redirections en cours
Dim premièreLigne As String ' 1ère ligne de la réponse
Dim redir As Boolean = False ' indique s'il y a redirection ou non
Dim locationString As String = "" ' la chaîne URI d'une éventuelle redirection
' регулярний вираз для пошуку URL перенаправлення
Dim location As New Regex("^Location: (.+?)$") '
' обробка помилок
Try
' можна мати кілька запитів на URL, якщо є перенаправлення
While nbRedirs <= nbRedirsMax
' здійснюється підключення до сервера
client = New TcpClient(uri.Host, uri.Port)
' створюються потоки вхідних та вихідних даних клієнта TCP
[IN] = New StreamReader(client.GetStream())
OUT = New StreamWriter(client.GetStream())
OUT.AutoFlush = True
' надсилаються заголовки HTTP для запиту URL
OUT.WriteLine((commande + " " + uri.PathAndQuery + " HTTP/1.1"))
OUT.WriteLine(("Host: " + uri.Host + ":" & uri.Port))
OUT.WriteLine("Connection: close")
OUT.WriteLine()
' зчитується перший рядок відповіді
premièreLigne = [IN].ReadLine()
' відображення на екрані
Console.Out.WriteLine(premièreLigne)
' перенаправлення?
If Regex.IsMatch(premièreLigne, "302 Object moved$") Then
' перенаправлення є
redir = True
nbRedirs += 1
End If
' наступні заголовки HTTP, доки не буде знайдено порожній рядок, що сигналізує про кінець заголовків
Dim locationFound As Boolean = False
réponse = [IN].ReadLine()
While réponse <> ""
' виводиться відповідь
Console.Out.WriteLine(réponse)
' якщо є перенаправлення, шукаємо заголовок Location
If redir And Not locationFound Then
' порівняти рядок із реляційним виразом location
Dim résultat As Match = location.Match(réponse)
If résultat.Success Then
' якщо знайдено, записується URL перенаправлення
locationString = résultat.Groups(1).Value
' фіксується, що знайдено
locationFound = True
End If
End If
' наступний рядок
réponse = [IN].ReadLine()
End While
' наступні рядки відповіді
Console.Out.WriteLine(réponse)
réponse = [IN].ReadLine()
While Not (réponse Is Nothing)
' вивести відповідь
Console.Out.WriteLine(réponse)
' наступний рядок
réponse = [IN].ReadLine()
End While
' з'єднання закрито
client.Close()
' чи все закінчено?
If Not locationFound Or nbRedirs > nbRedirsMax Then
Exit While
End If
' необхідно виконати перенаправлення — формуємо новий URI
URIstring = uri.Scheme + "://" & uri.Host & ":" & uri.Port & locationString
uri = New Uri(URIstring)
' відстеження
Console.Out.WriteLine((ControlChars.Lf + "<--Redirection vers l'URL " + URIstring + "-->" + ControlChars.Lf))
End While
Catch e As Exception
' обробка винятку
erreur(e.Message, 4)
End Try
End Sub
' виведення повідомлень про помилки
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
9.4.7. Сервер розрахунку податків
Ми повертаємося до вправи IMPOTS, яку вже розглядали в різних варіантах. Нагадаємо останню версію. Було створено клас «податок». Його атрибутами є три масиви чисел:
Public Class impôt
' дані, необхідні для розрахунку податку
' походять із зовнішнього джерела
Private limites(), coeffR(), coeffN() as double
Клас має два конструктори:
- конструктор, якому передаються три масиви даних, необхідні для розрахунку податку
// виробник 1
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' ініціалізує три масиви меж, coeffR, coeffN, на основі
' параметрів, переданих конструктору
- конструктор, якому передається ім'я DSN з бази даних ODBC
' конструктор 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' ініціалізує три масиви меж, coeffR, coeffN, на основі
' вмісту таблиці Timpots у базі даних ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN — це три стовпці цієї таблиці
' може викликати виняток
Була написана програма для тестування:
dos>vbc /r:impots.dll testimpots.vb
dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
Тут тестова програма та об’єкт impôt знаходилися на одній машині. Ми пропонуємо розмістити тестову програму та об’єкт impôt на різних машинах. Ми отримаємо клієнт-серверну програму, в якій віддалений об’єкт impôt буде виконувати роль сервера. Новий клас називається ServeurImpots і є похідним від класу impôt:
Public Class ServeurImpots
Inherits impôt
' атрибути
Private portEcoute As Integer ' le port d'écoute des demandes clients
Private actif As Boolean ' état du serveur
' конструктор
Public Sub New(ByVal portEcoute As Integer, ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
MyBase.New(DSNimpots, Timpots, colLimites, colCoeffR, colCoeffN)
' відзначаємо порт прослуховування
Me.portEcoute = portEcoute
' наразі неактивний
actif = False
' створює та запускає потік для зчитування команд, введених з клавіатури
' сервер буде керуватися за допомогою цих команд
Dim threadLecture As Thread = New Thread(New ThreadStart(AddressOf admin))
threadLecture.Start()
End Sub
Єдиним новим параметром у конструкторі є порт, на якому сервер очікує запитів від клієнтів. Інші параметри передаються безпосередньо базовому класу impôt. Сервер податків керується командами, що вводяться з клавіатури. Тому ми створюємо потік для зчитування цих команд. Можливі два варіанти: start для запуску служби, stop для її остаточного зупинення. Метод admin, який обробляє ці команди, виглядає наступним чином:
Public Sub admin()
' зчитує команди адміністрування сервера, введені з клавіатури
' у нескінченному циклі
Dim commande As String = Nothing
While True
' запрошує
Console.Out.Write("Serveur d'impôts>")
' читання команди
commande = Console.In.ReadLine().Trim().ToLower()
' виконання команди
If commande = "start" Then
' активний?
If actif Then
'помилка
Console.Out.WriteLine("Le serveur est déjà actif")
Else
' запускається служба прослуховування
Dim threadEcoute As Thread = New Thread(New ThreadStart(AddressOf ecoute))
threadEcoute.Start()
End If
Else
If commande = "stop" Then
' завершення всіх потоків виконання
Environment.Exit(0)
Else
' помилка
Console.Out.WriteLine("Commande incorrecte. Utilisez (start,stop)")
End If
End If
End While
End Sub
Якщо команда, введена з клавіатури, — start, запускається потік, що очікує на запити клієнтів. Якщо введена команда — stop, усі потоки зупиняються. Потік очікування виконує метод ecoute:
Public Sub ecoute()
' потік, що приймає запити клієнтів
' створено службу прослуховування
Dim ecoute As TcpListener = Nothing
Try
' створення служби
ecoute = New TcpListener(IPAddress.Parse("127.0.0.1"), portEcoute)
' запуск служби
ecoute.Start()
' моніторинг
Console.Out.WriteLine(("Serveur d'écho lancé sur le port " & portEcoute))
' цикл роботи сервісу
Dim liaisonClient As TcpClient = Nothing
While True ' boucle infinie
' очікування клієнта
liaisonClient = ecoute.AcceptTcpClient()
' сервіс забезпечується іншим завданням
Dim threadClient As Thread = New Thread(New ThreadStart(AddressOf New traiteClientImpots(liaisonClient, Me).Run))
threadClient.Start()
End While
' повернення до очікування запитів
Catch ex As Exception
' повідомлення про помилку
erreur("L'erreur suivante s'est produite : " + ex.Message, 3)
End Try
End Sub
' відображення помилок
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення повідомлення про помилку
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
Тут використовується звичайний TCP-сервер, що слухає на порту portEcoute. Запити клієнтів обробляються методом Run об’єкта, якому передаються два параметри:
- об’єкт TcpClient, який дозволить зв’язатися з клієнтом
- об’єкт impôt this, який надає доступ до методу this.calculer для розрахунку податку.
' -------------------------------------------------------
' забезпечує обслуговування клієнта податкового сервера
Public Class traiteClientImpots
Private liaisonClient As TcpClient ' liaison avec le client
Private [IN] As StreamReader ' flux d'entrée
Private OUT As StreamWriter ' flux de sortie
Private objImpôt As impôt ' objet Impôt
' конструктор
Public Sub New(ByVal liaisonClient As TcpClient, ByVal objImpôt As impôt)
Me.liaisonClient = liaisonClient
Me.objImpôt = objImpôt
End Sub
Метод Run обробляє запити клієнтів. Вони можуть мати дві форми:
- розрахунок для одружених (так/ні) nbEnfants salaireAnnuel
- фінансові розрахунки
Форма 1 дозволяє розрахувати податок, форма 2 завершує з’єднання «клієнт-сервер».
' метод Run
Public Sub Run()
' надає послугу клієнту
Try
' вхідний потік
[IN] = New StreamReader(liaisonClient.GetStream())
' вихідний потік
OUT = New StreamWriter(liaisonClient.GetStream())
OUT.AutoFlush = True
' надсилання привітального повідомлення клієнту
OUT.WriteLine("Bienvenue sur le serveur d'impôts")
' цикл читання запиту/запису відповіді
Dim demande As String = Nothing
Dim champs As String() = Nothing ' les éléments de la demande
Dim commande As String = Nothing ' la commande du client : calcul ou fincalculs
demande = [IN].ReadLine()
While Not (demande Is Nothing)
' розбиття запиту на поля
champs = Regex.Split(demande.Trim().ToLower(), "\s+")
' два прийняті запити: «calcul» та «fincalculs»
commande = champs(0)
Dim erreur As Boolean = False
If commande <> "calcul" And commande <> "fincalculs" Then
' помилка клієнта
OUT.WriteLine("Commande incorrecte. Utilisez (calcul,fincalculs).")
End If
If commande = "calcul" Then
calculerImpôt(champs)
End If
If commande = "fincalculs" Then
' повідомлення про завершення сеансу для клієнта
OUT.WriteLine("Au revoir...")
' звільнення ресурсів
Try
OUT.Close()
[IN].Close()
liaisonClient.Close()
Catch
End Try
' кінець
Return
End If
' новий запит
demande = [IN].ReadLine()
End While
Catch e As Exception
erreur("L'erreur suivante s'est produite (" + e.ToString + ")", 2)
End Try
End Sub
Розрахунок податку здійснюється методом calculerImpôt, який отримує як параметр масив полів запиту, надісланого клієнтом. Перевіряється правильність запиту, після чого, за необхідності, розраховується податок і повертається клієнту.
' розрахунок податків
Public Sub calculerImpôt(ByVal champs() As String)
' обробка запиту: розрахунок для одружених nbEnfants salaireAnnuel
' розбито на поля в таблиці полів
Dim marié As String = Nothing
Dim nbEnfants As Integer = 0
Dim salaireAnnuel As Integer = 0
' правильність аргументів
Try
' потрібно щонайменше 4 поля
If champs.Length <> 4 Then
Throw New Exception
End If
' одружений
marié = champs(1)
If marié <> "o" And marié <> "n" Then
Throw New Exception
End If
' діти
nbEnfants = Integer.Parse(champs(2))
' зарплата
salaireAnnuel = Integer.Parse(champs(3))
Catch
OUT.WriteLine(" syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel")
' завершено
Exit Sub
End Try
' можна розрахувати податок
Dim impot As Long = objImpôt.calculer(marié = "o", nbEnfants, salaireAnnuel)
' відправляємо відповідь клієнту
OUT.WriteLine(impot.ToString)
End Sub
' відображення помилок
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' відображення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
Цей клас компілюється за допомогою
де impots.dll містить код класу impôt. Тестова програма може виглядати так:
' простори імен
Imports System
Imports System.IO
Imports Microsoft.VisualBasic
Public Class testServeurImpots
Public Shared syntaxe As String = "Syntaxe : pg port dsnImpots Timpots colLimites colCoeffR colCoeffN"
' головна програма
Public Shared Sub Main(ByVal args() As String)
' потрібно 6 аргументів
If args.Length <> 6 Then
erreur(syntaxe, 1)
End If
' порт має бути цілим числом >0
Dim port As Integer = 0
Dim erreurPort As Boolean = False
Dim E As Exception = Nothing
Try
port = Integer.Parse(args(0))
Catch ex As Exception
E = ex
erreurPort = True
End Try
erreurPort = erreurPort Or port <= 0
If erreurPort Then
erreur(syntaxe + ControlChars.Lf + "Port incorrect (" + E.ToString + ")", 2)
End If
' створюється сервер податків
Try
Dim srvimots As ServeurImpots = New ServeurImpots(port, args(1), args(2), args(3), args(4), args(5))
Catch ex As Exception
'помилка
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
End Try
End Sub
' виведення помилок
Public Shared Sub erreur(ByVal msg As String, ByVal exitCode As Integer)
' виведення помилки
System.Console.Error.WriteLine(msg)
' зупинка з помилкою
Environment.Exit(exitCode)
End Sub
End Class
У тестову програму передаються дані, необхідні для створення об’єкта ServeurImpots, і на їх основі вона створює цей об’єкт. Ця тестова програма компілюється за допомогою:
Ось перший тест:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
stop
Рядок
створює об’єкт ServeurImpots, який ще не приймає запити від клієнтів. Саме команда start, введена з клавіатури, запускає цей процес прийому запитів. Команда stop зупиняє сервер. Тепер скористаємося клієнтом. Ми будемо використовувати раніше створений універсальний клієнт. Сервер запущено:
dos>testimpots 124 odbc-mysql-dbimpots impots limites coeffr coeffn
Serveur d'impôts>Serveur d'impôts>start
Serveur d'impôts>Serveur d'écho lancé sur le port 124
Універсальний клієнт запускається в іншому вікні DOS:
Бачимо, що клієнт успішно отримав привітальне повідомлення від сервера. Надсилаємо інші команди:
x
<-- Commande incorrecte. Utilisez (calcul,fincalculs).
calcul
<-- syntaxe : calcul marié(O/N) nbEnfants salaireAnnuel
calcul o 2 200000
<-- 22506
calcul n 2 200000
<-- 33388
fincalculs
<-- Au revoir...
[fin du thread de lecture des réponses du serveur]
fin
[fin du thread d'envoi des commandes au serveur]
Повертаємося до вікна сервера, щоб його зупинити:















