Цель работы: научиться создавать распределенные приложения на основе протоколов TCP и UDP с помощью API java.net.
Пакет java.net API предоставляет возможность для реализации сетевого взаимодействия с применением одного из двух транспортных протоколов: UDP или TCP.
Стек протоколов TCP/IP стал результатом исследований и разработок в экспериментальной сети с коммутацией пакетов под названием ARPANET (Advanced Research Projects Agency Network), компьютерной сети, созданной в 1969 году агентством министерства обороны США по перспективным исследованиям (DARPA) и явившейся прототипом сети Интернет [3].
Распределенные приложения обеспечивают обмен данными между двумя и более компьютерными системами, объединенными в сеть. В широком смысле распределенная система может охватывать два исполняемых модуля, которые нуждаются в обмене данными, даже если они функционируют на одной и той же физической машине [4].
Передача данных от одного приложения другому (или от одного компонента другому в рамках одного распределенного приложения) включает в себя, во-первых, получение данных тем узлом, на котором находится приложение-адресат, и, во-вторых, получение данных именно тем выполняющимся на узле-адресате приложением, которому они предназначены (одновременно на узле могут выполняться несколько приложений!).
Напомним пять основных уровней в задаче обмена информацией:
· физический уровень;
· уровень доступа к сети (сетевой);
· межсетевой уровень;
· транспортный уровень;
· уровень приложений.
На физическом уровне находится физический интерфейс между устройством передачи данных и передающей средой. На этом уровне задаются характеристики передающей среды, природа сигналов, скорость передачи данных и т.д.
Уровень доступа к сети связан с обменом данными между конечной системой (рабочей станцией, сервером и т.д.) и сетью, к которой подсоединена эта система. Узел-отправитель должен передать в сеть адрес узла-адресата, чтобы сеть могла направить данные по месту требования. Этот уровень обеспечивает доступ к сети и маршрутизацию данных между двумя подключенными к сети оконечными системами.
В тех случаях, когда устройства подключены к разным сетям, нужны дополнительные процедуры, позволяющие данным переходить из одной сети в другую. Такие функции относятся к межсетевому уровню. На этом уровне функции межсетевой маршрутизации предоставляются с помощью Internet-протокола (IP). Internet-протокол может быть реализован не только в конечных системах, но и в маршрутизаторах.
Надежность обмена данных означает, что все данные будут переданы приложению-адресату, и эти данные будут получены в том порядке, в котором они были отправлены. Механизмы обеспечения надежности выделяются в один общий транспортный уровень, совместно используемый всеми приложениями. В то время как задачей уровня межсетевого взаимодействия, к которому относится протокол IP, является передача данных между сетевыми интерфейсами в составной сети, главная задача транспортного уровня заключается в передаче данных между прикладными процессами, выполняющимися на компьютерах в сети. Транспортный уровень может быть реализован одним из двух протоколов:
- протоколом управления передачей (Transmission Control Protocol - TCP), описанным в стандарте RFC 793;
- протоколом пользовательских дейтаграмм (User Datagram Protocol – UDP), описанным в стандарте RFC 768.
Напомним, что мы занимаемся разработкой уровня приложения. Для организации обмена информацией, каждый элемент системы должен иметь уникальный адрес. При этом нужно указать два уровня адресации. Каждый узел сети должен обладать своим уникальным глобальным сетевым адресом, что позволит доставить данные соответствующему узлу. И каждый процесс (компонент) на узле должен иметь адрес, который был бы уникальным в пределах этого узла, что даст возможность транспортному протоколу (TCP) доставить данные нужному процессу. Этот адрес – адрес процесса - в терминологии протоколов семейства TCP/IP называют портом.
Прикладной процесс однозначно определяется в пределах сети парой (IP-адрес, номер порта), называемой сокетом (socket). Различают UDP-сокеты и TCP-сокеты[5].
Для большинства приложений, выполняющихся в рамках архитектуры протокола TCP/IP, протоколом транспортного уровня выступает TCP. Этот протокол обеспечивает надежное соединение для передачи данных от одного приложения к другому. Протокол UDP предоставляет сервис без установления соединения, он не гарантирует доставку, сохранение последовательности при приеме переданных пакетов или защиту от их дублирования. Он позволяет процессу лишь отправлять сообщения другим процессам, с помощью минимального протокольного механизма. По сути, он лишь добавляет к протоколу IP некоторые возможности адресации портов [5].
Рассмотрим вкратце возможности, которые предоставляет нам java и ее пакет java.net для разработки распределенных приложений.
Рассмотрим API, использующий протокол UDP. Для обеспечения надежной передачи, необходимо организовывать надстройку над этим протоколом, обеспечивающую, например, нумерацию пакетов, повторную передачу пакетов при истечении времени ожидания и т.д. Длина одного сообщения (одной датаграммы) при использовании этого протокола ограничена 65536 байтами, причем многие реализации вообще ограничивают размер датаграммы 8Кб. В случае необходимости пересылки порции данных большего размера они должны быть разбиты на куски отправителем и снова собраны получателем.
При работе с сокетами в Windows чтение данных из сокета или запись в него могут осуществляться асинхронно, не прекращая выполнения другого кода в сетевом приложении. Такое соединение называется неблокирующим. Поддержка сокетов в Windows посылает сообщение в тот момент, когда данные становятся доступны. Альтернативный подход – использование блокирующих соединений обеспечивает, что сетевое приложение ждет завершения операции чтения или записи перед выполнением следующей строки кода. При работе с блокирующими соединениями необходимо создавать поток на сервере и, как правило, работать с потоком на стороне клиента [6].
Передача сообщения при использовании протокола UDP - неблокирующая, прием - блокирующий с возможностью прерывания по истечении времени ожидания.
Для работы с UDP в пакете java.net определены следующие классы:
- DatagramPacket (датаграмма). Конструктор этого класса принимает массив байт и адрес процесса-получателя (IP-адрес узла и порт). Класс предназначен для представления единичной датаграммы (сообщения). Этот класс используется как для создания сообщения с целью последующей его отправки, так и при приеме сообщения (функция приема возвращает экземпляр этого класса).
- DatagramSocket. Предназначен для посылки/приема UDP-датаграмм. Один из конструкторов принимает в качестве аргумента порт, с которым связывается сокет, другой конструктор, без аргументов, задействует в качестве порта первый попавшийся свободный порт. Класс имеет методы send и receive, для, соответственно, передачи и приема датаграмм. Метод setSoTimeout устанавливает тайм-аут для операций сокета.
Ниже приведены две простые программы, использующие рассмотренные механизмы для организации взаимодействия [2].
Первая программа создает сокет, соединяется с сервером (порт 7777), пересылает ему сообщение и ждет ответа (листинг 1).
1 import java.net.*;
2 import java.io.*;
3 public class UDPClient{
4 public static void main (String args[]){
5 // аргументы - сообщение и адрес сервера
6 try {// create socket
7 DatagramSocket aSocket = new DatagramSocket();
8 byte [] message = args[0].getBytes();// DNS lookup
9 InetAddress aHost = InetAddress.getByName(args[1]);
10 int serverPort = 7777;
11 DatagramPacket request =
12 new DatagramPacket(message, args[0].length(), aHost, serverPort);
13 aSocket.send(request);
14 //send message
15 byte[] buffer = new byte[1000];
16 DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
17 aSocket.receive(reply);
18 String str=new String(reply.getData());
19System.out.println("REPLY:"+str.replace("Server","Client"));
20 aSocket.close();
21 } catch (SocketException e){ System.out.println("Socket: " + e.getMessage());
22 // socket creation failed
23 } catch (IOException e){ System.out.println("IO: " + e.getMessage());
24 // ошибка при приеме
25 }
26 }
27 }
Листинг 1. Клиент
Класс UDPClient содержит единственный статический метод main, являющийся точкой входа в программу. Метод принимает массив аргументов командной строки, переданных программе при запуске. В качестве аргументов для нашей программы должны быть переданы строка сообщения, которая будет оправлена на сервер и адрес узла, на котором запущена программа-сервер. Порт не передается, поскольку в нашем случае он заранее известен. Далее создается сокет для передачи сообщения (строка 7), затем происходит разрешение переданного в качестве аргумента командной строки имени узла сервера в адрес (строка 9). Затем создается датаграмма (строки 11-12), в конструкторе которой передается массив, составляющий передаваемые данные, адрес узла, на котором выполняется сервер и порт, который нам известен заранее. Пакет передается на сервер (строка 13). Адрес получателя определяется в пакете, а не в сокете, через который мы его передаем. Т.е. один и тот же сокет может быть использован для передачи пакетов разным узлам, и этот же сокет может применяться для приема пакета от сервера (строка 17). После использования сокет должен быть закрыт (строка 20).