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

Всю роботу в мережі інкапсульовано в пакеті java.net. В Java є два класи, які дозволяють створювати мережні програми на основі сокетів: Socket та ServerSocket.

Клас Socket

Клас Socket використовується для звичайного двобічного обміну даними. Він має чотири конструктори:

public Socket (String host, int port) throws UnknownHostException, IOException,

де host – це адреса комп’ютера, з яким треба установити зв’язок; port – номер порта на комп’ютері, з яким треба установити зв’язок. Наприклад,

try {

Socket socket = new Socket(“10.0.9.1”, 4444);

. . .

}

catch (. . .){}

catch (. . .){}

Другий конструктор першим параметром отримує змінну класу InetAddress:

public Socket (InetAddress address, int port) throws UnknownHostException, IOException

Об’єкт класу InetAddress містить IP-адресу комп’ютера в мережі. Цікаво, що цей клас не має конструкторів, але має цілу низку так званих фабричних (статичних) методів, які повертають екземпляри класу InetAddress. Наприклад,

try {

InetAddress address=InetAddress.getByName(“10.0.9.1”);

Socket socket = new Socket(address, 4444);

}

catch (. . .){}

catch (. . .){}

Звичайно, в цьому прикладі створення сокета можна було б зробити в одному операторі. Також заміть IP- можна використовувати DNS-адресу.

Третій та четвертий конструктори аналогічні першим двом, але мають ще додатковий третій параметр, який дозволяє задати бульове значення. Якщо воно встановлюється в true, використовується протокол на основі потоків даних (наприклад, TCP, як і за замовчуванням в перших двох кон­структорах), якщо в false – протокол на основі дейтаграм (наприклад, UDP).

Клас Socket також має методи, що дозволяють читати та писати в нього: getInputStream() і getOutputStream(), які повертають потоки введення та виведення. Для зручності використання ці потоки звичайно над­будо­ву­ються добре відомими вам класами DataInputStream і PrintStream відповідно. І метод getInputStream(), і метод getOutputStream() викидає виняток IOException, який треба перехопити та обробити.

try

{ Socket socket = new Socket(“10.0.9.1”);

DataInputStream input = new DataInputStream(socket.getInputStream());

PrintStream output = new PrintStream(socket.getOutputStream());

}

catch (UnknownHostException e) {

System.err.println(“Unknown host: “ + e.toString()

System.exit(1);

}

catch (IOException e)

{

System.err.println(“Failed I/O: “ + e.toString()

System.exit(1);

}

Наразі, щоб послати повідомлення та отримати відповідь на один рядок достатньо використати надбудовані потоки

output.println(“test”);

String response input.readLine();

Методи getInetAddress() і getPort() дозволяють визначити адресу IP і номер порту, пов'язані з даним сокетом (для видаленого вузла):

public InetAddress getInetAddress();public int getPort();

Метод getLocalPort() повертає для даного сокета номер локального порту:

public int getLocalPort();

Завершуючи обмін даними треба закрити потоки, а потім і сам сокет:

output.close();

input.close();

socket.close();

Клас ServerSocket

Щоб створити сокет TCP, необхідно скористатися класом, який дозволяє прив’язатися до порта та очікувати підключення клієнтів. При кожному підключенні буде створено екземпляр класу Socket. ServerSocket має два конструктори:

public ServerSocket(int port) throws IOException;

public ServerSocket(int port, int count) throws IOException;

Перший з них створює сокет, підключений до вказаного порта. За замовчуванням в черзі очікування підключення може знаходитися до 50 клієнтів. Другий конструктор дозволяє задавати довжину черги.

Після створення об’єкта ServerSocket можна використовувати метод accept() для очікування підключення клієнтів. Цей метод блокується до момента підключення клієнта, а потім повертає об’єкт Socket для зв’язку з клієнтом. Блокування означає, що програма входить у внутрішній нескін­чений цикл, який завершується за певних умов. Іншими словами, поки клієнт не підключиться, наступний за accept() оператор виконуватися не буде.

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

try {

ServerSocket server = newServerSocket(4444);

Socket con = server.accept();

DataInputStream inp=new DataInputStream(cont.getInputStream());

PrintStream output = new PrintStream(cont.getOutputStream());

}

catch (IOException e)

{

System.err.println(“Failed I/O: “ + e.toString()

System.exit(1);

}

  1. Створення сокетів UDP

 

Для роботи з сокетами UDP додаток має створити сокет на базі класу DatagramSocket, а також підготувати об’єкт класу DatagramPacket, в який буде занесено блок даних для прийому/передавання.

Канал, а також вхідні та вихідні потоки створювати не треба. Дані передаються та приймаються методами send() і receive(), визначеними в класі DatagramSocket.

Клас DatagramSocket

Розглянемо конструктори та методи класу DatagramSocket, при­зна­че­ного для створення та використання сокетів UDP або дейтаграмних сокетів.

В класі DatagramSocket визначено два конструктора:

public DatagramSocket(int port);

public DatagramSocket();

Перший з цих конструкторів дозволяє визначити порт для сокета, інший припускає використання будь-якого вільного порта.

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

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

Звичайно перший з цих конструкторів використовують для серверів, які, як правило, знають самі і мають повідомити клієнтам номер свого порта, другий – для клієнтів.

До речі, за допомогою метода getLocalPort() додаток завжди може довідатися номер порта, що його закріплено за даним сокетом:

public int getLocalPort();

Прийом і передавання даних на дейтаграмному сокеті виконується за допомогою методів receive() і send() відповідно:

public void receive(DatagramPacket p);

public void send(DatagramPacket p);

Як параметр цим методам передається посилання на пакет даних, визначений як об’єкт класа DatagramPacket.

Ще один метод в класі DatagramSocket, яким ви маєте скористатися, це метод close(), призначений для закриття сокета:

public void close();

Клас DatagramPacket

Перед тим, як приймати або передавати дані з використанням методів receive() і send(), ви маєте підготувати об’єкти класу DatagramPacket. Метод receive() запише в такий об’єкт прийняті дані, а метод send() – перешле дані з об’єкта класу DatagramPacket вузлу, адреса якого указана в пакеті.

Підготовка об’єкта класу DatagramPacket для прийому пакетів виконується за допомогою такого конструктора:

public DatagramPacket(byte buf[], int length);

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

Якщо вам треба підготувати пакет для передавання, скористайтеся конструктором, який додатково дозволяє задати адресу IP addr и номер порта port вузла призначення:

public DatagramPacket(byte buf[], int length, InetAddress addr, int port);

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

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

Метод getData() повертає посилання на масив даних пакета:

public byte[] getData();

Розмір пакета, дані з якого зберігаються в цьому масиві, легко визначити за допомогою метода getLength():

public int getLength();

Методи getAddress() і getPort() дозволяють визначити адресу та номер порта вузла, звідки прийшов пакет, або вузла, для якого призначено пакет:

public InetAddress getAddress();

public int getPort();

Якщо ви створюєте кліент-серверну систему, в якій сервер має заздалегідь відому адресу та номер порта, а клієнти довільні адреси та різні номери портів, то після отримання пакета від клієнта сервер може визначити за допомогою методів getAddress() і getPort() адресу клієнта для встановлення з ним зв’язку.