Сокет з'єднання. Теорія мереж та низькі рівні. Приклад: безпечна багатопотокова DLL для обміну повідомленнями через сокет

останнє оновлення: 31.10.2015

В основі міжмережевих взаємодійза протоколами TCP та UDP лежать сокети. В.NET сокети представлені класом System.NET.Sockets.Socket, який надає низькорівневий інтерфейс для прийому та надсилання повідомлень через мережу.

Розглянемо основні властивості цього класу:

    AddressFamily: повертає всі адреси, що використовуються сокетом. Ця властивістьпредставляє одне із значень, визначених в однойменному перерахуванні AddressFamily . Перелік містить 18 різних значень, що найбільш використовуються:

    • InterNetwork: адреса за протоколом IPv4

      InterNetworkV6: адреса за протоколом IPv6

      IPX: адреса IPX або SPX

      NetBios: адреса NetBios

    Available: повертає обсяг даних, доступних для читання

    Connected: повертає true, якщо сокет підключено до віддаленого хоста

    LocalEndPoint: повертає локальну точку, за якою запущено сокет і за якою він приймає дані

    ProtocolType: повертає одне із значень перерахування ProtocolType , що представляє протокол, що використовується сокетом. Є такі можливі значення:

    • IPSecAuthenticationHeader (Заголовок IPv6 AH)

      IPSecEncapsulatingSecurityPayload (Заголовок IPv6 ESP)

      IPv6DestinationOptions (Заголовок IPv6 Destination Options)

      IPv6FragmentHeader (Заголовок IPv6 Fragment)

      IPv6HopByHopOptions (Заголовок IPv6 Hop by Hop Options)

      IPv6NoNextHeader (Заголовок IPv6 No next)

      IPv6RoutingHeader (Заголовок IPv6 Routing)

      Unknown (невідомий протокол)

      Unspecified (невказаний протокол)

    Кожне значення представляє відповідний протокол, але найбільш використовуються Tcp і Udp.

    RemoteEndPoint: повертає адресу віддаленого хоста, до якого підключено сокет

    SocketType: Повертає тип сокету. Представляє одне із значень із переліку SocketType :

    • Dgram: сокет отримуватиме і надсилатиме дейтаграми по протоколу Udp. Цей типсокет працює у зв'язці з типом протоколу - Udp і значенням AddressFamily.InterNetwork

      Raw: сокет має доступ до протоколу нижче. транспортного рівняі може використовувати для передачі повідомлень такі протоколи, як ICMP та IGMP

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

      Seqpacket: забезпечує надійну двосторонню передачу даних із встановленням постійного підключення

      Stream: забезпечує надійну двосторонню передачу даних із встановленням постійного підключення. Для зв'язку використовується протокол TCP, тому цей тип сокету використовується в парі з типом протоколу TCP і значенням AddressFamily.InterNetwork

      Unknown: адреса NetBios

Для створення об'єкта сокету можна використовувати один із його конструкторів. Наприклад, сокет, який використовує протокол Tcp:

Socket socket = New Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

Або сокет, який використовує протокол Udp:

Socket socket = New Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

Таким чином, під час створення сокету ми можемо вказувати різні комбінаціїпротоколів, типів сокету, значень із переліку AddressFamily. Проте водночас не всі комбінації є коректними. Так, для роботи через протокол TCP, нам треба обов'язково вказати параметри: AddressFamily.InterNetwork, SocketType.Stream та ProtocolType.Tcp. Для Udp набір параметрів буде іншим: AddressFamily.InterNetwork, SocketType.Dgram та ProtocolType.Udp. Для інших протоколів набір значень відрізнятиметься. Тому використання сокетів може вимагати певного знання принципів роботи окремих протоколів. Хоча щодо Tcp та Udp все відносно просто.

Загальний принцип роботи сокетів

При роботі з сокетами незалежно від вибраних протоколів ми спиратимемося на методи класу Socket:

    Accept() : створює новий об'єкт Socket для обробки вхідного підключення

    Bind() : зв'язує об'єкт Socket з локальною кінцевою точкою

    Close() : закриває сокет

    Connect() : встановлює з'єднання з віддаленим хостом

    Listen() : починає прослуховування вхідних запитів

    Poll() : визначає стан сокету

    Receive() : отримує дані

    Send() : надсилає дані

    Shutdown() : блокує на сокеті прийом та відправлення даних

Залежно від протоколу, що застосовується (TCP, UDP і т.д.) загальний принципроботи з сокетами трохи відрізнятимуться.

При застосуванні протоколу, який вимагає встановлення з'єднання, наприклад, TCP, сервер повинен викликати метод Bind для встановлення точки для прослуховування вхідних підключень і потім запустити прослуховування підключень за допомогою методу Listen. Далі за допомогою методу Accept можна отримати вхідні запити на підключення як об'єкт Socket, який використовується для взаємодії з віддаленим вузлом. У отриманого об'єкта Socket викликаються методи Send та Receive відповідно для відправлення та отримання даних. Якщо потрібно підключитися до сервера, то викликається метод Connect. Для обміну даними із сервером також застосовуються методи Send або Receive.

Якщо застосовується протокол, для якого не потрібно встановлення з'єднання, наприклад UDP, то після виклику методу Bind не потрібно викликати метод Listen. І тут для прийому даних використовується метод ReceiveFrom, а відправки даних - метод SendTo.

Сокети

Сокет- це один кінець двостороннього каналу зв'язку між двома програмами, що працюють у мережі. З'єднуючи разом два сокети, можна передавати дані між різними процесами(локальними чи віддаленими). Реалізація сокетів забезпечує інкапсуляцію протоколів мережного та транспортного рівнів.

Спочатку сокети були розроблені для UNIX в університеті Каліфорнії в Берклі. У UNIX забезпечує зв'язок метод введення-виведення слідує алгоритму open/read/write/close. Перш ніж використовувати ресурс, його потрібно відкрити, задавши відповідні дозволи та інші параметри. Як тільки ресурс відкритий, з нього можна зчитувати або записувати дані. Після використання ресурсу користувач повинен викликати метод Close(), щоб подати сигнал операційній системі завершення його роботи з цим ресурсом.

Коли в операційну систему UNIXбули додані кошти міжпроцесної взаємодії(Inter-Process Communication, IPC)та мережного обміну, був запозичений звичний шаблон введення-виведення. Усі ресурси, відкриті зв'язку, в UNIX і Windows ідентифікуються дескрипторами. Ці дескриптори, або описувачі (handles), можуть вказувати на файл, пам'ять або інший канал зв'язку, а фактично вказують на внутрішню структуруданих, що використовується операційною системою. Сокет, будучи таким самим ресурсом, теж представляється дескриптором. Отже, для сокетів життя дескриптора можна поділити на три фази: відкрити (створити) сокет, отримати із сокету або відправити сокету і зрештою закрити сокет.

Інтерфейс IPC для взаємодії між різними процесами побудований поверх методів введення-виведення. Вони полегшують для сокетів відправлення та отримання даних. Кожен цільовий об'єкт задається адресою сокета, отже, цю адресу можна вказати в клієнті, щоб встановити з'єднання з метою.

Типи сокетів

Існують два основних типи сокетів - потокові сокети та дейтаграмні.

Поточні сокети (stream socket)

Потоковий сокет - це сокет з встановленим з'єднанням, Що складається з потоку байтів, який може бути двонаправленим, тобто через цю кінцеву точку додаток може і передавати, і отримувати дані.

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

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

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

Потоки базуються на явних з'єднаннях: сокет А запитує з'єднання з сокетом, а сокет або погоджується із запитом на встановлення з'єднання, або відкидає його.

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

Сервер електронної поштипредставляє приклад додатку, який повинен доставляти зміст у правильному порядку, без дублювання та перепусток. Поточний сокет розраховує, що TCP забезпечить доставку повідомлень за призначенням.

Дейтаграмні сокети (datagram socket)

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

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

Використання дейтаграмних сокетів вимагає, щоб передачі даних від клієнта до сервера займалися. User Datagram Protocol (UDP). У цьому протоколі розмір повідомлень накладаються деякі обмеження, і на відміну потокових сокетів, вміють надійно відправляти повідомлення серверу-адресату, дейтаграммные сокети надійність не забезпечують. Якщо дані загубилися в мережі, сервер не повідомить про помилки.

Крім двох розглянутих типів існує узагальнена форма сокетів, яку називають необроблюваними або сирими.

Сирі сокети (raw socket)

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

За визначенням, сирий сокет - це сокет, який приймає пакети, обходить рівні TCP і UDP у стеку TCP/IP і надсилає їх додатку.

У разі використання таких сокетів пакет не проходить через фільтр TCP/IP, тобто. ніяк не обробляється, і постає у своїй сирій формі. У такому разі обов'язок правильно обробити всі дані та виконати такі дії, як видалення заголовків та розбір полів, лягає на одержуючий додаток - все одно, що включити додаток невеликий стек TCP/IP.

Однак нечасто може бути потрібна програма, що працює з сирими сокетами. Якщо ви не пишете системне програмне забезпеченняабо програму, аналогічну до аналізатора пакетів, вникати в такі деталі не доведеться. Сирі сокети переважно використовуються розробки спеціалізованих низькорівневих протокольних додатків. Наприклад, такі різноманітні утиліти TCP/IP, як trace route, ping чи arp використовують сирі сокети.

Робота з сирими сокетами потребує солідного знання базових протоколів TCP/UDP/IP.

Порти

Порт визначено, щоб вирішити завдання одночасної взаємодії з кількома програмами. Фактично з його допомогою розширюється поняття IP-адреси. Комп'ютер, на якому одночасно виконується кілька програм, отримуючи пакет з мережі, може ідентифікувати цільовий процес, користуючись унікальним номеромпорту, визначеним під час встановлення з'єднання.

Сокет складається з IP-адреси машини та номера порту, що використовується додатком TCP. Оскільки IP-адреса унікальна в Інтернеті, а номери портів унікальні на окремій машині, номери сокетів також унікальні у всьому Інтернеті. Ця характеристика дозволяє процесу спілкуватися через мережу з іншим процесом лише на підставі номера сокету.

За певними службами номери портів зарезервовані - широко відомі номери портів, наприклад порт 21, що використовується в FTP. Ваша програма може користуватися будь-яким номером порту, який не був зарезервований і поки не зайнятий. Агентство Internet Assigned Numbers Authority (IANA)веде список широко відомих номерів портів.

Зазвичай додаток клієнт-сервер, що використовує сокети, складається з двох різних додатків- клієнта, який ініціює з'єднання з метою (сервером), та сервера, що очікує з'єднання від клієнта.

Наприклад, на стороні клієнта, програма повинна знати адресу мети та номер порту. Надсилаючи запит на з'єднання, клієнт намагається встановити з'єднання із сервером:

Якщо події розвиваються вдало, за умови, що сервер запущений перш, ніж клієнт спробував з ним з'єднатися, сервер погоджується на з'єднання. Давши згоду, серверний додаток створює новий сокет для взаємодії саме з клієнтом, що встановив з'єднання:

Тепер клієнт і сервер можуть взаємодіяти між собою, зчитуючи повідомлення кожен із свого сокету і, відповідно, записуючи повідомлення.

Робота із сокетами в.NET

Підтримку сокетів в .NET забезпечують класи у просторі імен System.Net.Sockets- Почнемо з їх короткого опису.

Класи для роботи із сокетами
Клас Опис
MulticastOption Клас MulticastOption встановлює значення IP-адреси для приєднання до IP-групи або виходу з неї.
NetworkStream Клас NetworkStream реалізує базовий класпотоку, з якого дані відправляються та в якому вони виходять. Це абстракція високого рівня, що є з'єднання з каналом зв'язку TCP/IP.
TcpClient Клас TcpClient будується на класі Socket, щоб забезпечити TCP-обслуговування на більш високому рівні. TcpClient надає кілька методів для надсилання та отримання даних через мережу.
TcpListener Цей клас також збудовано на низькорівневому класі Socket. Його основне призначення – серверні програми. Він очікує на вхідні запити на з'єднання від клієнтів і повідомляє додаток про будь-які з'єднання.
UdpClient UDP - це протокол, який не організує з'єднання, отже, для реалізації UDP-обслуговування в.NET потрібна інша функціональність.
SocketException Це виняток породжується, як у сокеті виникає помилка.
Socket Останній клас у просторі імен System.Net.Sockets – це сам клас Socket. Він забезпечує базову функціональність програми сокета.

Клас Socket

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

Перш ніж звертатися до прикладу використання класу Socket, розглянемо деякі важливі властивості та методи цього класу:

Властивості та методи класу Socket
Властивість чи метод Опис
AddressFamily Дає сімейство адрес сокету - значення з переліку Socket.AddressFamily.
Available Повертає обсяг доступних для читання даних.
Blocking Дає або встановлює значення, що показує, чи знаходиться сокет у режимі блокування.
Connected Повертає значення, що інформує, чи з'єднаний сокет з віддаленим хостом.
LocalEndPoint Дає локальну кінцеву точку.
ProtocolType Дає тип протоколу сокету.
RemoteEndPoint Дає кінцеву точку сокету.
SocketType Дає тип сокету.
Accept() Створює новий сокет для обробки запиту на з'єднання.
Bind() Зв'язує сокет з локальною кінцевою точкою для очікування запитів на з'єднання.
Close() Змушує сокет закритися.
Connect() Встановлює з'єднання з віддаленим хостом.
GetSocketOption() Повертає значення SocketOption.
IOControl() Встановлює для сокету низькорівневі режими роботи. Цей метод забезпечує низькорівневий доступ до класу Socket, що лежить в основі.
Listen() Поміщає сокет у режим прослуховування (очікування). Цей метод призначений лише для серверних програм.
Receive() Отримує дані з'єднаного сокету.
Poll() Визначає статус сокету.
Select() Перевіряє статус одного чи кількох сокетів.
Send() Надсилає дані з'єднаному сокету.
SetSocketOption() Встановлює опцію сокету.
Shutdown() Забороняє операції надсилання та отримання даних на сокеті.

ВСЕВОЛОД СТАХІВ

Програмування сокетів

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

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

int socket (int domain, int type, int protocol);

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

  • PF_UNIX або PF_LOCALлокальна комунікаціядля ОС UNIX (та подібних).
  • PF_INET – IPv4, IP-протокол Інтернету, найбільш поширений зараз (32-бітна адреса).
  • PF_INET6– IPv6, наступне покоління протоколу IP (IPng) – 128 бітна адреса.
  • PF_IPX – IPX- Протоколи Novell.

Підтримуються інші протоколи, але ці 4 є найпопулярнішими.

Параметр типу означає тип сокета, тобто. те, як будуть передаватися дані: зазвичай застосовується константа SOCK_STREAM, її використання означає безпечну передачуданих двонаправленим потоком із контролем помилок. При такому способі передачі програмістові не доводиться дбати про обробку помилок мережі, хоча це не уберігає від логічних помилок, що актуально для мережевого сервера.

Параметр protocol визначає конкретний типпротоколу для даного domain, наприклад IPPROTO_TCP або IPPROTO_UDP (параметр type повинен у даному випадкубути SOCK_DGRAM).

Функція socket просто створює кінцеву точку та повертає дескриптор сокету; до того, як сокет не з'єднаний з віддаленою адресою функцією connect, дані через нього пересилати не можна! Якщо пакети губляться у мережі, тобто. сталося порушення зв'язку, то додатку, що створив сокет, надсилається сигнал Broken Pipe - SIGPIPE, тому доцільно присвоїти обробник даному сигналуфункцією сигналу. Після того, як сокет з'єднаний з іншою функцією connect, ним можна пересилати дані або стандартними функціями read - write, або спеціалізованими recv - send. Після закінчення роботи сокет слід закрити функцією close. Для створення клієнтської програмидостатньо зв'язати локальний сокет із віддаленим (серверним) функцією connect. Формат цієї функції такий:

int connect(int sock_fd, const struct * sockaddr serv_addr, socketlen_t addr_len);

За помилки функція повертає -1, статус помилки можна отримати засобами операційної системи. При успішній роботіповертається 0. Сокет, одного разу пов'язаний, найчастіше може бути пов'язаний знову, так, наприклад, відбувається у протоколі ip. Параметр sock_fd задає дескриптор сокету, структура serv_addr призначає віддалену адресу кінцевої точки, addr_len містить довжину serv_addr (тип socketlen_t має історичне походження, зазвичай він збігається з типом int). Самий важливий параметру цій функції – адреса віддаленого сокету. Він, звичайно, неоднаковий для різних протоколів, тому я опишу тут структуру адреси тільки для IP (v4)-протоколу. Для цього використовується спеціалізована структура sockaddr_in (її необхідно прямо приводити до типу sockaddr при виклику connect). Поля даної структури виглядають так:

struct sockaddr_in(

Sa_family_t sin_family; – визначає сімейство адрес, завжди має бути AF_INET

U_int16_t sin_port; - Порт сокету в мережевому порядку байт

Struct in_addr sin_addr; - Структура, що містить IP-адресу

Структура, що описує IP-адресу:

struct in_addr(

U_int32_t s_addr; - IP-адреса сокету в мережевому порядку байт

Зверніть увагу на особливий порядок байт у всіх полях. Для переведення номера порту в мережевий порядок байт можна скористатися макросом htons ( unsigned short port). Дуже важливо використовувати саме цей тип цілого – коротке беззнакове ціле.

Адреси IPv4 поділяються на одиночні, широкомовні (broadcast) та групові (multicast). Кожна одиночна адреса вказує на один інтерфейс хоста, широкомовні адреси вказують на всі хости в мережі, а групові адреси відповідають всім хостів групи (multicast group). У структурі in_addr можна призначати будь-яку з цих адрес. Але для сокетних клієнтів у переважній більшості випадків надають одиночну адресу. Винятком є ​​той випадок, коли необхідно просканувати всю локальну мережуу пошуках сервера, тоді можна використовувати як адресу широкомовний. Потім, швидше за все, сервер повинен повідомити свою реальну IP-адресу і сокет для подальшої передачі повинен приєднуватися саме до нього. Передача даних через широкомовні адреси немає хороша ідеятому що невідомо, який саме сервер обробляє запит. Тому в даний час сокети, орієнтовані на з'єднання, можуть використовувати лише поодинокі адреси. Для сокетних серверів, орієнтованих на прослуховування адреси, спостерігається інша ситуація: тут можна використовувати широкомовні адреси, щоб відразу ж відповісти клієнту на запит про місцезнаходження сервера. Але про все по порядку. Як ви помітили, у структурі sockaddr_in поле IP-адреси представлене як беззнакове довге ціле, а ми звикли до адрес або у форматі x.x.x.x (172.16.163.89) або в символьному форматі (myhost.com). Для перетворення першого служить функція inet_addr (const char * ip_addr), а другого – функція gethostbyname (const char * host). Розглянемо обидві з них:

u_int32_t inet_addr(const char *ip_addr)

- Повертає відразу ж ціле, придатне для використання в структурі sockaddr_in за IP-адресою, переданою їй у форматі x.x.x.x. У разі виникнення помилки повертається значення INADDR_NONE.

struct HOSTENT* gethostbyname(const char *host_name)

- Повертає структуру інформації про хост, виходячи з його імені. У разі невдачі повертає NULL. Пошук імені відбувається спочатку у файлі hosts, а потім у DNS. Структура HOSTENT надає інформацію про необхідний хост. З усіх її полів найбільш значним є поле char h_addr_list, що представляє список IP-адрес цього хоста. Зазвичай використовується h_addr_list, що представляє першу адресу хоста ip, для цього можна також використовувати вираз h_addr. Після виконання функції gethostbyname у списку h_addr_list структури HOSTENT виявляються прості символічні адреси ip, тому необхідно скористатися додатково функцією inet_addr для перетворення в формат sockaddr_in.

Отже, ми пов'язали клієнтський сокет із серверною функцією connect. Далі можна використовувати функції передачі. Для цього можна використовувати або стандартні функціїнизькорівневого вводу/виводу для файлів, оскільки сокет - це, по суті справи файловий дескриптор. На жаль, для різних операційних систем функції низькорівневої роботи з файлами можуть бути різними, тому треба подивитися керівництво до своєї операційної системи. Врахуйте, що передача даних через мережу може закінчитися сигналом SIGPIPE, і функції читання/запису повернуть помилку. Завжди потрібно пам'ятати про перевірку помилок, крім того, не можна забувати про те, що передача даних через мережу може бути дуже повільною, а функції введення/виводу є синхронними, і це може спричинити суттєві затримки в роботі програми.

Для передачі між сокетами існують спеціальні функції, єдині всім ОС – це функції сімейства recv і send. Формат їх дуже схожий:

int send(int sockfd, void *data, size_t len, int flags);

- Відправляє буфер data.

int recv(int sockfd, void *data, size_t len, int flags);

- Приймає буфер data.

Перший аргумент – дескриптор сокету, другий – покажчик даних для передачі, третій – довжина буфера і четвертий – прапори. У разі успіху повертається кількість переданих байт, у разі невдачі – негативний код помилки. Прапори дозволяють змінити параметри передачі (наприклад, увімкнути асинхронний режим роботи), але для більшості завдань достатньо залишити поле прапорів нульовим. звичайного режимупередачі. При надсиланні або прийомі даних функції блокують виконання програми до того, як буде відіслано весь буфер. А при використанні протоколу tcp/ip від віддаленого сокету має прийти відповідь про успішне відправлення або прийом даних, інакше пакет пересилається ще раз. При пересиланні даних враховуйте MTU мережі ( максимальний розмірпереданого за один раз кадру). Для різних мережвін може бути різним, наприклад, для мережі Ethernetвін дорівнює 1500.

Отже, для повноти викладу наведу найпростіший приклад програми на Сі, що реалізує сокетного клієнта:

#include /* Стандартні бібліотекисокетів для Linux */

#include /* Для Windows використовуйте #include */

#include

int main()(

Int sockfd = -1;

/* Дескриптор сокету */

Char buf;

Char s = "Client ready";

HOSTENT * h = NULL;

Sockaddr_in addr;

Unsigned short port = 80;

Addr.sin_family = AF_INET;

/* Створюємо сокет */

If(sockfd == -1)

/* Створено сокет */

Return-1;

H = gethostbyname("www.myhost.com");

/* Отримуємо адресу хоста */

If(h == NULL)

/* А чи є така адреса? */

Return-1;

Addr.sin_addr.s_addr = inet_addr(h->h_addr_list);

/* Переводимо ip адресу в число */

If(connect(sockfd, (sockaddr*) &addr, sizeof(addr)))

/* Намагаємось з'єднається з віддаленим сокетом */

Return-1;

/* З'єднання пройшло успішно - продовжуємо */

If(send(sockfd, s, sizeof(s), 0)< 0)

Return-1;

If(recv(sockfd, buf, sizeof(buf), 0)< 0)

Return-1;

Close(sockfd);

/* Закриваємо сокет */

/* Для Windows застосовується функція closesocket(s) */

Return 0;

Ось бачите, використати сокети не так важко. У серверних додаткахвикористовуються зовсім інші принципи роботи із сокетами. Спочатку створюється сокет, потім йому надається локальна адресафункцією bind, при цьому можна надати сокету широкомовну адресу. Потім починається прослуховування адреси функцією listen, запити на з'єднання розміщуються у чергу. Тобто функція listen виконує ініціалізацію сокету для отримання повідомлень. Після цього потрібно застосувати функцію accept, яка повертає новий, вже пов'язаний із клієнтом сокет. Зазвичай серверам характерно приймати багато з'єднань через невеликі проміжки часу. Тому потрібно постійно перевіряти чергу вхідних з'єднань функцією accept. Для організації такої поведінки найчастіше вдаються до можливостей операційної системи. Для Windows частіше використовується багатопоточний варіант роботи сервера (multi-threaded), після прийняття з'єднання відбувається створення нового потоку в програмі, який і обробляє сокет. У *nix-системах найчастіше використовується породження дочірнього процесу функцією fork. При цьому накладні витрати зменшені за рахунок того, що фактично відбувається копія процесу файловій системі proc. У цьому всі змінні дочірнього процесу збігаються з батьком. І дочірній процес може відразу ж обробляти з'єднання, що входить. Батьківський процес продовжує прослуховування. Врахуйте, що порти з номерами від 1 до 1024 є привілейованими та їх прослуховування не завжди можливе. Ще один момент: не можна, щоб два різні сокети прослуховували один і той же порт за тією ж адресою! Спочатку розглянемо формати вищеописаних функцій для створення серверного сокету:

int bind (int sockfd, const struct * sockaddr, socklen_t addr_len);

– надає сокету локальну адресу для забезпечення можливості приймати вхідні з'єднання. Для адреси можна використовувати константу INADDR_ANY, яка дозволяє приймати вхідні з'єднання з усіх адрес у даній підмережі. Формат функції аналогічний connect. У разі помилки повертає негативне значення.

int listen (int sockfd, int backlog);

– функція створює чергу вхідних сокетів (кількість підключень визначається параметром backlog, вона має перевищувати числа SOMAXCONN, яке залежить від ОС). Після створення черги очікується з'єднання функцією accept. Сокети зазвичай є блокуючими, тому виконання програми припиняється, доки з'єднання не буде прийнято. У разі помилки повертається -1.

int accept(int sockfd, struct * sockaddr, socklen_t addr_len)

– функція чекає вхідного з'єднання(або витягує його з черги з'єднань) і повертає новий сокет, вже пов'язаний із віддаленим клієнтом. При цьому вихідний сокет sockfd залишається в постійному стані. Структура sockaddr заповнюється значеннями віддаленого сокету. У разі помилки повертається -1.

Отже, наведу приклад простого сокетного сервера, що використовує функцію fork для створення дочірнього процесу, що обробляє з'єднання:

int main()(

Pid_t pid;

/* Ідентифікатор дочірнього процесу */

Int sockfd = -1;

/* Дескриптор сокету для прослуховування */

Int s = -1;

/* Дескриптор сокету для прийому */

Char buf;

/* Вказівник на буфер для прийому */

Char str = "Server ready";

/* Рядок передачі серверу */

HOSTENT * h = NULL;

/* Структура для отримання ip адреси */

Sockaddr_in addr;

/* Структура tcp/ip протоколу */

Sockaddr_in raddr;

Unsigned short port = 80;

/* Заповнюємо поля структури: */

Addr.sin_family = AF_INET;

Addr.sin_port = htons(port);

sockfd = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);

/* Створюємо сокет */

If(sockfd == -1)

/* Створено сокет */

Return-1;

Addr.sin_addr.s_addr = INADDR_ANY;

/* Слухаємо на всіх адресах */

If(bind(sockfd, (sockaddr*) &addr, sizeof(addr)))

/* Привласнюємо сокету локальну адресу */

Return-1;

If(listen(sockfd, 1))

/* Починаємо прослуховування */

Return-1;

S = accept(sockfd, (sockaddr *) &raddr, sizeof(raddr));

/* Приймаємо з'єднання */

Pid = fork ();

/* породжуємо дочірній процес */

If(pid == 0)(

/* Це дочірній процес */

If(recv(s, buf, sizeof(buf), 0)< 0)

/* Надсилаємо видаленому сокету рядок s */

Return-1;

If(send(s, str, sizeof(str), 0)< 0)

/* Отримуємо відповідь від віддаленого сервера */

Return-1;

Printf("Recieved string was: %s", buf);

/* Виведення буфера на стандартний висновок */

Close(s);

/* Закриваємо сокет */

Return 0;

/* Виходимо з дочірнього процесу */

Close(sockfd);

/* Закриваємо сокет для прослуховування */

Return 0;

При створенні потоку (thread) для обробки сокету дивіться посібник до ОС, тому що для різних системвиклик функції створення потоку може суттєво відрізнятися. Але принципи обробки потоку залишаються тими самими. Функції обробки необхідно лише передати як аргумент покажчик на сокет (зазвичай у функцію потоку можна передавати дані будь-якого типу у форматі void *, що вимагає використання типів).

Важливе зауваження для Windows. Я помітив, що система сокетів не працює без застосування функції WSAStartup для ініціалізації бібліотеки сокетів. Програма із сокетами в ОС Windows повинна починатися так:

WSADATA wsaData;

WSAStartup(0x0101, &wsaData);

І при виході із програми пропишіть наступне:

WSACleanup();

Так як в основному операції з сокетами є блокуючими, часто доводиться переривати виконання завдання очікуванням синхронізації. Тому часто в *nix-подібних системах уникають блокування консолі створенням особливого типу програми – демона. Демон не належить віртуальним консолям і виникає коли дочірній процес викликає fork, а батьківський процес завершується раніше, ніж 2-й дочірній (а це завжди буває саме таким чином). Після цього другий дочірній процес стає основним і не блокує консоль. Наведу приклад такої поведінки програми:

pid = fork ();

/* Створення першого дочірнього процесу */

if (pid<0){

/* Помилка виклику fork */

Printf("Forking Error:)");

Exit(-1);

)else if (pid !=0)(

/* Це перший батько! */

Printf("This is a Father 1");

)else(

Pid = fork ();

/* Робота 1-го батька завершується */

/* І ми викликаємо ще один дочірній процес */

If (pid<0){

Printf("Forking error:)");

Exit(-1);

)else if (pid !=0)(

/* Це другий батько */

Printf("This is a father 2");

)else(

/* А ось це той самий 2-й дочірній процес*/

/* Перехід у "стандартний" режим демона */

Setsid();

/* Виконуємо демон у режимі superuser */

Umask(0); /* Стандартна маска файлів */

Chdir("/"); /* Перехід у кореневий каталог */

Daemoncode(); /* Власне сам код демона */

/* При викликі fork демона з'являється нащадок-демон */

От і все. Я думаю, для створення простенького сокетного сервера цього достатньо.

інтерфейс мережевих системних викликів ( socket(), bind(), recvfrom(), sendto()і т. д.) в операційній системі UNIX може застосовуватися і для інших стеків протоколів (і для протоколів, що лежать нижче транспортного рівня).

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

Другий параметр служить для завдання виду інтерфейсу роботи з сокетом – це потоковий сокет, сокет для роботи з датаграмами або будь-якої іншої. Третій параметр визначає протокол для заданого типу інтерфейсу. У стеку протоколів TCP/IPІснує лише один протокол для потокових сокетів – TCP і лише один протокол для датаграмних сокетів – UDP, тому для транспортних протоколів TCP/IP третій параметр ігнорується.

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

Для транспортних протоколів TCP/IP ми завжди як перший параметр будемо вказувати визначену константу AF_INET (Address family – Internet) або її синонім PF_INET (Protocol family – Internet).

Другий параметр прийматиме визначені значення SOCK_STREAM для потокових сокетів та SOCK_DGRAM – для датаграмних.

Оскільки третій параметр у нашому випадку не враховується, ми будемо підставляти значення 0 .

Посилання на інформацію про створений сокет міститься в таблицю відкритих файлів процесуподібно до того, як це робилося для pip 'ів та FIFO (див. семінар 5). Системний виклик повертає користувачеві файловий дескриптор , що відповідає заповненому елементу таблиці, який далі називатимемо дескриптором сокета . Такий спосіб зберігання інформації про сокет дозволяє, по-перше, процесам-дітям успадковувати її від процесів-батьків, а, по-друге, використовувати для сокетів частину системних викликів, які вже знайомі нам по роботі з pip'ами та FIFO : close( ), read(), write().

Системний виклик для створення сокету

Прототип системного виклику

#include #include int socket (int domain, int type, int protocol);

Опис системного виклику

Системний виклик socketслужить до створення віртуального комунікаційного вузла операційній системі. Цей опис не є повним описом системного виклику, а призначений лише для використання у нашому курсі. За повною інформацією зверніться до UNIX Manual.

Параметр domain визначає сімейство протоколів, у межах якого здійснюватиметься передача інформації. Ми розглянемо лише два таких сімейства з кількох існуючих. Для них є визначені значення параметра:

  • PF_INET – для сімейства протоколів TCP/IP ;
  • PF_UNIX – для сімейства внутрішніх протоколів UNIX, інакше званого UNIX domain.

Параметр type визначає семантику обміну інформацією: чи здійснюватиметься зв'язок через повідомлення (datagrams), за допомогою встановлення віртуального з'єднанняабо ще якимось способом. Ми будемо користуватися лише двома способами обміну інформацією з визначеними значеннями для параметра type:

  • SOCK_STREAM – для зв'язку за допомогою встановлення віртуального з'єднання ;
  • SOCK_DGRAM – обмінюватись інформацією через повідомлення.

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

Значення, що повертається

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

Адреси сокетів. Настроювання адреси сокета. Системний виклик bind()

Коли створено сокет, необхідно налаштувати його адресу . Для цього використовується системний виклик bind(). Перший параметр дзвінка повинен містити дескриптор сокета , для якого виконується налаштування адреси. Другий і третій параметри задають цю адресу.

У другому параметрі має бути покажчик на структуру struct sockaddr , що містить віддалену та локальні частини повної адреси.

Вказівники типу struct sockaddr * зустрічаються у багатьох мережевих системних викликах; вони використовуються передачі інформації у тому, якого адресою прив'язаний чи може бути прив'язаний сокет . Розглянемо цей тип даних докладніше. Структура struct sockaddr описана у файлі наступним чином:

struct sockaddr (short sa_family; char sa_data;);

Такий склад структури обумовлений тим, що мережні системні виклики можуть застосовуватися для різних сімейств протоколів, які по-різному визначають адресні простори для віддалених та локальних сокетних адрес. За суттю, цей тип даних є лише загальний шаблон передачі системним викликам структур даних, специфічних кожному за сімейства протоколів. Загальним елементом цих структур залишається лише поле short sa_family (яке у різних структурах, звісно, ​​може мати різні імена, важливо лише, щоб вони були одного типу і були першими елементами своїх структур) для опису сімейства протоколів. Вміст цього поля системний виклик аналізує для точного визначення складу інформації, що надійшла.

Для роботи з сімейством протоколів TCP/IPми будемо використовувати адресу сокету наступного виду, описаного у файлі :

struct sockaddr _in( short sin_family; /* Вибране сімейство протоколів – завжди AF_INET */ unsigned short sin_port; /* 16-бітовий номер порту в мережевому порядку байт */ struct in_addr sin_addr; /* Адреса мережного інтерфейсу */ char sin_zero; Це поле не використовується, але має бути заповнене нулями */ );

Перший елемент структури – sin_family задає сімейство протоколів. До нього ми заноситимемо вже відому нам зумовлену константу AF_INET (див. попередній розділ).

Віддалена частина повної адреси - IP-адреса - міститься в структурі типу struct in_addr , з якою ми зустрічалися в розділі "Функції перетворення IP-адрес inet_ntoa(), inet_aton() " .

Для вказівки номера порту призначений елемент структури sin_port, у якому номер порту повинен зберігатися в мережевому порядку байт. Існує два варіанти завдання номера порту: фіксований порт за бажанням користувача і порт, який довільно призначає операційна система. Перший варіант вимагає вказівки як номер порту позитивного заздалегідь відомого числа і для протоколу UDP зазвичай використовується при налаштуванні адрес сокетів і передачі інформації за допомогою системного виклику sendto()(Див. наступний розділ). Другий варіант вимагає вказівки як номер порту значення 0. У цьому випадку операційна системасама прив'язує сокет до вільного номера порту. Цей спосіб зазвичай використовується для налаштування сокетів програм клієнтів, коли заздалегідь точно знати номер порту програмісту необов'язково.

Який номер порту може використовувати користувач при фіксованому налаштуванні? Номери портів з 1 по 1023 можуть призначати сокетам лише процеси, які працюють із привілеями системного адміністратора. Як правило, ці номери закріплені за системними мережевими службами незалежно від виду операційної системи, що використовується, для того щоб користувацькі клієнтські програми могли запитувати обслуговування завжди за одним і тим же локальним адресам. Існує також ряд широко застосовуваних мережевих програм, які запускають процеси з повноваженнями звичайних користувачів (наприклад, X-Windows). Для таких програм корпорацією Internet з присвоєння імен та номерів (

Ви хочете розробити мережеву програму на Джаві – іграшку, чат, або те й інше разом… Ви знайшли правильну статтю – тут ви зможете ознайомитись із захоплюючим світом сокетів у Java. Прочитавши цю статтю, ви побачите світло в кінці тунелю – стане очевидним призначення сокетів і те, як розробити просту програму з використанням сокетів мовою програмування Джава.

Що таке сокет?

На сьогоднішній день використання клієнтів служб миттєвого обміну повідомленнями стало незамінним засобом для всіх користувачів Інтернету. Існує безліч різних протоколів та клієнтів (MSN, ICQ, Skype тощо), про які кожен чув і які ми всі щодня використовуємо. Але не всі знають, що покладено в основу їхньої роботи – власне для цього стаття написана. Припустимо, ви встановили один із клієнтів служб миттєвого обміну повідомленнями на ваш комп'ютер. Після його запуску та введення імені та пароля вашого користувача, програма намагається підключитися до сервера. Що означає слово "підключитися"?

Кожен комп'ютер у мережі має IP-адресу. Ця адреса схожа на вашу домашню адресу – вона однозначно ідентифікує ваш комп'ютер і дозволяє іншим спілкуватися з вашим комп'ютером. Не будемо вдаватися в подробиці IP-адреси, так як ця стаття не про них, хочу тільки відзначити, що IP-адреса це набір номерів розділених точками (наприклад, 64.104.137.158). Хоча існує й інший спосіб ідентифікації комп'ютерів у мережі – доменне ім'я, яке зручніше та наочніше ідентифікує комп'ютер, ніж простий набір чисел (наприклад, www.сайт). В Інтернеті існують спеціальні комп'ютери, які здійснюють перетворення доменного імені на IP-адресу і навпаки.

Один комп'ютер може паралельно виконується кілька програм. Припустимо, що ви запустили 10 програм на власному комп'ютері, і всі вони очікують, щоб інші комп'ютери зв'язалися з ними. Можете уявити це так: вас 10 чоловік у великому офісі з 1 телефоном і всі чекають дзвінків від їхніх власних клієнтів. Як ви це вирішите? Можна звичайно призначити відповідального працівника, і він приноситиме телефон відповідній людині, якій дзвонять, але тоді інші клієнти не зможуть додзвонитися до інших людей. Крім того, це дуже важко і безглуздо мати відповідального працівника за маршрутизацію дзвінків до потрібних людей. Ви, мабуть, вже здогадалися, до чого я веду – якщо всі ці програми, що виконуються на одному комп'ютері, з гордістю просять своїх клієнтів зв'язатися з ними за певною IP-адресою, то їх клієнти не будуть задоволені. Ідея полягає в наступному … мати окрему IP-адресу для кожної програми, чи не так? НЕ ВІРНО! Суть питання не правильна - це також як запитувати про окремий офіс для кожного з вас. Ну, тоді… може, окремих телефонних номерів буде достатньо? ТАК! На мережному жаргоні "окремі телефонні номери" мають назву порти. Порт – це число і кожна з програм, яка виконується певному комп'ютері, може вибрати унікальне число порту, щоб визначити себе зовнішнього світу. ПОМ'ЯТАЙТЕ - ці порти ви не зможете знайти серед апаратних засобів комп'ютера (навіть не намагайтеся їх шукати). Ці числа – логічні. Тепер все з'ясувалося: існує IP-адреса, за допомогою якої інші комп'ютери можуть розпізнавати певний комп'ютер у мережі, і число, яке визначає якусь програму, що працює на комп'ютері. Також ставати зрозумілим і те, що дві програми на різних комп'ютерах можуть використовувати той самий порт (два будинки на різних вулицях теж можуть мати один і той самий номер, чи ні?). Ну що ж, ми практично біля мети, тільки щоб трохи вас налякати, давайте виведемо формулу:

IP-адреса = унікально визначає комп'ютер у мережі.
Порт-число = унікально визначає програму, яка виконується на комп'ютері.

Якщо скласти разом вище описані рівняння, то отримаємо:

IP-адреса + порт-число = _____

Іншими словами:

Унікально визначає програму у мережі. Якщо ви здогадалися до цього самі – значить, мої зусилля не зникли даремно. Якщо ні, тоді прочитайте знову все спочатку або скористайтеся Google для пошуку кращої статті. _____ - це і є ... СОКЕТ!

Підіб'ємо підсумок, сокет - це комбінація IP-адреси та порту. Сокет адреса надає можливість іншим комп'ютерам у мережі знаходити певну програму, яка виконується певному комп'ютері. Ви можете відображати сокет адресу ось так 64.104.137.58:80, де 64.104.137.58 – IP-адреса та 80 – порт.

Як програмувати з використанням сокетів?

Достатньо про теорію, давайте перейдемо до дій. Розробимо дуже простий і наочний код Java, який продемонструє можливості використання сокетів. Спробуємо реалізувати наступний перелік дій:

1) Одна програма Джава буде намагатися зв'язатися з іншою програмою Java (яка відчайдушно чекає когось, щоб з нею зв'язався). Назвемо першу програму Клієнтом, а другу Сервером.

2) Після успішного зв'язування з сервером клієнт чекає введення даних від вас і відсилає текст серверу.

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

4) Отриманий від сервера текст, покупець показує вам, щоб показати вам думка сервера про вас. Чи приготувалися приступити до розробки? Почнемо. Зазначу тільки, що я не навчатиму вас програмування на Java з чистого аркуша, а лише поясню код, який відноситься до сокетів. Створіть 2 нові програми Джава і назвіть їх Server.java і Client.java. Я навів код нижче, тільки не лякайтеся, я все поясню.


import java.net.*;
import java.io.*;
public class Server (
int port = 6666; // випадковий порт (можливо будь-яке число від 1025 до 65535)
try (
ServerSocket ss = новий ServerSocket(port); // створюємо сокет сервера і прив'язуємо його до вказаного вище порту
System.out.println("Waiting for a client...");

Socket socket = ss.accept(); // змушуємо сервер чекати на підключення і виводимо повідомлення коли хтось зв'язався з сервером
System.out.println("Got a client:) ... Нарешті, деякий один я можу через все, що я cover!");
System.out.println();

// Беремо вхідний та вихідний потоки сокету, тепер можемо отримувати та надсилати дані клієнту.


String line = null;
while(true) (
line = in.readUTF(); // Чекаємо поки клієнт надішле рядок тексту.
System.out.println("The dumb client just sent me this line: " + line);
System.out.println("I"m sending it back...");
out.writeUTF(line); // Відсилаємо клієнту назад той самий рядок тексту.
System.out.println("Waiting for the next line...");
System.out.println();
}
) catch(Exception x) ( x.printStackTrace(); )
}
}

Import java.net.*;
import java.io.*;

public class Client (
public static void main(String ar) (
int serverPort = 6666; // Тут обов'язково потрібно вказати порт якого прив'язується сервер.
String address = "127.0.0.1"; // це IP-адреса комп'ютера, де виконується наша серверна програма.
// Тут вказано адресу того самого комп'ютера, де буде виконуватися і клієнт.

Try (
InetAddress ipAddress = InetAddress.getByName(address); // створюємо об'єкт, який відображає вищеописану IP-адресу.
System.out.println("Any of you heard of a socket with IP address " + address + " and port " + serverPort + "?");
Socket socket = новий Socket (ipAddress, serverPort); // Створюємо сокет використовуючи IP-адресу і порт сервера.
System.out.println("Yes! I just got hold of the program.");

// Беремо вхідний та вихідний потоки сокету, тепер можемо отримувати та надсилати дані клієнтом.
InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

// Конвертуємо потоки на інший тип, щоб легше обробляти текстові повідомлення.
DataInputStream in = new DataInputStream(sin);
DataOutputStream out = New DataOutputStream(sout);

// Створюємо потік читання з клавіатури.
BufferedReader keyboard = New BufferedReader(New InputStreamReader(System.in));
String line = null;
System.out.println("Type in something and press enter. Will send it to the server and tell ya what it thinks.");
System.out.println();

While (true) (
line = keyboard.readLine(); // чекаємо, поки користувач введе щось і натисне кнопку Enter.
System.out.println("Sending this line to the server...");
out.writeUTF(line); // надсилаємо введений рядок тексту серверу.
out.flush(); // Примушуємо потік закінчити передачу даних.
line = in.readUTF(); // Чекаємо поки сервер відішле рядок тексту.
System.out.println("The server was very polite. It sent me this: " + line);
System.out.println("Looks like the server is pleased with us. Go ahead and enter more lines.");
System.out.println();
}
) catch (Exception x) (
x.printStackTrace();
}
}
}

Тепер скомпілюємо код:

Javac Server.java Client.java

Відкриємо два командні вікна (DOS). В одному вікні введемо:

Java Server

а в іншому:

Java Client

Обов'язково у такому порядку.

Тепер введіть рядок тексту у вікні, де запущено клієнта, та натисніть кнопку Enter. Спостерігайте за двома вікнами і побачите, що станеться. Наприкінці, натисніть Ctrl-C у тому щоб зупинити програми.

Пояснення коду роботи із сокетами

Заглибимося тепер трохи в код. Ви вже вловили деякі ідеї, прочитавши коментарі, але проаналізуємо кілька критичних рядків коду.

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

ServerSocket ss = новий ServerSocket(port);
Socket socket = ss.accept();

Клас ServerSocket трохи відрізняється від класу Socket. Клас Socket – це сокет. Головна відмінність ServerSocket полягає в тому, що він вміє змушувати програму чекати на підключення від клієнтів. Коли ви створюєте, потрібно вказувати порт, з яким він буде працювати, і викликати його метод accept(). Цей метод змушує програму чекати на підключення по вказаному порту. Виконання програми зависає у цьому місці, доки клієнт не підключиться. Після успішного підключення клієнтом, створюється нормальний Socket об'єкт, який ви можете використовувати для виконання всіх існуючих операцій з сокетом. Зауважимо також, що цей Socket об'єкт відображає інший кінець з'єднання. Якщо ви хочете надіслати дані клієнту, то ви не можете використовувати для цього свій власний сокет.

Наступним розглянемо Socket клас. Ви можете створити Socket об'єкт, вказавши IP-адресу та порт. Ви можете використовувати InetAddress клас для відображення IP-адреси (цей спосіб кращий). Для створення об'єкта InetAddress використовуйте наступний метод:

InetAddress ipAddress = InetAddress.getByName(address);

Зауважимо, що у нашій програмі ми використовували адресу 127.0.0.1. Ця спеціальна адреса називається адреса замикання – вона просто відображає локальний комп'ютер. Якщо ви маєте намір запустити клієнт і сервер на різних комп'ютерах, тоді потрібно використовувати відповідну IP-адресу сервера.

Після того як ми створили InetAddress, можна створити Socket:

Socket socket = новий Socket (ipAddress, serverPort);

Після створення Socket об'єкта, можна взяти вхідний та вихідний потоки сокету. Вхідний потік дозволить читати з сокету, а вихідний потік дає можливість писати в сокет.

InputStream sin = socket.getInputStream();
OutputStream sout = socket.getOutputStream();

Наступні рядки просто конвертують потоки до інших типів потоків. Після цього нам буде легше працювати з String об'єктами. Цей код нічого не робить із мережею.

DataInputStream in = new DataInputStream(sin);
DataOutputStream out = New DataOutputStream(sout);

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