воскресенье, 30 сентября 2012 г.

Архитектура "клиент-сервер". Кроссплатформенность или программированиедля ленивых.

Java_Logo_like_cup_of_coffie_named_java

Привет, читака! Сегодня у нас на повестке ненавистная многими Java SE. Для далеких от предмета (но интересующихся) далее краткий экскурс.

Дисклеймер: данная статья будет скорее интересна новичкам для поиска вдхновения и ответа на тривиальные вопросы.

Java SE, т.е. Standard Edition, означает классическое приложение, работающее под управлением местной Java-машины (или JVM, т.е. среды, которое исполняет java-приложение на данном компьютере). Эта штука может обитать на всех популярных ОС: Windows,*nix, MacOS и др(мобильной сферы и web-апплетов мы касаться не будем здесь) благодаря пакету jre (Java Runtime Environment), или его НАДмножеству - пакету для разработчиков - jdk (Java Development Kit).

Предыстория

Моя версия далее происходящего, если хотите :-) По учебе мне необходимо сделать простейшее соединение (пока что лаба №1) по VPN(Virtual Private Network, виртуальаня частная сеть - шифрованный канал поверх публичной сети интернет) между Linux-вервером и Windows-клиентом, и как-то его использовать. В предлагаемом нам варианте - это кривой мануал по настройке Samba + 1C_server + PostgreSQL. Мало приятное занятие (лично мне кажущееся бестолковым). Своим путем я избрал написание некоторого приложения в архитектуре "клиент-сервер". А точнее - двух приложений, первое из которых будет поставщиком данных для второго. Данные будут черпаться серверной частью, по задумке, из БД по некоторым критериям, которые запросит клиентская часть.

На рисунке идея моей работы) Серверная часть и БД находится на одной машине (Linux Debian), а клиентская часть - на другой (Windows XP SP3). Клиент формирует запрос (посредством GUI и логики программы), запрос летит к серверу (I), который формирует SQL-запрос (или вызов хранимой процедуры с параметрами), взаимодействует с БД (II), получает от нее выборку с данными (III), из них формирует какие-то сущности бизнесс-логики (объекты, с ними же удобнее работать, правда?) и отправляет их обратно клиенту (IV). Такой подход нужен по следующим причинам: клиентов может быть много, при изменении источника данных (вместо БД будут тектовые файлы/XML/веб-сайты/другие программы и т.п.) достаточно переписать логику одного лишь сервера. Абстракция от источника данных, инкапсуляция, это тот-самый "черный ящик", бро ;-)

Сразу проблема - кроссплатформенность. Куда проще писать обе части приложения на одном языке. С Mono связываться не хочется (последующие лабы будут с FreeBSD, что делать там - хз), поэтмоу мой любимый C# .Net идет к черту. Для C/С++ - слишком уж низкий уровень и, следоветельно, платформенно-ориентированная работа с сокетами, а фреймворков, вроде Qt я не знаю. Какой мой выбор? Конечно Java! Вот именно этот мой опыт здесь мы далее и разберем...

Передача данных

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

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

class TCPServer
{
    public static void main(String argv[]) throws Exception
    {
        final int PORT_NUMBER = 6789;   // Номер порта для соединения
        String clientSentence;          // Переменная под строку клиента
        
        // Создаем сокет сервера для подключения клиента
        ServerSocket welcomeSocket = new ServerSocket(PORT_NUMBER);
        System.out.println("Ожидание подключения...");
        
        // Ждем подключение клиента к сокету
        Socket connectionSocket = welcomeSocket.accept();
        
        // Создаем текстовый поток ввода из готового сокета
        BufferedReader inFromClient =
           new BufferedReader(new InputStreamReader(connectionSocket.getInputStream()));
        
        // Читаем строку из потока
        clientSentence = inFromClient.readLine();
        inFromClient.close();
        System.out.println("Получено сообщение: " + clientSentence);
    }
}

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

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

class TCPClient
{
  public static void main(String argv[]) throws Exception
  {
    final int PORT_NUMBER = 6789;   // Номер порта для соединения
    String sentence = "Hello to Server from Client!";

    /* Подключаемся к серверу
     * Обращаю внимание на надпись "localhost", т.к. на период
     * тестирования программа-сервер и клиент запускаются на одном компьютере!
     * Иначе необходимо написать IP-адресс сервера в ковычках
    */
    Socket clientSocket = new Socket("localhost", PORT_NUMBER);
    
    // Создаем БАЙТОВЫЙ поток для отправки сообщения...
    DataOutputStream outToServer = 
            new DataOutputStream(clientSocket.getOutputStream());
    // Записываем наше сообщение + символ конца строки в поток(переводя в байты)        
    outToServer.writeBytes(sentence + '\n');
    // Закрываем соединение
    clientSocket.close();
  }
}

Теперь необходимо скомпилировать и запустить проект сервера, затем проект клиента.

Передача объектов

Здесь мы сэмитируем то, что сервер выполнил какие-то операции с базой данных, на основе выборки данных сформировал какой-то объект, который теперь нужно как-то передать клиенту. Далее привожу пример класса DataMessage этого-самого объета. У него есть поля - строка и целочисленная переменная.

package tcpserver;

import java.io.Serializable;

public class DataMessage implements Serializable {
    private String strData;
    private int intData;
    
    public DataMessage(String strArg, int intArg){
        strData=strArg;
        intData=intArg;
    }
    
    public String GetStr(){ return strData;}
    public int GetInt(){ return intData;}
}

Как видите, класс помечен маркерным интерфейсом java.io.Serializable, который позволяет нам объект сериализовать, т.е. закодировать в байтовое представление, которое можно передать по сети (сохранить в файл и т.п.), чтобы потом восстановить (десериализовать).

Также хочу заметить, что данный класс относиться к пакету (а, соответственно, и проекту) tcpserver, чтобы была возможность снова "собрать" объект на стороне клиента, необходимо подключить классы проекта TCPServer к проекту TCPClient. В IDE Netbeans это делается так: панель проект-вьюер (по умолчанию слева-вверху), на ней (в проекте клиента) правый клик на "библиотеки", выбрать "добавить проект" и выбрать проект TCPServer.

Далее текст сервера
package tcpserver;

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

class TCPServer
{
    public static void main(String argv[]) throws Exception
    {
        final int PORT_NUMBER = 6789;
        ServerSocket welcomeSocket = new ServerSocket(PORT_NUMBER);

        System.out.println("Ожидание подключения...");
        Socket connectionSocket = welcomeSocket.accept();
        
        DataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream());
        ObjectOutputStream outObjStream = new ObjectOutputStream(outToClient);
        
        DataMessage obj = new DataMessage("Hello from server!", 333);
        outObjStream.writeObject(obj);
        outObjStream.flush();
        outObjStream.close();
        System.out.println("Сообщение передано!");
      }
}
Клиент

package tcpclient;

import java.io.*;
import java.net.*;
import tcpserver.DataMessage;

class TCPClient
{
  public static void main(String argv[]) throws Exception{
      final int PORT_NUMBER = 6789;
      Socket clientSocket = new Socket("localhost", PORT_NUMBER);
      DataInputStream inFromServer = new DataInputStream(clientSocket.getInputStream());
      ObjectInputStream inObjStream = new ObjectInputStream(inFromServer);
      DataMessage dm = (DataMessage)inObjStream.readObject();
      clientSocket.close();
      
      if (dm != null)
          System.out.println("Объект получен!\nString: "+dm.GetStr()+"\nInteger value: "+dm.GetInt());
      else
          System.out.println("Объект НЕ получен!");
  }
}

Как ты заметил, теперь мы используем интересный оберточный класс потока для передачи объектов - ObjectInputStream. Запустите сначала сервер, затем клиент. Смысл программного комплекса аналогичен первому варианту.

1 комментарий:

  1. Babyliss nano titanium flat iron - Titanium Arts
    The top part of this glass frame and metal core titanium bikes is a clear acrylic acrylic surface that is rainbow titanium a touch smoother and smooth. The samsung galaxy watch 3 titanium top part of titanium necklace this glass ion chrome vs titanium frame and

    ОтветитьУдалить