2. Архитектура Клиент-Сервер на сокетах Java

  • В этой теме 0 ответов, 1 участник, последнее обновление 1 месяц, 1 неделя назад сделано Васильев Владимир Сергеевич.
Просмотр 0 веток ответов
  • Автор
    Сообщения
    • #6338
      @admin

      Наиболее распространённый способ создания распределённого приложения с клиент-серверной архитектурой – это использование сокетов.

      1 Понятие сокета

      Под сокетом понимается программный интерфейс для обеспечения информационного обмена между процессами.

      Следует различать клиентские и серверные сокеты. Клиентские сокеты грубо можно сравнить с оконечными аппаратами телефонной сети, а серверные — с коммутаторами. Клиентское приложение (например,браузер) использует только клиентские сокеты, а серверное (например, веб-сервер, которому браузер посылает запросы) — как клиентские, так и серверные сокеты.

      При создании сокета, необходимо определить три параметра: стиль взаимодействия, пространство имен, и протокол.

      Стиль взаимодействия контролирует, как сокет обрабатывает передаваемые данные, и определяет количество партнеров взаимодействия. Через сокеты данные передаются блоками (пакетами). Стиль взаимодействия определяет, как эти пакеты будут обработаны и как они передаются от отправителя к получателю.

      • Стили соединения гарантируют доставку всех пакетов в том порядке, в каком они были отправлены. Если во время передачи пакеты были потеряны или доставлены в неправильном порядке, получатель автоматически отправляет запрос на их повторную передачу. Стиль соединения напоминает телефонный звонок: адреса отправителя и получателя фиксируются в начале соединения, при установке подключения.
      • Стили дейтаграмм не гарантирует доставки и правильного порядка прибытия. Пакеты могут быть потеряны или переупорядочены в пути из-за сетевых ошибок. Каждый пакет должен быть помечен его адресатом, и нет гарантии, что он будет доставлен. Система гарантирует только «максимальные усилия», поэтому пакеты могут исчезнуть или прибыть в различном порядке. Сокет стиля дейтаграммы ведет себя сходно с почтой. Отправитель определяет адрес получателя для каждого индивидуального сообщения.

      Пространство имен определяет, как записаны адреса сокета (socket addresses). Адрес сокета идентифицирует один конец подключения сокета. Например, адреса сокета в локальном пространстве имен являются обычными именами файлов. В пространстве имен Интернет-адрес сокета состоит из IP-адреса главного компьютера, присоединенного к сети, и номера порта, который идентифицирует сокет среди множества сокетов на том же главном компьютере (порты с 1 по 1024 зарезервированы операционной системой).

      Протокол определяет, как передаются данные. Существуют следующие виды протоколов: TCP/IP, первичные сетевые протоколы, используемые Интернетом; сетевой протокол AppleTalk; локальный UNIX протокол взаимодействия. Не все комбинации стилей, пространств имен и протоколов поддерживаются.

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

      на стороне клиента:

      • создание клиентского сокета;
      • установка параметров сокета (IP-адрес и порт, к которым необходимо подключиться);
      • установка соединения между сокетом и удаленной конечной точкой;
      • отправка/получение информации;
      • разрыв соединения и освобождение сокета.

      на стороне сервера:

      • создание серверного сокета;
      • установка параметров серверного сокета (IP-адрес и порт, на которые ожидаются подключения);
      • перевод серверного сокета в режим отслеживания входящих соединений;
      • при наличии входящего соединения: получить отдельный сокет для работы с этим конкретным соединением;
      • отправка/получение информации;
      • по окончании работы с клиентом: разрыв соединения и освобождение сокета, привязанного к этому клиенту;
      • по окончании работы сервера: освобождение серверного сокета.

      2 Реализация сокетов на языке Java

      Для создания соединения можно использовать API (Application Programming Interface), входящий в состав пакета java.net. Этот пакет предоставляет возможность для реализации сетевого взаимодействия с применением одного из двух транспортных протоколов: UDP и TCP.

      2.1 Стек протоколов TCP/IP

      Архитектура протоколов TCP/IP, известная как стек протоколов TCP/IP, возникла в результате исследований в области протоколов и разработок, выполнявшейся в экспериментальной сети с коммутацией пакетов под названием ARPANET, которая была основана Управлением перспективных исследовательских программ Министерства обороны США (DARPA).

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

      Ввиду этого в задаче обмена информацией выделяют пять основных относительно независимых уровней:

      • физический уровень;
      • уровень доступа к сети (сетевой);
      • межсетевой уровень;
      • транспортный уровень;
      • уровень приложений.

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

      Уровень доступа к сети связан с обменом данными между конечной системой (рабочей станцией, сервером и т.д.) и сетью, к которой подсоединена эта система. Узел-отправитель должен передать в сеть адрес узла-адресата, чтобы сеть могла направить данные по месту требования.

      Уровень доступа к сети рассматривается в связи с доступом к сети и маршрутизацией данных между двумя подключенными к сети оконечными системами. В тех случаях, когда устройства подключены к разным сетям, нужны дополнительные процедуры, позволяющие данным переходить из одной сети в другую. Такие функции относятся к межсетевому уровню. На этом уровне функции межсетевой маршрутизации предоставляются с помощью Интернет-протокола (Internet ProtocolIP). Интернет-протокол может быть реализован не только в конечных системах, но и в маршрутизаторах.

      Независимо от природы приложений обмен данными должен быть надежным, т.е. хотелось бы иметь уверенность в том, что все данные попали к приложению-адресату и что эти данные получены в том порядке, в котором они были отправлены. Механизмы обеспечения надежности, по сути, независимы от природы приложений, таким образом, имеет смысл выделить такие механизмы в один общий уровень, совместно используемый всеми приложениями. Этот уровень называется транспортным. Чаще всего для него применяется протокол управления передачей (Transmission Control ProtocolTCP).

      Наконец, в уровень приложений заложена логика, необходимая для обеспечения работы различных пользовательских приложений. Приложению каждого вида нужен отдельный модуль, специально предназначенный для этого приложения. В дальнейшем мы будем заниматься разработкой приложений данного уровня.

      Итак, чтобы обмен информацией был возможен, каждый элемент системы должен иметь уникальный адрес. Фактически, нужно указать два уровня адресации. Каждый узел сети должен обладать своим уникальным глобальным сетевым адресом, это позволит доставить данные соответствующему узлу. Каждый процесс (компонент) на узле должен иметь адрес, который был бы уникальным в пределах этого узла, что даст возможность транспортному протоколу (TCP) доставить данные нужному процессу. Этот адрес в терминологии протоколов семейства TCP/IP называют портом.

      Для большинства приложений, выполняющихся в рамках архитектуры протокола TCP/IP, протоколом транспортного уровня выступает TCP. Этот протокол обеспечивает надежное соединение для передачи данных от одного приложения к другому. Кроме него существует еще один широко используемый протокол транспортного уровня, входящий в набор протоколов TCP/IP: пользовательский протокол дейтаграмм (User Datagram ProtocolUDP). Протокол UDP предоставляет сервис без установления соединения, предназначенный для процедур на уровне приложения; он не гарантирует доставку, сохранение последовательности при приеме переданных пакетов или защиту от их дублирования. Он позволяет процессу отправлять сообщения другим процессам, с помощью минимального протокольного механизма. Протокол UDP выполняет крайне ограниченный набор функций, так как работает без установления соединения, он только добавляет к протоколу IP некоторые возможности адресации портов.

      2.2 Состав пакета java.net

      2.2.1 Средства передачи по протоколу UDP

      Для работы с UDP в пакете java.net определены следующие классы.

      1. DatagramSocket — предназначен для посылки/приема UDP дейтаграмм.

      Конструкторы класса:

      • DatagramSocket() — создаваемый сокет присоединяется к любому свободному порту на локальной машине;
      • DatagramSocket (int port) — создаваемый сокет присоединяется к порту port на локальной машине;
      • DatagramSocket(int port, InetAddress addr) — создаваемый сокет присоединяется к порту port; аргумент addr – IP-адрес сервера.

      Методы класса:

      • send(DatagramPacket pack) — отправляет дейтаграмму, упакованную в пакет pack;
      • receive(DatagramPacket pack) — дожидается получения дейтаграммы и заносит ее в пакет pack;
      • setSoTimeout() — устанавливает тайм-аут для операций сокета;
      • close() — закрывает сокет.

      2. DatagramPacket — предназначен для представления единичной дейтаграммы.

      Конструкторы класса:

      • DatagramPacket(byte[] buf, int length) — служит для получения пакета длиной length, buf — буфер для получения дейтаграммы;
      • DatagramPacket(byte[] buf, int length, InetAddress addr, int port) — Создает пакет дейтаграммы длиной length, чтобы отправить его к указанному номеру порта (port) на указанном узле (address), buf — буфер, содержащий данные для передачи.

      Кроме того пакет содержит вспомогательные методы:

      • connect(InetAddress addr, int port) — установить соединение;
      • disconnect() — разъединить;
      • InetAddress.getByName(host) — получить IP-адрес хоста;
      2.2.2 Примеры UDP-соединения

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

      Пример UDP клиента:

      Приведённая в примере программа создает сокет, соединяется с сервером (порт 2100), пересылает ему сообщение и ждет ответа:

      import java.net.*;
      import java.io.*;
      public class UDPClient{
        public static void main(String args[]){
          try {
            // создаём сокет
            DatagramSocket aSocket = new DatagramSocket();
      
            // задаём строку для передачи
            String str = "Hello";
      
            // и преобразуем её в поток байт
            byte [] message = str.getBytes();
      
            // получаем IP-адрес компьютера в формате Java
            InetAddress aHost = InetAddress.getByName("localhost");
      
            // задаём порт
            int sPort = 2100;
      
            // создаём дейтаграмму, длина которой определяется длиной // сообщения
            DatagramPacket request = new  DatagramPacket(message, message.length, aHost, sPort);
      
            // передаём серверу
            aSocket.send(request);
      
            // определяем буфер под ответ сервера
            byte [] buffer = new byte[1000];
      
            // создаём дейтаграмму под ответ сервера
            DatagramPacket reply = new DatagramPacket(buffer, buffer.length);
      
            // принимаем ответ
            aSocket.receive(reply);
      
            // выводим ответ на консоль
            System.out.println("Reply: " + new String(reply.getData()));
      
            // закрываем сокет
            aSocket.close();
          } 
      
          catch (SocketException e) {
            // если не получилось создать сокет
            System.out.println("Socket: " + e.getMessage());
          } 
      
          catch (IOException e) {
            // ошибка при приеме
            System.out.println("IO: " + e.getMessage());
          }
        }
      }

      В первых строках кода происходит импорт необходимых библиотек (пакетов) классов — в нашем случае это java.net и java.io. Первый пакет содержит необходимые нам классы для работы с UDP, второй определяет необходимые классы ввода/вывода.

      Класс называется UDPClient, он содержит единственный статический метод main(), являющийся точкой входа в программу. В нём создаётся сокет и пакет с текстовым сообщением, который затем будет передан серверу. После передачи происходит ожидание ответа от сервера, полученный ответ выводится на экран.

      Адрес получателя определяется в пакете, а не в сокете, через который он передаётся. Поэтому один и тот же сокет может быть использован для передачи пакетов разным узлам, и этот же сокет может применяться для приема пакета от сервера. После использования сокет должен быть закрыт (aSocket.close()).

      Пример UDP сервера:

      Метод main() класса UDPServer создает сокет, принимает запрос клиента и возвращает клиенту полученное от него текстовое сообщение:

      import java.net.*;
      import java.io.*;
      
      public class UDPServer{
        public static void main(String args[]) {
          DatagramSocket aSocket = null;
          try{
            // создаём сокет: порт 2100
            aSocket = new DatagramSocket(2100);
      
            // резервируем буфер под клиентское сообщение
            byte [] buffer = new byte[1000];
      
            while(true){ // бесконечный цикл
              // резервируем дейтаграмму под пакет клиента
              DatagramPacket request = new DatagramPacket(buffer, buffer.length);
      
              // принимаем пакет клиента
              aSocket.receive(request);
      
              // создаём ответную дейтаграмму
              DatagramPacket reply = new DatagramPacket(request.getData(),
              request.getLength(), request.getAddress(), request.getPort());
      
              // отправляем ответ клиенту
              aSocket.send(reply);
            }
          } 
      
          catch (SocketException e) {
            // обрабатываем ошибку создания сокета
            System.out.println("Socket: " + e.getMessage());
          } 
      
          catch (IOException e) {
            // обрабатываем ошибку передачи пакета
            System.out.println("IO: " + e.getMessage());
          } 
      
          finally {
            // закрываем сокет
            if(aSocket != null) 
              aSocket.close();
          }
        } 
      }

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

      После запуска клиент отправляет строку на сервер, печатает ее в консоли и завершает работу. Сервер же продолжает ожидать очередное подключение следующего клиента.

      Сначала производится запуск сервера, а затем клиента.

      2.2.3 Средства передачи по протоколу TCP

      Классы:
      1. Socket

      Конструктор: Socket(InetAddress host, int serverPort) – создаёт клиентский сокет, аргументы IP-адрес сервера и порт;

      2. ServerSocket
      Конструктор: ServerSocket(int serverPort) – служит для создания «слушающего» сокета на сервере.

      Метод: accept() – ожидание сервером нового соединения.

      Пример (на стороне сервера):

      ServerSocket servsock = new ServerSocket(7896);
      Socket sock = servsock.accept();

      Первая строка примера создаёт «слушающий» сокет сервера (ожидающий клиента). Во второй строке метод accept() серверного сокета (servsock) возвращает соответствующий сокет (sock) для общения с клиентом.

      После сеанса связи сокеты следует закрыть, используя метод close().

Просмотр 0 веток ответов
  • Для ответа в этой теме необходимо авторизоваться.