Как связать приложение с сервером

Обновлено: 28.03.2024

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

Как должно работать:

  • есть база данных
  • есть серверное приложение, которое будет получать запросы от клиентов для выборки данных из базы данных и отсылать им, парсить данные в базу из txt -файлов или даже с страниц в интернете или эл.почты
  • клиенты (десктопный, Android, простенький веб-интерфейс

Изучаю SQL и, в принципе, понимаю как создать базу данных в MySQL. Примерно понимаю как написать серверное приложение. Для изучения Tomcat, наверное, серверное приложение и база данных должны на нём крутиться.

Суть вопроса: как серверное приложение и базу данных разместить на Tomcat'e?

Я знаю, где у меня исходники и *.classes серверного приложения и не знаю, где сохраняется база данных, если я работаю через консоль MySQL.

Как все это запустить на моём PC под Windows 10 Pro?

по-большому счёту ничего, я так себе планирую. Сейчас читаю HEAD FIRST SQL, чтобы познакомиться с БД. Через пару-тройку дней готов приступать к написанию кода. Ещё волнует вопрос как код БД коммитить на GitHab. IntellijIdea у меня настроена, но я не представляю себе в ней писать код для БД

2 ответа 2

Итак, в целях обучения можете сделать Rest сервис. Работать это будет примерно следующим образом:

Все это крутится на Tomcat'e А интерфейс это уже отдельная тема. Надеюсь, все доступно объяснил, если есть вопросы, с радостью отвечу.

PS Для подключения к БД используйте (Более подробно почитайте в гугле "JDBC mysql connection"):

Если клиенты отправляют запросы на сервер, то это отправляют Tomcat'y или серверному приложению, которое мне нужно написать?

Да, Tomcat'y, он следит за запросами(если можно так выразиться), а ваше приложение(сервер) их обрабатывает, выглядит это примерно так (если без спринга):

Этот фрагмент кода взят из реализации Сервлета, Если вы не знаете, что это очень рекомендую почитать, без сервлетов в j2ee никуда.

Спасибо за ответ. Что-то подобное я и хочу сделать. Все это крутится на Tomcat'e - вот это не знаю как реализовать. Если клиенты отправляют запросы на сервер, то это отправляют Tomcat'y или серверному приложению, которое мне нужно написать? Со Spring пока не знаком и не думаю, что охвачу это все вместе. Хотел сделать с помощью Hibernate, но тоже ещё рано, так как сначала нужно сделать ручками всё. Меня сейчас интересует где моя БД, которую я заполняю через консоль и как её и серверное приложение поставить на Tomcat. В интернетах пока не нашёл ответа.

Отлично! Теперь я понимаю, что ещё и с сервлетами нужно познакомиться, чтобы сложилась более полная картина что и к чему нужно прикрутить. А если вернуться к вопросу о БД. Вот второй день изучаю SQL и пишу в консоли MySQL. Где сохраняется эта БД? Как с ней работать из серверного приложения, которое должно обрабатывать клиентские запросы?

"jdbc:mysql://hostname:port/dbname" вот эту строку в getConnection надо заменить на jdbc:mysql://localhost:3306/MyDataBaseName, где MyDataBaseName - имя вашей базы данных, localhost значит, что сервер БД стоит на той же машине, а 3306 это порт с которым работает MySql

Для подключения базы данных уже с давних пор предлагается использовать ресурсы сервера приложений. Зачем этот вот DriverManager? Только лишь для ознакомления с основами JDBC? Но это не тот путь, который стоит использовать с tomcat и пр.

База сохраняется на сервере базы данных. И клиенту точное расположение файлов базы знать совершенно незачем, и не предусмотрено SQL такое знание. Для MySQL на Windows это может быть, например, папка C:\Program Files\MySQL\MySQL Server 5.6\data. Чем это может помочь даже не представляю.

Если достаточно хорошо знаете аглицкий для чтения мануалов, то лучше почитать документацию к tomcat, javaee.
На русском легко найти про java, про servlet-ы, про jdbc отдельно. А вместе не попадалось никогда.

Сначала подключение к БД описывается где-то в контексте tomcat-а. Есть варианты как именно, в какой файл записать эти строки (см. документацию):

Обратите внимание на url. Правильно догадались, что надо прописать jdbc:mysql://hostname:port/dbname
Считайте, что url и есть база данных. Это адрес сервера (hostname:port) и имя базы, которое дали ей при созданий. Там она и хранится.
Вполне возможно и у tomcat-а есть web-морда, в которой можно создать такое подключение, вместо ручного написания xml. Не знаю. У других серверов есть такая возможность.

Потом в WEB-INF/web.xml web-приложения помещается ссылка на этот ресурс:

Наконец, используя аннотацию @Resource , внедряем базу например в servlet:

Несмотря на обратный порядок изложения, имя ресурса ( @Resource(name) ) задаётся в программе и это имя с помощью конфигурации связывается с настоящей базой.

Возможно не всё тут правда, но как-то так.
Да. Длинновато получается. Но ничего не поделаешь - разработчику для tomcat-ов всяких надо знать все эти конфигурации (называются дескрипторы развёртывания или deployment descriptor) стандартные из javaee и специфические для конкретного сервера приложений. Зато подключение к базе задаётся не железно в коде, а гибко в конфигурации. Да и кода уже никакого нет, так - одна аннотация. Более-менее приличные IDE иногда упрощают написание этой конфигурации.

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




Конечно, сейчас уже не 2010 год, когда разработчикам приходилось использовать знаменитые паттерны A/B/C или вообще запускать AsyncTask-и и сильно бить в бубен. Появилось большое количество различных библиотек, которые позволяют вам без особых усилий выполнять запросы, в том числе и асинхронно. Эти библиотеки весьма интересны, и нам тоже стоит начать с выбора подходящей. Но для начала давайте немного вспомним, что у нас уже есть.

2013 год стал в этом плане весьма эффективным. Появились замечательные библиотеки Volley и Retrofit. Volley — библиотека более общего плана, предназначенная для работы с сетью, в то время как Retrofit специально создана для работы с REST Api. И именно последняя библиотека стала общепризнанным стандартом при разработке клиент-серверных приложений.

У Retrofit, по сравнению с другими средствами, можно выделить несколько основных преимуществ:
1) Крайне удобный и простой интерфейс, который предоставляет полный функционал для выполнения любых запросов;
2) Гибкая настройка — можно использовать любой клиент для выполнения запроса, любую библиотеку для разбора json и т.д.;
3) Отсутствие необходимости самостоятельно выполнять парсинг json-а — эту работу выполняет библиотека Gson (и уже не только Gson);
4) Удобная обработка результата и ошибок;
5) Поддержка Rx, что тоже является немаловажным фактором сегодня.

Если вы еще не знакомы с библиотекой Retrofit, самое время изучить ее. Но я в любом случае сделаю небольшое введение, а заодно мы немного рассмотрим новые возможности версии 2.0.0 (советую также посмотреть презентацию по Retrofit 2.0.0).

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

В первую очередь нам нужно подключить все выбранные библиотеки и требуемые зависимости для Retrofit:

Мы будем получать аэропорты в виде списка объектов определенного класса.

Создаем сервис для запросов:

Примечание про Retrofit 2.0.0
Раньше для выполнения синхронных и асинхронных запросов мы должны были писать разные методы. Теперь при попытке создать сервис, который содержит void метод, вы получите ошибку. В Retrofit 2.0.0 интерфейс Call инкапсулирует запросы и позволяет выполнять их синхронно или асинхронно.

Теперь создадим вспомогательные методы:

Отлично! Подготовка завершена, и теперь мы можем выполнить запрос:

Все кажется очень простым. Мы без особых усилий создали нужные классы, и уже можем делать запросы, получать результат и обрабатывать ошибки, и все это буквально за 10 минут. Что же еще нужно?

Однако такой подход является в корне неверным. Что будет, если во время выполнения запроса пользователь повернет устройство или вообще закроет приложение? С уверенностью можно сказать только то, что нужный результат вам не гарантирован, и мы недалеко ушли от первоначальных проблем. Да и запросы в активити и фрагментах никак не добавляют красоты вашему коду. Поэтому пора, наконец, вернуться к основной теме статьи — построение архитектуры клиент-серверного приложения.

В данной ситуации у нас есть несколько вариантов. Можно воспользоваться любой библиотекой, которая обеспечивает грамотную работу с многопоточностью. Здесь идеально подходит фреймворк Rx, тем более что Retrofit его поддерживает. Однако построить архитектуру с Rx или даже просто использовать функциональное реактивное программирование — это нетривиальные задачи. Мы пойдем по более простому пути: воспользуемся средствами, которые предлагает нам Android из коробки. А именно, лоадерами.

Лоадеры появились в версии API 11 и до сих пор остаются очень мощным средством для параллельного выполнения запросов. Конечно, в лоадерах можно делать вообще что угодно, но обычно их используют либо для чтения данных с базы, либо для выполнения сетевых запросов. И самое важное преимущество лоадеров — через класс LoaderManager они связаны с жизненным циклом Activity и Fragment. Это позволяет использовать их без опасения, что данные будут утрачены при закрытии приложения или результат вернется не в тот коллбэк.

Обычно модель работы с лоадерами подразумевает следующие шаги:
1) Выполняем запрос и получаем результат;
2) Каким-то образом кэшируем результат (чаще всего в базе данных);
3) Возвращаем результат в Activity или Fragment.

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

Давайте реализуем такую модель. Я опускаю подробности того, как реализована работа с базой данных, при необходимости вы можете посмотреть пример на Github (ссылка в конце статьи). Здесь тоже возможно множество вариаций, и я буду рассматривать их по очереди, все их преимущества и недостатки, пока, наконец, не дойду до модели, которую считаю оптимальной.

Примечание
Все лоадеры должны работать с универсальным типом данных, чтобы можно было использовать интерфейс LoaderCallbacks в одной активити или фрагменте для разных типов загружаемых данных. Первым таким типом, который приходит на ум, является Cursor.

Еще одно примечание
Все модели, связанные с лоадерами, имеют небольшой недостаток: для каждого запроса нужен отдельный лоадер. А это значит, что при изменении архитектуры или, например, переходе на другую базу данных, мы столкнемся с большим рефакторингом, что не слишком хорошо. Чтобы максимально обойти эту проблему, я буду использовать базовый класс для всех лоадеров и именно в нем хранить всю возможную общую логику.

Loader + ContentProvider + асинхронные запросы

Предусловия: есть классы для работы с базой данных SQLite через ContentProvider, есть возможность сохранять сущности в эту базу.

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

Тогда лоадер для загрузки аэропортов может выглядеть следующим образом:

И теперь мы наконец можем использовать его в UI классах:

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

Loader + ContentProvider + синхронные запросы

Спрашивается, зачем мы выполняли запрос асинхронно с помощью Retrofit-а, когда лоадеры и так позволяют нам работать в background? Исправим это.

Эта модель упрощенная, но основное отличие заключается в том, что асинхронность запроса достигается за счет лоадеров, и работа с базой уже происходит не в основном потоке. Наследники базового класса должны лишь вернуть нам объект типа Cursor. Теперь базовый класс может выглядеть следующим образом:

И тогда реализация абстрактного метода может выглядеть следующим образом:

Работа с лоадером в UI у нас никак не изменилась.

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

И наконец, давайте полностью устраним эти недостатки и получим универсальную и почти идеальную модель!

Loader + любое хранилище данных + синхронные запросы

Перед рассмотрением конкретных моделей я говорил о том, что для лоадеров мы должны использовать единый тип данных. Кроме Cursor ничего на ум не приходит. Так давайте создадим такой тип! Что должно в нем быть? Естественно, он не должен быть generic-типом (иначе мы не сможем использовать коллбэки лоадера для разных типов данных в одной активити / фрагменте), но в то же время он должен быть контейнером для объекта любого типа. И вот здесь я вижу единственное слабое место в этой модели — мы должны использовать тип Object и выполнять unchecked преобразования. Но все же, это не столь существенный минус. Итоговая версия данного типа выглядит следующим образом:

Данный тип может хранить результат выполнения запроса. Если мы хотим что-то делать для конкретного запроса, нужно унаследоваться от этого класса и переопределить / добавить нужные методы. Например, так:

Отлично! Теперь напишем базовый класс для лоадеров:

Этот класс лоадера является конечной целью данной статьи и, на мой взгляд, отличной, работоспособной и расширяемой моделью. Хотите перейти с SQLite, например, на Realm? Не проблема. Рассмотрим это в качестве следующего примера. Классы лоадеров не изменятся, изменится только модель, которую вы бы в любом случае редактировали. Не удалось выполнить запрос? Не проблема, доработайте в наследнике метод apiCall. Хотите очистить базу данных при ошибке? Переопределите onError и работайте — этот метод выполняется в фоновом потоке.

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

Примечание
При неудачно выполненном запросе будет выброшен Exception, и мы попадем в catch-ветку базового лоадера.

В итоге мы получили следующие результаты:
1) Каждый лоадер зависит исключительно от своего запроса (от параметров и результата), но при этом он не знает, что он делает с полученными данными. То есть он будет меняться только при изменении параметров конкретного запроса.
2) Базовый лоадер управляет всей логикой выполнения запросов и работы с результатами.
3) Более того, сами классы модели тоже не имеют понятия о том, как устроена работа с базой данных и прочее. Все это вынесено в отдельные классы / методы. Я этого нигде не указывал явно, но это можно посмотреть в примере на Github — ссылка в конце статьи.

Вместо заключения

Чуть выше я обещал показать еще один пример — переход с SQLite на Realm — и убедиться, что мы действительно не затронем лоадеры. Давайте сделаем это. На самом деле, кода здесь совсем чуть-чуть, ведь работа с базой у нас сейчас выполняется лишь в одном методе (я не учитываю изменения, связанные со спецификой Realm, а они есть, в частности, правила именования полей и работа с Gson; их можно посмотреть на Github).

И изменим метод save в AirportsResponse:

Вот и все! Мы элементарным образом, не затрагивая классы, которые содержат другую логику, изменили способ хранения данных.

Все-таки заключение

Хочу выделить один достаточно важный момент: мы не рассмотрели вопросы, связанные с использованием закэшированных данных, то есть при отстуствии интернета. Однако стратегия использования закэшированных данных в каждом приложении индивидуальна, и навязывать какой-то определенный подход я не считаю правильным. Да и так статья растянулась.

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

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

Поехали. Многие мобильные приложения (и не только) используют архитектуру клиент-сервер. Общая схема, думаю, понятна.


Уделим внимание каждому элементу и отметим:


Делаем сервер

Для реализации «сервера», нам нужно зарегистрироваться на любом хостинге, который дает возможность работы с SQL и PHP.

Создаем пустую SQL БД, в ней создаем таблицу.

Структура запросов к api:

  • chat.php?action=delete – удалит все записи на сервере
  • chat.php?action=insert&author=Jon&client=Smith&text=Hello — добавит на сервере новую запись: автор Jon, получатель Smith, содержание Hello
  • chat.php?action=select&data=151351333 — вернет все записи, полученные после переданного времени в long формате

Клиентская часть

Теперь структура Android приложения:


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

— Потоки: для того чтобы не перепутать что именно подразумевается под потоком я буду использовать существующий в профессиональной литературе синоним — нить, чтобы не путать Stream и Thread, всё-таки более профессионально выражаться — нить, говоря про Thread.

— Сокеты(Sockets): данное понятие тоже не однозначно, поскольку в какой-то момент сервер выполняет — клиентские действия, а клиент — серверные. Поэтому я разделил понятие серверного сокета — (ServerSocket) и сокета (Socket) через который практически осуществляется общение, его будем называть сокет общения, чтобы было понятно о чём речь.

Спасибо за подсказку про Thread.sleep();!
Конечно в реальном коде Thread.sleep(); устанавливать не нужно — это моветон! В данной публикации я его использую только для того чтобы выполнение программы было нагляднее, что бы успевать разобраться в происходящем.
Так что тестируйте, изучайте и в своём коде никогда не используйте Thread.sleep();!

1) Однопоточный элементарный сервер.
2) Клиент.
3) Многопоточный сервер – сам по себе этот сервер не участвует в общении напрямую, а лишь является фабрикой однонитевых делегатов(делегированных для ведения диалога с клиентами серверов) для общения с вновь подключившимися клиентами, которые закрываются после окончания общения с клиентом.
4) Имитация множественного обращения клиентов к серверу.

Итак, начнём с изучения структуры однопоточного сервер, который может принять только одного клиента для диалога. Код приводимый ниже необходимо запускать в своей IDE в этом идея всей статьи. Предлагаю все детали уяснить из подробно задокументированного кода ниже:

Сервер запущен и находится в блокирующем ожидании server.accept(); обращения к нему с запросом на подключение. Теперь можно подключаться клиенту, напишем код клиента и запустим его. Клиент работает когда пользователь вводит что-либо в его консоли (внимание! в данном случае сервер и клиент запускаются на одном компьютере с локальным адресом — localhost, поэтому при вводе строк, которые должен отправлять клиент не забудьте убедиться, что вы переключились в рабочую консоль клиента!).
После ввода строки в консоль клиента и нажатия enter строка проверяется не ввёл ли клиент кодовое слово для окончания общения дальше отправляется серверу, где он читает её и то же проверяет на наличие кодового слова выхода. Оба и клиент и сервер получив кодовое слово закрывают ресурсы после предварительных приготовлений и завершают свою работу.
Посмотрим как это выглядит в коде:

А что если к серверу хочет подключиться ещё один клиент!? Ведь описанный выше сервер либо находится в ожидании подключения одного клиента, либо общается с ним до завершения соединения, что делать остальным клиентам? Для такого случая нужно создать фабрику которая будет создавать описанных выше серверов при подключении к сокету новых клиентов и не дожидаясь пока делегированный подсервер закончит диалог с клиентом откроет accept() в ожидании следующего клиента. Но чтобы на серверной машине хватило ресурсов для общения со множеством клиентов нужно ограничить количество возможных подключений. Фабрика будет выдавать немного модифицированный вариант предыдущего сервера(модификация будет касаться того что класс сервера для фабрики будет имплементировать интерфейс — Runnable для возможности его использования в пуле нитей — ExecutorServices). Давайте создадим такую серверную фабрику и ознакомимся с подробным описанием её работы в коде:

  • Модифицированный Runnable сервер для запуска из предыдущего кода:
  • 4) Имитация множественного обращения клиентов к серверу.

Как видно из предыдущего кода фабрика запускает — TestRunnableClientTester() клиентов, напишем для них код и после этого запустим саму фабрику, чтобы ей было кого исполнять в своём пуле:

Запускайте, вносите изменения в код, только так на самом деле можно понять работу этой структуры.

Я новичок в мобильных приложениях. Я в основном с платформы веб-разработки. Я просто играю вокруг мобильных фреймворков, таких как App Framework, LungoJS, jQuery Mobile, kendo и т. д., Чтобы получить некоторые знания в этой вертикали.

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

Мне нужны некоторые идеи, чтобы создать связи между смарт-устройством и сервером. Мой вопрос are

  1. какой сервер необходим для мобильных приложений ? Облако или обычного веб-сервера достаточно ?
  2. Каковы способы подключения приложения к серверу ? ( на крестах платформа мобильной разработки)
  3. каков безопасный способ связи с сервером ?

какой сервер необходим для мобильных приложений ? Достаточно облака или обычного веб-сервера ?

вы также не нужно ничего создавать с нуля. Лучший способ действий-использовать какой-то микро RESTFul framework(например, PHP Сокол или Java Play Framework). Подробнее о них здесь.

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

существует также альтернатива службам RESTFul, вы можете создать webservice, снова используя Java, PHP или .Сеть.

Каковы способы подключения приложения к серверу ? (на кросс-платформенной мобильной разработке )

вы бы использовали AJAX в качестве технологии (в случае RESTFul), отдых зависит от вас. Вероятно, вы сделали бы это в формате JSON (или JSONP, если вы выполняете междоменные вызовы, но вам не нужно думать о JSONP при создании гибридного приложения).

Если вы собираетесь использовать веб-службу, вы должны использовать SOAP подключение и связь через формат XML.

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

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

каков безопасный способ связи с сервером ?

примеры:

Если вы хотите использовать jQuery Mobile, взгляните на этой учебник. Он покажет вам основы клиент-серверной связи.

Читайте также: