JavaFX, Sockets and Threading: Lessons Learned
When contemplating how machine-dependent applications might communicate with Java/JavaFX, JNI or the Java Native Interface, having been created for just such a task, would likely be the first mechanism that comes to mind. Although JNI works just fine thank you, a group of us ultimately decided against using it for a small project because, among others: Errors in your JNI implementation can corrupt the Java Virtual Machine in very strange ways, leading to difficult diagnosis. JNI can be time consuming and tedious, especially if there's a varied amount of interchange between the Native and Java platforms. For each OS/Platform supported, a separate JNI implementation would need to be created and maintained. Instead we opted for something a bit more mundane, namely sockets. The socket programming paradigm has been around a long time, is well understood and spans a multitude of hardware/software platforms. Rather than spending time defining JNI interfaces, just open up a socket between applications and send messages back and forth, defining your own message protocol. Following are some reflections on using sockets with JavaFX and Java. For the sake of simplicity, we'll skip the native stuff and focus on how sockets can be incorporated into a JavaFX application in a thread safe manner. Sockets and Threading Socket programming, especially in Java, lends itself to utilizing threads. Because a socket read() will block waiting for input, a common practice is to place the read loop in a background thread enabling you to continue processing while waiting for input at the same time. And if you're doing this work entirely in Java, you'll find that both ends of the socket connection -- the "server" side and the "client" side -- share a great deal of common code. Recognizing this, an abstract class called GenericSocket.java was created which is responsible for housing the common functionality shared by "server" and "client" sockets including the setup of a reader thread to handle socket reads asynchronously. For this simple example, two implementations of the abstract GenericSocket class, one called SocketServer.java, the other called SocketClient.java have been supplied. The primary difference between these two classes lies in the type of socket they use. SocketServer.java uses java.net.ServerSocket, while SocketClient.java uses java.net.Socket. The respective implementations contain the details required to set up and tear down these slightly different socket types. Dissecting the Java Socket Framework If you want to utilize the provided Java socket framework with JavaFX, you need to understand this very important fact: JavaFX is not thread safe and all JavaFX manipulation should be run on the JavaFX processing thread.1 If you allow a JavaFX application to interact with a thread other than the main processing thread, unpredictable errors will occur. Recall that the GenericSocket class created a reader thread to handle socket reads. In order to avoid non-main-thread-processing and its pitfalls with our socket classes, a few modifications must take place. [1] Stolen from JavaFX: Developing Rich Internet Applications - Thanks Jim Clarke Step 1: Identify Resources Off the Main Thread The first step to operating in a thread safe manner is to identify those resources in your Java code, residing off the main thread, that might need to be accessed by JavaFX. For our example, we define two abstract methods, the first, onMessage(), is called whenever a line of text is read from the socket. The GenericSocket.java code will make a call to this method upon encountering socket input. Let's take a look at the SocketReaderThread code inside GenericSocket, to get a feel for what's going on. 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(); } } Because onMessage() is called off the main thread and inside SocketReaderThread, the comment states that some additional work, which we'll explain soon, must take place to assure main thread processing. Our second method, onClosedStatus(), is called whenever the status of the socket changes (either opened or closed for whatever reason). This abstract routine is called in different places within GenericSocket.java -- sometimes on the main thread, sometimes not. To assure thread safety, we'll employ the same technique as with onMessage(). Step 2: Create a Java Interface with your Identified Methods Once identified, these method signatures have to be declared inside a Java interface. For example, our socket framework includes a SocketListener.java interface file which looks like this: package genericsocket; public interface SocketListener { public void onMessage(String line); public void onClosedStatus(Boolean isClosed); } Step 3: Create Your Java Class, Implementing Your Defined Interface With our SocketListener interface defined, let's take a step-by-step look at how the SocketServer class is implemented inside SocketServer.java. One of the first requirements is to import a special Java class which will allow us to do main thread processing, achieved as follows: import com.sun.javafx.runtime.Entry; Next, comes the declaration of SocketServer. Notice that in addition to extending the abstract GenericSocket class it also must implement our SocketListener interface too: public class SocketServer extends GenericSocket implements SocketListener { Inside the SocketServer definition, a variable called fxListener of type SocketListener is declared: private SocketListener fxListener; The constructor for SocketServer must include a reference to fxListener. The other arguments are used to specify a port number and some debug flags. public SocketServer(SocketListener fxListener, int port, int debugFlags) { super(port, debugFlags); this.fxListener = fxListener; } Next, let's examine the implementation of the two methods which are declared in the SocketListener interface. The first, onMessage(), looks like this: /** * 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); } }); } As the comment points out, the Entry.deferAction() call enables fxListener.onMessage() to be executed on the main thread. It takes as an argument an instance of the Runnable class and, within its run() method, makes a call to fxListener.onMessage(). Another important point to notice is that onMessage()'s String argument must be declared as final. Along the same line, the onClosedStatus() method is implemented as follows: /** * 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); } }); } Another Runnable is scheduled via Entry.deferAction() to run fxlistener.onClosedStatus() on the main thread. Again, onClosedStatus()'s Boolean argument must also be defined as final. Accessing the Framework within JavaFX With this work behind us, now we can integrate the framework into JavaFX. But before elaborating on the details, lets show screenshots of two simple JavaFX applications, SocketServer and SocketClient which, when run together, can send and receive text messages to one another over a socket. These JavaFX programs were developed in NetBeans and utilize the recently announced NetBeans JavaFX Composer tool. You can click on the images to execute these programs via Java WebStart. Note: depending upon your platform, your system may ask for permission prior to allowing these applications to network. Source for the JavaFX applications and the socket framework in the form of NetBeans projects can be downloaded here. Step 4: Integrating into JavaFX To access the socket framework within JavaFX, you must implement the SocketListener class that was created for this project. To give you a feel for how this is done with our JavaFX SocketServer application, here are some code excerpts from the project's Main.fx file, in particular the definition of our ServerSocketListener class: 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(); } } } Sparing all of the gory details, the onMessage() method will place the line of text read from the socket in to a JavaFX ListView control which is displayed in the program user interface. The onClosedStatus() method primarily updates the local socketClosed variable and attempts to reconnect the socket if the autoconnect option has been selected. To demonstrate how the socket is created, we examine the connectButtonAction() function: var socketServer : SocketServer; ... public function connectButtonAction (): Void { if (not tryingToConnect) { if (socketClosed) { socketServer = new SocketServer(ServerSocketListener{}, java.lang.Integer.parseInt(portTextbox.text), javafx.util.Bits.bitOr(GenericSocket.DEBUG_STATUS, GenericSocket.DEBUG_IO)); tryingToConnect = true; socketServer.connect(); } } } Whenever the user clicks on the "Connect" button, the connectButtonAction() function will be called. On invocation, if the socket isn't already open, it will create a new SocketServer instance. Recognize also that the SocketServer constructor includes an instance of the ServerSocketListener class which was defined above. To round this out, when the user clicks on the "Disconnect" button, the disconnectButtonAction() function is called. When invoked, it tears down the SocketServer instance. function disconnectButtonAction (): Void { tryingToConnect = false; socketServer.shutdown(); } Conclusion Admittedly, there's a fair amount to digest here. Hopefully, by carefully reviewing the steps and looking at the complete code listing, this can serve as a template if you wish to accomplish something similar in JavaFX. From http://blogs.sun.com/jtc/
February 11, 2010
·
20,607 Views
·
0 Likes
Comments
Apr 21, 2020 · Jim Connors
It needs to be updated as the API between that early access version in JDK 13 and the JDK 14 incubator package has chaged. Also, as it is an incubator module, it's possible it might change again.
Apr 21, 2020 · Jim Connors
Sorry to say, cross building of packages is not supported. As per the jpackage JEP: https://openjdk.java.net/jeps/343
There will be no support for cross compilation. For example, in order to create Windows packages one must run the tool on Windows. The packaging tool will depend upon platform-specific tools.
Apr 20, 2020 · Jim Connors
Have you pulled the most recent source fro github? It is now based on the new JDK 14 jpackage utility which is different from the original post a year ago. Can you try using the latest source with a JDK 14 build?
Jan 22, 2019 · Jim Connors
Yes, indeed AdoptOpenJDK, one of many, many Open JDK distributions, does still provide a JRE, but it's different from Oracle's and Sun's JREs. Only the Oracle/Sun implementations contain the technologies needed to run Java applets or Java Web Start which came with the JRE. So whatever they're making, it ain't the same.
Dec 26, 2012 · Mr B Loid
It looks like the YouTube link never made it to this article! Check out:
http://www.youtube.com/watch?v=GZAhOx-YYL0
-- Jim CDec 26, 2012 · James Sugrue
It looks like the YouTube link never made it to this article! Check out:
http://www.youtube.com/watch?v=GZAhOx-YYL0
-- Jim CDec 25, 2012 · James Sugrue
It looks like the YouTube link never made it to this article! Check out:
http://www.youtube.com/watch?v=GZAhOx-YYL0
-- Jim C
Jan 04, 2012 · Mr B Loid
Jan 04, 2012 · James Sugrue
Jun 23, 2011 · James Sugrue
As per a previous article entitled Why I like the New JavaFX, here's some more rationale:
The new JavaFX results in, for lack of a better term, more predictability. One of the primary JavaFX Script mechanisms used to define and place graphical assets (Nodes) into the scenegraph is the object literal notation. Being static in nature, the notion of a sequential flow of events inside an object literal may not make much sense. To compensate, JavaFX Script introduces the powerful concept of binding. Without question, binding is very elegant, but it comes with a price. It is not uncommon to see liberal and arguably unnecessary use of binding throughout JavaFX script code.
One can argue that bringing JavaFX to Java should lessen, but certainly not eliminate, the need for binding. Rather than defining objects inside a static initializer, JavaFX API objects are created and defined sequentially by constructor and method calls respectively. By defining objects in an order that makes sense, there is potential to eliminate a certain class of dependencies that were previously resolved with binding.
Jun 23, 2011 · James Sugrue
As per a previous article entitled Why I like the New JavaFX, here's some more rationale:
The new JavaFX results in, for lack of a better term, more predictability. One of the primary JavaFX Script mechanisms used to define and place graphical assets (Nodes) into the scenegraph is the object literal notation. Being static in nature, the notion of a sequential flow of events inside an object literal may not make much sense. To compensate, JavaFX Script introduces the powerful concept of binding. Without question, binding is very elegant, but it comes with a price. It is not uncommon to see liberal and arguably unnecessary use of binding throughout JavaFX script code.
One can argue that bringing JavaFX to Java should lessen, but certainly not eliminate, the need for binding. Rather than defining objects inside a static initializer, JavaFX API objects are created and defined sequentially by constructor and method calls respectively. By defining objects in an order that makes sense, there is potential to eliminate a certain class of dependencies that were previously resolved with binding.
Jun 23, 2011 · James Sugrue
As per a previous article entitled Why I like the New JavaFX, here's some more rationale:
The new JavaFX results in, for lack of a better term, more predictability. One of the primary JavaFX Script mechanisms used to define and place graphical assets (Nodes) into the scenegraph is the object literal notation. Being static in nature, the notion of a sequential flow of events inside an object literal may not make much sense. To compensate, JavaFX Script introduces the powerful concept of binding. Without question, binding is very elegant, but it comes with a price. It is not uncommon to see liberal and arguably unnecessary use of binding throughout JavaFX script code.
One can argue that bringing JavaFX to Java should lessen, but certainly not eliminate, the need for binding. Rather than defining objects inside a static initializer, JavaFX API objects are created and defined sequentially by constructor and method calls respectively. By defining objects in an order that makes sense, there is potential to eliminate a certain class of dependencies that were previously resolved with binding.
Nov 30, 2010 · Mr B Loid
One important fact I failed to mention in the article is that although Oracle has effectively ceased its work on JavaFX Script, ongoing development is still taking place in the open source under project Visage. Stephen Chin, principal on the project and JavaFX author, mentions that some of the shortcomings of the language, many of them mentioned above, are being addressed.
For more info, check out http://code.google.com/p/visage/
Nov 30, 2010 · Jim Connors
One important fact I failed to mention in the article is that although Oracle has effectively ceased its work on JavaFX Script, ongoing development is still taking place in the open source under project Visage. Stephen Chin, principal on the project and JavaFX author, mentions that some of the shortcomings of the language, many of them mentioned above, are being addressed.
For more info, check out http://code.google.com/p/visage/
Aug 12, 2009 · Mr B Loid
With the 1.2 version of the application, I get comparable results on a state-of-the-art Toshiba (<1% CPU) laptop running Vista SP1, JDK 6u15. The original rationale for running the application on an old laptop was as follows:
1. A slower/older device would exaggerate the performance differences. There may appear to be no performance issues on a modern desktop/laptop, that is until the application is migrated to a mobile device which is much more constrained.
2. With regards to Solaris and JavaFX you are correct, it's not nearly as optimized as the Windows stack. Again the point was to demonstrate how node count can influence performance. This seemed to be an ideal platform. In addition, and I admit a whole lot of bias here, Solaris/OpenSolaris has nice instrumentation. Even the rudimentary vmstat(5) can produce quite meaningful data. From a Windows perspective, perhaps it's just due to ignorance, I found the performance data to be erratic.
Aug 12, 2009 · Mr B Loid
With the 1.2 version of the application, I get comparable results on a state-of-the-art Toshiba (<1% CPU) laptop running Vista SP1, JDK 6u15. The original rationale for running the application on an old laptop was as follows:
1. A slower/older device would exaggerate the performance differences. There may appear to be no performance issues on a modern desktop/laptop, that is until the application is migrated to a mobile device which is much more constrained.
2. With regards to Solaris and JavaFX you are correct, it's not nearly as optimized as the Windows stack. Again the point was to demonstrate how node count can influence performance. This seemed to be an ideal platform. In addition, and I admit a whole lot of bias here, Solaris/OpenSolaris has nice instrumentation. Even the rudimentary vmstat(5) can produce quite meaningful data. From a Windows perspective, perhaps it's just due to ignorance, I found the performance data to be erratic.
Aug 12, 2009 · James Sugrue
With the 1.2 version of the application, I get comparable results on a state-of-the-art Toshiba (<1% CPU) laptop running Vista SP1, JDK 6u15. The original rationale for running the application on an old laptop was as follows:
1. A slower/older device would exaggerate the performance differences. There may appear to be no performance issues on a modern desktop/laptop, that is until the application is migrated to a mobile device which is much more constrained.
2. With regards to Solaris and JavaFX you are correct, it's not nearly as optimized as the Windows stack. Again the point was to demonstrate how node count can influence performance. This seemed to be an ideal platform. In addition, and I admit a whole lot of bias here, Solaris/OpenSolaris has nice instrumentation. Even the rudimentary vmstat(5) can produce quite meaningful data. From a Windows perspective, perhaps it's just due to ignorance, I found the performance data to be erratic.
Aug 12, 2009 · James Sugrue
With the 1.2 version of the application, I get comparable results on a state-of-the-art Toshiba (<1% CPU) laptop running Vista SP1, JDK 6u15. The original rationale for running the application on an old laptop was as follows:
1. A slower/older device would exaggerate the performance differences. There may appear to be no performance issues on a modern desktop/laptop, that is until the application is migrated to a mobile device which is much more constrained.
2. With regards to Solaris and JavaFX you are correct, it's not nearly as optimized as the Windows stack. Again the point was to demonstrate how node count can influence performance. This seemed to be an ideal platform. In addition, and I admit a whole lot of bias here, Solaris/OpenSolaris has nice instrumentation. Even the rudimentary vmstat(5) can produce quite meaningful data. From a Windows perspective, perhaps it's just due to ignorance, I found the performance data to be erratic.
Oct 03, 2008 · Mr B Loid
Jim,
A relative newbie to JavaFX, I realized that your create() function inside ButtonNode, starting on line 98, i.e.:
public function create():Node {Group {
was missing the return keyword. Shouldn't it be:
public function create():Node {
return Group {
Is this optional?
Oct 03, 2008 · James Weaver
Jim,
A relative newbie to JavaFX, I realized that your create() function inside ButtonNode, starting on line 98, i.e.:
public function create():Node {Group {
was missing the return keyword. Shouldn't it be:
public function create():Node {
return Group {
Is this optional?