How to Implement a Java WebSocket Server for Image Transmission with Jetty
In the previous articles, I shared how to implement a .Net WebSocket server with SuperWebSocket. Today, I’d like to continue the series WebSocket: How-to, talking about how to implement a WebSocket server in Java.
What is Jetty?
“Jetty provides a Web server andjavax.servlet container, plus support forSPDY, WebSocket, OSGi, JMX, JNDI, JAASand many other integrations.” from http://www.eclipse.org/jetty/
Quickly Setup a Java WebSocket Server
Create a class WSHandler which extends the class WebSocketHandler:
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
@WebSocket
public class WSHandler extends WebSocketHandler {
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
}
@OnWebSocketError
public void onError(Throwable t) {
}
@OnWebSocketConnect
public void onConnect(Session session) {
}
@OnWebSocketMessage
public void onMessage(String message) {
}
@Override
public void configure(WebSocketServletFactory factory) {
// TODO Auto-generated method stub
factory.register(WSHandler.class);
}
}
Start the server with a port and a handler:
public static void main(String[] args) throws Exception {
Server server = new Server(2014);
server.setHandler(new WSHandler());
server.setStopTimeout(0);
server.start();
server.join();
}
Done.
JavaScript Client for WebSocket Connection
We can create a simple web client for testing.
Index.htm:
<!DOCTYPE html>
<html>
<body>
<script src="websocket.js"></script>
</body>
</html>
Websocket.js:
var ws = new WebSocket("ws://127.0.0.1:2014/");
ws.onopen = function() {
alert("Opened");
ws.send("I'm client");
};
ws.onmessage = function (evt) {
};
ws.onclose = function() {
alert("Closed");
};
ws.onerror = function(err) {
alert("Error: " + err);
};
Run the app and check the message.
Image Transmission Between WebSocket Server & Web Clients
What I’m going to do:
- Send an image from the Java WebSocket server to all connected Web clients by clicking the Swing button Send.
How to improve the source code?
Clients fetch images:
-
On the client side, we need to add a button and an image in index.htm:
<!DOCTYPE html> <html> <body> <h1>WebSocket Image Display</h1> <input type="button" id="button" value="image" ><br> <img id="image"></<img> <script src="websocket.js"></script> </body> </html>
-
In websocket.js, use the following code to fetch and display the image:
ws.binaryType = "arraybuffer"; var button = document.getElementById("button"); button.onclick = function() { ws.send("image"); // send the fetch request }; ws.onmessage = function (evt) { // display the image var bytes = new Uint8Array(evt.data); var data = ""; var len = bytes.byteLength; for (var i = 0; i < len; ++i) { data += String.fromCharCode(bytes[i]); } var img = document.getElementById("image"); img.src = "data:image/png;base64,"+window.btoa(data); };
-
Once a message received, the server will load an image and send it to Clients:
public void onMessage(String message) { System.out.println("Message: " + message); if (message.equals("image")) { System.out.println("session: " + mSession); if (mSession != null) { try { File f = new File("image\\github.jpg"); BufferedImage bi = ImageIO.read(f); ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(bi, "png", out); ByteBuffer byteBuffer = ByteBuffer.wrap(out.toByteArray()); mSession.getRemote().sendBytes(byteBuffer); out.close(); byteBuffer.clear(); } catch (IOException e) { e.printStackTrace(); } } } }
Server sends images:
-
On the server side, there are two buttons: one for loading images from the local disk, and the other for sending images to Web clients. Here is the UI code in Java Swing:
package com.ui; import java.awt.BorderLayout; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.filechooser.FileNameExtensionFilter; import com.server.WSHandler; import com.server.WebSocketServer; public class UIMain extends JPanel implements ActionListener { private JButton mLoad, mSend; private JFileChooser mFileChooser; private JLabel mImage; private byte[] mData; private WebSocketServer mWebSocketServer; public UIMain() { super(new BorderLayout()); //Create a file chooser mFileChooser = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter( ".png.jpg", "png","jpg"); mFileChooser.setFileFilter(filter); mLoad = new JButton("Load"); mLoad.addActionListener(this); mSend = new JButton("Send"); mSend.addActionListener(this); mSend.setEnabled(false); // button panel JPanel buttonPanel = new JPanel(); buttonPanel.add(mLoad); buttonPanel.add(mSend); add(buttonPanel, BorderLayout.PAGE_START); // image panel JPanel imageViewer = new JPanel(); mImage = new JLabel(); mImage.setSize(480, 640); imageViewer.add(mImage); add(imageViewer, BorderLayout.CENTER); // WebSocketServer mWebSocketServer = new WebSocketServer(); mWebSocketServer.start(); } @Override public void actionPerformed(ActionEvent e) { } private static void createAndShowGUI() { //Create and set up the window. JFrame frame = new JFrame("WebSocket Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Add content to the window. frame.add(new UIMain()); //Display the window. frame.pack(); frame.setVisible(true); frame.setResizable(false); frame.setSize(480, 700); double width = Toolkit.getDefaultToolkit().getScreenSize().getWidth(); double height = Toolkit.getDefaultToolkit().getScreenSize().getHeight(); int frameWidth = frame.getWidth(); int frameHeight = frame.getHeight(); frame.setLocation((int)(width - frameWidth) / 2, (int)(height - frameHeight) / 2); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { UIManager.put("swing.boldMetal", Boolean.FALSE); createAndShowGUI(); } }); } }
-
Note: do not start the WebSocket server in UI thread, otherwise the UI will be blocked. So, you have to launch the server in a worker thread:
package com.server; import org.eclipse.jetty.server.Server; public class WebSocketServer extends Thread{ @Override public void run() { // TODO Auto-generated method stub super.run(); try { Server server = new Server(2014); server.setHandler(new WSHandler()); server.setStopTimeout(0); server.start(); server.join(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
-
The events for loading and sending image:
public void sendImage(byte[] data) { if (mSession == null) return; try { ByteBuffer byteBuffer = ByteBuffer.wrap(data); mSession.getRemote().sendBytes(byteBuffer); byteBuffer.clear(); } catch (IOException e) { e.printStackTrace(); } } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == mLoad) { int returnVal = mFileChooser.showOpenDialog(UIMain.this); if (returnVal == JFileChooser.APPROVE_OPTION) { File file = mFileChooser.getSelectedFile(); // load image data to byte array try { BufferedImage bi = ImageIO.read(file); ByteArrayOutputStream out = new ByteArrayOutputStream(); ImageIO.write(bi, "png", out); mData = out.toByteArray(); out.close(); } catch (IOException exception) { exception.printStackTrace(); } mImage.setIcon(new ImageIcon(mData)); mSend.setEnabled(true); } } else if (e.getSource() == mSend) { ArrayList<WSHandler> sessions = WSHandler.getAllSessions(); for (WSHandler session : sessions) { session.sendImage(mData); } mSend.setEnabled(false); } }
That’s all. You can try to make your hands dirty now.