Опубликован: 10.10.2010 | Доступ: свободный | Студентов: 3197 / 288 | Оценка: 4.14 / 3.32 | Длительность: 13:16:00
ISBN: 978-5-9963-0444-8
Специальности: Системный архитектор
Лекция 3:

Использование API java.net

< Лекция 2 || Лекция 3: 123 || Лекция 4 >

Использование протокола TCP

Другим подходом к организации взаимодействия является использование API, основанных на идеологии потоков ввода-вывода. Примером могут служить TCP-Streams. В отличие от UDP, это семейство протоколов обеспечивает гарантированную доставку сообщений в порядке, соответствующем порядку отправки (естественно, это достигается за счет больших накладных расходов, поскольку базовым протоколом является тот же датаграммный протокол). В рамках этого подхода программисту предоставляется абстрактный сервис, основанный на использовании потоков. Для передачи данных программист создает TCP -сокет, извлекает из него связанный с ним поток (с сокетом связано два потока - поток ввода и поток вывода) и пишет (читает) данные в поток (из потока). В отличие от подхода, основанного на использовании датаграмм, в этом случае не накладывается никаких ограничений на размер сообщения. Кроме того, рассматриваемый подход является ориентированным на соединение, опять же в отличие от первого подхода. Реализация протокола основана на повторной передаче данных, в случае если истек тайм-аут по ожиданию от получателя подтверждения о приеме.

Классы:

  • ServerSocket - сокет на стороне сервера;
  • Socket - класс для работы с соединением (клиент и сервер). Имеет конструктор для создания сокета и соединения с удаленным узлом и портом, методы для работы с входными и выходными потоками.

Ниже приведена простая программа-клиент, иллюстрирующая использование рассмотренных классов.

TCPClient

1  import java.net.*;
2  import java.io.*;
3  public class TCPClient {
4  public static void main (String args[]) {
5  Socket s = null;
6  try {
7  int serverPort = 7896;
8  s = new Socket(args[1], serverPort);
9  DataInputStream in = new DataInputStream( s.getInputStream());
10  DataOutputStream out =new DataOutputStream( s.getOutputStream());
11  out.writeUTF(args[0]); // UTF is a string encoding
12  String data = in.readUTF(); // read a line of data from the stream
13  System.out.println("Received: "+ data) ;
14  } catch (UnknownHostException e) {System.out.println("Socket:" + e.getMessage());
15  } catch (EOFException e) {System.out.println("EOF:" +e.getMessage());
16  } catch (IOException e) {System.out.println("readline:" +e.getMessage());
  // error in reading the stream
17  } finally {
18  if(s!=null) try {s.close();} catch (IOException e) 
  {System.out.println("close:" + e.getMessage());}}
19  }
20  }
Листинг 3.3. Клиент

В первых строках (строки 1,2) программы (пример 3.3) импортируются пакеты java.net и java.io - соответственно, пакет, содержащий API TCP, и пакет, содержащий классы ввода-вывода. Класс TCPClient имеет единственный метод main,который также является точкой входа в клиентскую программу. Метод принимает аргументы (аргументы командной строки), первый из которых рассматривается как строка, передаваемая клиентом серверу, второй - как имя (адрес) сервера. Поскольку создание соединения и передача по нему данных сопряжена с возможностью ошибок, остальные действия производятся в блоке try - catch.

Переменная s, объявленная в строке 5, инициализируется в строке 8, где создается соединение с сервером. Для соединения требуются два параметра - имя сервера и порт. Имя сервера передано нам из командной строки, порт нам известен. В строках 9 и 10 создаются потоки ввода и вывода, с помощью которых можно взаимодействовать с сервером - передавать и принимать данные. В данном случае используются не базовые классы InputStream и OutputStream,а их более специализированные потомки DataInputStream и DataOutputStream, которые содержат готовые методы чтения/записи для всех базовых типов данных Java. Затем производится отправка полученной строки на сервер, после чего клиент ожидает получения информации от сервера (строка 12). Метод чтения данных из потока - блокирующий. Прочитав строку, посланную сервером, клиент печатает ее на экране и завершает работу.

Здесь стоит сделать небольшое отступление. Дело в том, что нам очень повезло: в нашем примере и клиент, и сервер реализованы на одной и той же программной платформе - на java. В реальных ситуациях дело может осложняться тем, что различные компоненты распределенного приложения могут быть реализованы на различных не только программных, но и аппаратных платформах. При передаче данных между такими "разными" компонентами возникает проблема их интерпретации - ведь форматы представления могут быть различными для разных платформ.

Представление данных в распределенных системах

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

В предыдущем разделе мы научились передавать пакеты данных между процессами системы. Теперь стоит поподробнее взглянуть на те данные, которые мы передаем. Нужно заметить, что передачу данных между различными компонентами распределенной системы можно представить как "перенос" некой области памяти между процессами (для простоты считаем, что каждый компонент выполнен в виде процесса), которые могут функционировать на различных физических узлах. Передаваемые данные (т.е. переменные, массивы, структуры и т.д.) определяются внутри процесса и существуют в локальной памяти процесса. Если говорить о данных внутри процесса, то они имеют какую-то физическую структуру, определяемую используемыми технологиями программирования (языком, компилятором) и аппаратной средой. Эта физическая структура отображается на логическую путем применения некоторых правил, используемых на данной конкретной платформе. Проблема состоит в том, что не существует универсальных правил - они различны для разных платформ. Так, одна и та же последовательность битов интерпретируется совершенно по-разному в случае если она представляет вещественное число с плавающей точкой и в случае если она представляет целое число. Таким образом, мало научиться просто передавать данные, принимающая сторона должна уметь их должным образом интерпретировать. Причем, даже если принимающей стороне известно (например, по порядку передачи), какого типа данные передавались, может потребоваться их дополнительная обработка, поскольку данные одного и того же примитивного типа ( int, float, char ) могут иметь различное представление на различных платформах. Таким образом, кроме работы собственно по передаче данных, необходимо еще выполнить работу по их правильной интерпретации после приема.

Для решения этой проблемы обычно применяют один из двух подходов. Первый состоит в том, чтобы передавать данные в представлении посылающей либо принимающей стороны. В этом случае необходимо дополнительное преобразование данных при их передаче либо при их приеме. Второй подход состоит в использовании некоего "внешнего" представления данных, вообще говоря, отличного от представления как отправителя, так и получателя. В этом случае для передачи необходимо выполнить уже два преобразования, а не одно: сначала на передающей стороне преобразовать данные из локального представления передающей стороны во "внешнее" (транспортное), затем на принимающей - преобразовать "внешнее" представление в локальное представление принимающей стороны.

Несмотря на кажущуюся избыточность, второй подход применяется чаще в силу его универсальности. В настоящее время разработано множество таких "внешних" транспортных форматов - SUN Microsystems XDR (eXternal Data Representation), CORBA CDR (Common Data Representation), ASN.1 (OSI layer 6) и др. Обычно эти задачи возлагаются на слой middleware, и соответствующие преобразования для программиста прозрачны. Однако если никакое промежуточное программное обеспечение не используется, разработчик должен реализовать соответствующие механизмы трансляции самостоятельно - в том случае, если предполагается совместная работа компонент, реализованных на разных платформах.

TCPServer

Сервер, принимающий данные от клиента и посылающий ему ответ, выполнен следующим образом (пример 3.4).

1  import java.net.*;
2  import java.io.*;
3    public class TCPServer {
4      public static void main (String args[]) {
5        try {
6           int serverPort = 7896; // the server port
7           ServerSocket listenSocket = new ServerSocket (serverPort); // new server port generated
8           while(true) {
9              Socket clientSocket = listenSocket.accept(); // listen for new connection
10            Connection c = new Connection(clientSocket); // launch new thread
11         }
12     } catch(IOException e) { System.out.println("Listen socket:"+e.getMessage());
13   }
14  }
15 }
Листинг 3.4. Сервер

Как и клиенту, серверу необходима реализация сетевого ввода/вывода, поэтому он импортирует соответствующие пакеты - строки 1,2. Так же как и клиент, сервер состоит из одного метода - main,выполняющего всю работу и одновременно являющегося точкой входа в программу. Первое, что делает сервер - создает серверный сокет (строка 7). Для создания серверного сокета необходим один параметр - порт, на котором сокет будет "слушать" сеть, ожидая клиентских соединений - именно по этому порту клиенты смогут затем соединиться с сервером, и, соответственно, этот порт должен указываться в конструкторах клиентских сокетов, наряду с адресом сервера. В случае если создание серверного сокета прошло успешно, сервер запускает цикл ожидания соединений от клиентов. Внутри цикла ожидания сервер вызывает метод accept (строка 9) своего серверного сокета. Этот метод является блокирующим, т.е. он возвратит управление только тогда, когда к серверу подсоединится очередной клиент. Можно сказать, что большую часть времени сервер проводит именно в методе accept,ожидая соединения клиентов. Метод accept возвращает в качестве результата своей работы класс Socket,который, наряду с сокетом, созданным на клиенте, представляет собой второй конец соединения. Начиная с этого момента, клиент и сервер могут обмениваться друг с другом данными, используя соответствующие методы потоков, связанных со своими сокетами. Поскольку наш сервер может одновременно взаимодействовать с несколькими клиентами, мы создаем класс Connection,который инкапсулирует в себе всю функциональность обслуживания соответствующего клиента, после чего снова входим в цикл ожидания подсоединения следующего клиента. Следует обратить внимание на тот факт, что использованное API позволяет серверу одновременно обслуживать несколько клиентов, поскольку для каждого клиента после его подсоединения создается собственный канал типа "точка-точка".

< Лекция 2 || Лекция 3: 123 || Лекция 4 >
Алмаз Мурзабеков
Алмаз Мурзабеков
Прохожу курс "Построение распределенных систем на Java" в третьей лекции где описывается TCPServer вылетает эта ошибка
"Connection cannot be resolved to a type"


Java version 1.7.0_05