При рассмотрении того, как машинно-зависимые приложения могут взаимодействовать с Java / JavaFX, JNI или собственный интерфейс Java , созданный именно для такой задачи, скорее всего, станет первым механизмом, который приходит на ум. Хотя JNI работает очень хорошо, спасибо, группа из нас в конечном итоге решила не использовать его для небольшого проекта, потому что, среди прочего:
- Ошибки в вашей реализации JNI могут очень странно повредить виртуальную машину Java, что приведет к сложной диагностике.
- JNI может быть трудоемким и утомительным, особенно если существует разный обмен между платформами Native и Java.
- Для каждой поддерживаемой ОС / платформы необходимо будет создать и поддерживать отдельную реализацию JNI.
Вместо этого мы выбрали нечто более приземленное, а именно сокеты. Парадигма программирования сокетов существует уже давно, хорошо понята и охватывает множество аппаратных / программных платформ. Вместо того чтобы тратить время на определение интерфейсов JNI, просто откройте сокет между приложениями и отправляйте сообщения туда и обратно, определяя собственный протокол сообщений. Ниже приведены некоторые соображения по использованию сокетов с JavaFX и Java. Для простоты мы пропустим нативный материал и сосредоточимся на том, как сокеты могут быть встроены в приложение JavaFX потокобезопасным способом.
Розетки и нарезка резьбы
Сокетное программирование, особенно в Java, позволяет использовать потоки. Поскольку сокет read () блокирует ожидание ввода, обычной практикой является размещение цикла чтения в фоновом потоке, позволяющем вам продолжить обработку, ожидая ввода одновременно. И если вы выполняете эту работу полностью на Java, вы обнаружите, что оба конца сокетного соединения — на стороне сервера и на стороне клиента — имеют много общего кода. Признавая это, был создан абстрактный класс GenericSocket.java, который отвечает за размещение общей функциональности, совместно используемой сокетами «сервер» и «клиент», включая настройку потока чтения для обработки асинхронного чтения сокетов.
Для этого простого примера были предоставлены две реализации абстрактного класса GenericSocket , одна с именем SocketServer.java , другая с именем SocketClient.java . Основное различие между этими двумя классами заключается в типе сокета, который они используют. SocketServer.java использует java.net.ServerSocket , а SocketClient.java использует java.net.Socket, Соответствующие реализации содержат детали, необходимые для установки и удаления этих немного разных типов сокетов.
Рассекая Java Socket Framework
Если вы хотите использовать предоставляемую инфраструктуру сокетов Java с JavaFX, вам нужно понять этот очень важный факт: JavaFX не является потокобезопасным, и все манипуляции с JavaFX должны выполняться в потоке обработки JavaFX . 1 Если вы разрешите приложению JavaFX взаимодействовать с потоком, отличным от основного потока обработки, возникнут непредсказуемые ошибки. Напомним, что класс GenericSocket создал поток чтения для обработки чтения сокетов. Чтобы избежать обработки не основного потока и его ошибок с нашими классами сокетов, необходимо внести несколько изменений.
[1] Украдено у JavaFX: разработка многофункциональных интернет-приложений — спасибо Джиму Кларку
Шаг 1: Определите ресурсы вне основного потока
Первым шагом к работе в поточно-ориентированном режиме является определение тех ресурсов в вашем Java-коде, которые находятся вне основного потока, к которым может потребоваться доступ JavaFX. Для нашего примера мы определяем два абстрактных метода, первый, onMessage () , вызывается всякий раз, когда строка текста читается из сокета. Код GenericSocket.java будет вызывать этот метод при обнаружении ввода сокета. Давайте посмотрим на код SocketReaderThread внутри GenericSocket , чтобы понять, что происходит.
class SocketReaderThread extends Thread {
@Override
public void run() {
String line;
waitForReady();
/*
* Read from from input stream one line at a time
*/
try {
if (input != null) {
while ((line = input.readLine()) != null) {
if (debugFlagIsSet(DEBUG_IO)) {
System.out.println("recv> " + line);
}
/*
* The onMessage() method has to be implemented by
* a sublclass. If used in conjunction with JavaFX,
* use Entry.deferAction() to force this method to run
* on the main thread.
*/
onMessage(line);
}
}
} catch (Exception e) {
if (debugFlagIsSet(DEBUG_EXCEPTIONS)) {
e.printStackTrace();
}
} finally {
notifyTerminate();
}
}
Поскольку onMessage () вызывается из основного потока и внутри SocketReaderThread , в комментарии говорится, что для обеспечения обработки основного потока должна быть проведена некоторая дополнительная работа, о которой мы вскоре расскажем.
Наш второй метод onClosedStatus () вызывается всякий раз, когда изменяется состояние сокета (открыт или закрыт по любой причине). Эта абстрактная подпрограмма вызывается в разных местах в GenericSocket.java — иногда в главном потоке, иногда нет. Чтобы обеспечить безопасность потоков, мы будем использовать ту же технику, что и с onMessage () .
Шаг 2: Создайте интерфейс Java с вашими идентифицированными методами
После идентификации эти сигнатуры методов должны быть объявлены внутри интерфейса Java. Например, наша структура сокетов включает файл интерфейса SocketListener.java, который выглядит следующим образом:
package genericsocket;
public interface SocketListener {
public void onMessage(String line);
public void onClosedStatus(Boolean isClosed);
}
Шаг 3. Создайте свой класс Java, реализуя определенный интерфейс
С помощью нашего SocketListener интерфейс , определенный, давайте шаг за шагом рассмотрим, как SocketServer класс реализуется внутри SocketServer .java . Одним из первых требований является импорт специального Java-класса, который позволит нам выполнять обработку основных потоков, что достигается следующим образом:
import com.sun.javafx.runtime.Entry;
Далее идет объявление SocketServer . Обратите внимание, что в дополнение к расширению абстрактного класса GenericSocket он также должен реализовать наш интерфейс SocketListener :
public class SocketServer extends GenericSocket implements SocketListener {
Внутри определения SocketServer объявлена переменная с именем fxListener типа SocketListener :
private SocketListener fxListener;
Конструктор для SocketServer должен включать ссылку на fxListener . Другие аргументы используются для указания номера порта и некоторых флагов отладки.
public SocketServer(SocketListener fxListener,
int port, int debugFlags) {
super(port, debugFlags);
this.fxListener = fxListener;
}
Далее давайте рассмотрим реализацию двух методов, которые объявлены в интерфейсе SocketListener . Первый, onMessage () , выглядит так:
/**
* Called whenever a message is read from the socket. In
* JavaFX, this method must be run on the main thread and
* is accomplished by the Entry.deferAction() call. Failure to do so
* *will* result in strange errors and exceptions.
* @param line Line of text read from the socket.
*/
@Override
public void onMessage(final String line) {
Entry.deferAction(new Runnable() {
@Override
public void run() {
fxListener.onMessage(line);
}
});
}
Как отмечается в комментарии, вызов Entry.deferAction () позволяет выполнять fxListener.onMessage () в главном потоке. Он принимает в качестве аргумента экземпляр класса Runnable и в своем методе run () вызывает функцию fxListener.onMessage () . Еще одним важным моментом является то , что уведомление OnMessage () «s Строка аргумент должен быть объявлен как окончательный .
В том же ключе метод onClosedStatus () реализован следующим образом:
/**
* Called whenever the open/closed status of the Socket
* changes. In JavaFX, this method must be run on the main thread and
* is accomplished by the Entry.deferAction() call. Failure to do so
* will* result in strange errors and exceptions.
* @param isClosed true if the socket is closed
*/
@Override
public void onClosedStatus(final Boolean isClosed) {
Entry.deferAction(new Runnable() {
@Override
public void run() {
fxListener.onClosedStatus(isClosed);
}
});
}
Еще один Runnable запланирован через Entry.deferAction () для запуска fxlistener.onClosedStatus () в главном потоке. Опять же, логический аргумент onClosedStatus () также должен быть определен как final .
Доступ к платформе в JavaFX
С этой работой позади нас, теперь мы можем интегрировать фреймворк в JavaFX. Но прежде чем углубляться в детали, давайте покажем скриншоты двух простых приложений JavaFX, SocketServer и SocketClient, которые при совместном запуске могут отправлять и получать текстовые сообщения друг другу через сокет.
Эти программы JavaFX были разработаны в NetBeans и используют недавно анонсированный инструмент NetBeans JavaFX Composer . Вы можете нажать на изображения, чтобы выполнить эти программы через Java WebStart. Примечание: в зависимости от вашей платформы ваша система может запросить разрешение, прежде чем разрешить этим приложениям работать в сети. Исходный код для приложений JavaFX и каркас сокетов в виде проектов NetBeans можно скачать здесь .
Шаг 4: Интеграция в JavaFX
Чтобы получить доступ к платформе сокетов в JavaFX, вы должны реализовать класс SocketListener , созданный для этого проекта. Чтобы дать вам представление о том, как это делается с нашим приложением JavaFX SocketServer , вот некоторые выдержки из кода файла Main.fx проекта, в частности определение нашего класса ServerSocketListener :
public class ServerSocketListener extends SocketListener {
public override function onMessage(line: String) {
insert line into recvListView.items;
}
public override function onClosedStatus(isClosed : java.lang.Boolean) {
socketClosed = isClosed;
tryingToConnect = false;
if (autoConnectCheckbox.selected) {
connectButtonAction();
}
}
}
Избавляясь от всех подробностей, метод onMessage () поместит строку текста, прочитанную из сокета, в элемент управления JavaFX ListView, который отображается в пользовательском интерфейсе программы. Метод onClosedStatus () в первую очередь обновляет локальную переменную socketClosed и пытается повторно подключить сокет, если была выбрана опция autoconnect.
Чтобы продемонстрировать, как создается сокет, мы рассмотрим функцию connectButtonAction () :
var socketServer : SocketServer;
...
public function connectButtonAction (): Void {
if (not tryingToConnect) {
if (socketClosed) {
<b>socketServer = new
SocketServer(ServerSocketListener{},
java.lang.Integer.parseInt(portTextbox.text),
javafx.util.Bits.bitOr(GenericSocket.DEBUG_STATUS,
GenericSocket.DEBUG_IO));</b>
tryingToConnect = true;
<b>socketServer.connect();</b>
}
}
}
Всякий раз, когда пользователь нажимает кнопку «Подключиться», вызывается функция connectButtonAction () . При вызове, если сокет еще не открыт, он создаст новый экземпляр SocketServer . Признайте также, что конструктор SocketServer включает в себя экземпляр класса ServerSocketListener, который был определен выше.
Чтобы завершить это, когда пользователь нажимает кнопку «Отключить», вызывается функция disconnectButtonAction () . При вызове он разрушает экземпляр SocketServer .
function disconnectButtonAction (): Void {
tryingToConnect = false;
socketServer.shutdown();
}
Вывод
По общему признанию, есть достаточное количество, чтобы переварить здесь. Надеемся, что, внимательно изучив шаги и просмотрев полный список кода , это может послужить шаблоном, если вы захотите сделать что-то подобное в JavaFX.