Java Jetty WebSocket Server: Handle broadcasts with asynchronously disconnecting clients - java

Background
I'm working of a proof of concept that consist of a server-client system using websocket communication. I use a small jetty solution (jetty-all-9.1.3.v20140225.jar together with servlet-api-3.1.jar). I have most of the basic functionality in order for a PoC.
I have 2 classes:
TestServer (With a main function creating a Server instance, see code)
ClientSocket (WebSocket-object instantiated for each client)
Problem
The problem I would like to discuss has to do with broadcasting client disconnects. The server saves all instances of ClientSockets in one array, the ClientSockets are added to this array in their "onConnect"-function.
The system will later on limit broadcasts to groups of clients, but for the PoC, all connected clients shall get broadcasts. If one client disconnects I want to send a notification to all other clients ("myClient has disconnected." or similar).
To do this I implement a broadcast function in the server that loops through the list of clients, sending this information to all connected clients except the one who disconnected. This function is also used to inform all clients about other things, such as new connections, and this problem should most likely occur here as well in the case a client disconnects at the very same time as someone connects or broadcasts something.
The problem is easy to produced by connecting several (10+) clients (I do it in js) and then disconnect them all at the same time. If I do this I always get concurrency errors, for very few clients (2-3) it works sometimes, depending on timing I guess.
Questions
How should I handle the task of broadcasting to all other clients, taking into account that any clients can disconnect (asynchronously) at any time? Can I do this without generating exceptions? Since it's asynchronous I can't see any other way than dealing with the exceptions occurring. Any suggestions are greatly appreciated.
Code
TestServer.java
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.websocket.server.WebSocketHandler;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
public class TestServer {
private static final TestServer testServer = new TestServer();
private ArrayList<ClientSocket> clients = new ArrayList<>();
public static void main(String[] args) throws Exception {
int port = 8080;
Server server = new Server(port);
WebSocketHandler wsHandler = new WebSocketHandler() {
#Override
public void configure(WebSocketServletFactory factory) {
factory.register(ClientSocket.class);
}
};
server.setHandler(wsHandler);
System.out.println("Starting server on port " + port + ".");
server.start();
server.join();
}
public static TestServer getServer() {
return testServer;
}
public void addClient(ClientSocket client) {
this.clients.add(client);
}
public void removeClient(ClientSocket client) {
this.clients.remove(client);
this.broadcast("disconnect " + client.id, client);
}
public void broadcast(String message, ClientSocket excludedClient) {
log("Sending to all clients: " + message);
for (ClientSocket cs : this.clients) {
if (!cs.equals(excludedClient) && cs.session.isOpen() && cs != null) {
try {
cs.session.getRemote().sendStringByFuture(message);
} catch (Exception e) {
log("Error when broadcasting to " + cs.id + " (" + cs.address + "):");
log(e.getMessage());
}
}
}
}
Since looping through an array in this way generally doesn't work if you meddle with the array in the process so I also tried this broadcast function:
public void broadcast(String message, ClientSocket excludedClient) {
log("Sending to all clients: " + message);
Iterator<ClientSocket> cs = this.clients.iterator();
while (cs.hasNext()) {
ClientSocket client = cs.next();
if (client != null) {
if (!client.equals(excludedClient) && client.session.isOpen()) {
try {
client.session.getRemote().sendStringByFuture(message);
} catch (Exception e) {
log("Error when broadcasting to " + client.id + " (" + client.address + "):");
log(e.getMessage());
}
}
}
}
It doesn't work any better though, since the problem is that the array can be meddled with asynchronously if another ClientSocket object disconnects as the first one is broadcasting it's disconnection.
ClientSocket.java
import java.io.IOException;
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;
#WebSocket(maxIdleTime=0)
public class ClientSocket {
TestServer server = TestServer.getServer();
Session session;
String id;
String address;
#OnWebSocketClose
public void onClose(Session session, int statusCode, String reason) {
server.log("Disconnected: " + this.id + "(" + this.address + ")" + " (statusCode=" + statusCode + ", reason=" + reason + ")");
server.removeClient(this);
}
#OnWebSocketError
public void onError(Session session, Throwable t) {
server.log(this.id + "(" + this.address + ") error: " + t.getMessage());
}
#OnWebSocketConnect
public void onConnect(Session session) {
this.session = session;
this.address = this.session.getRemoteAddress().getAddress().toString().substring(1);
this.id = this.address; //Until user is registered their id is their IP
server.log("New connection: " + this.address);
server.addClient(this);
try {
session.getRemote().sendString("Hello client with address " + this.address + "!");
} catch (IOException e) {
server.log("Error in onConnect for " + this.id + "(" + this.address + "): " + e.getMessage());
}
}
#OnWebSocketMessage
public void onMessage(Session session, String message) {
server.log("Received from " + this.id + "(" + this.address + "): " + message);
String replyMessage;
String[] commandList = message.split("\\s+");
switch (commandList[0].toLowerCase()) {
case "register":
if (commandList.length > 1) {
this.id = commandList[1];
replyMessage = "Registered on server as " + this.id;
server.broadcast(this.id + " has connected", this);
} else {
replyMessage = "Incorrect register message";
}
break;
default:
replyMessage = "echo " + message;
break;
}
server.log("Sending to " + this.id + "(" + this.address + "): " + replyMessage);
try {
session.getRemote().sendString(replyMessage);
} catch (IOException e) {
server.log("Error during reply in onMessage for " + this.id + "(" + this.address + "): " + e.getMessage());
}
}
}
I pasted this whole class for completeness, even though I removed some of the cases in the switch for onMessage. The parts to take notice in however are the onConnect and onClose functions that will populate and remove clients from the client array in the server.
Error log
[2014-04-17 17:40:17.961] Sending to all clients: disconnect testclient4
2014-04-17 17:40:17.962:WARN:ClientSocket:qtp29398564-17: Unhandled Error (closing connection)
org.eclipse.jetty.websocket.api.WebSocketException: Cannot call method public void ClientSocket#onClose(org.eclipse.jetty.websocket.api.Session, int, java.lang.String) with args: [org.eclipse.jetty.websocket.common.WebSocketSession, java.lang.Integer, <null>]
at org.eclipse.jetty.websocket.common.events.annotated.CallableMethod.call(CallableMethod.java:99)
at org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod.call(OptionalSessionCallableMethod.java:68)
at org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver.onClose(JettyAnnotatedEventDriver.java:122)
at org.eclipse.jetty.websocket.common.events.AbstractEventDriver.incomingFrame(AbstractEventDriver.java:125)
at org.eclipse.jetty.websocket.common.WebSocketSession.incomingFrame(WebSocketSession.java:302)
at org.eclipse.jetty.websocket.common.extensions.AbstractExtension.nextIncomingFrame(AbstractExtension.java:163)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.nextIncomingFrame(PerMessageDeflateExtension.java:92)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.incomingFrame(PerMessageDeflateExtension.java:66)
at org.eclipse.jetty.websocket.common.extensions.ExtensionStack.incomingFrame(ExtensionStack.java:210)
at org.eclipse.jetty.websocket.common.Parser.notifyFrame(Parser.java:219)
at org.eclipse.jetty.websocket.common.Parser.parse(Parser.java:257)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.read(AbstractWebSocketConnection.java:500)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.onFillable(AbstractWebSocketConnection.java:409)
at org.eclipse.jetty.io.AbstractConnection$1.run(AbstractConnection.java:505)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:744)
Caused by:
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.eclipse.jetty.websocket.common.events.annotated.CallableMethod.call(CallableMethod.java:71)
at org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod.call(OptionalSessionCallableMethod.java:68)
at org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver.onClose(JettyAnnotatedEventDriver.java:122)
at org.eclipse.jetty.websocket.common.events.AbstractEventDriver.incomingFrame(AbstractEventDriver.java:125)
at org.eclipse.jetty.websocket.common.WebSocketSession.incomingFrame(WebSocketSession.java:302)
at org.eclipse.jetty.websocket.common.extensions.AbstractExtension.nextIncomingFrame(AbstractExtension.java:163)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.nextIncomingFrame(PerMessageDeflateExtension.java:92)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.incomingFrame(PerMessageDeflateExtension.java:66)
at org.eclipse.jetty.websocket.common.extensions.ExtensionStack.incomingFrame(ExtensionStack.java:210)
at org.eclipse.jetty.websocket.common.Parser.notifyFrame(Parser.java:219)
at org.eclipse.jetty.websocket.common.Parser.parse(Parser.java:257)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.read(AbstractWebSocketConnection.java:500)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.onFillable(AbstractWebSocketConnection.java:409)
at org.eclipse.jetty.io.AbstractConnection$1.run(AbstractConnection.java:505)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:744)
Caused by:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at TestServer.broadcast(TestServer.java:61)
at TestServer.removeClient(TestServer.java:45)
at ClientSocket.onClose(ClientSocket.java:22)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at org.eclipse.jetty.websocket.common.events.annotated.CallableMethod.call(CallableMethod.java:71)
at org.eclipse.jetty.websocket.common.events.annotated.OptionalSessionCallableMethod.call(OptionalSessionCallableMethod.java:68)
at org.eclipse.jetty.websocket.common.events.JettyAnnotatedEventDriver.onClose(JettyAnnotatedEventDriver.java:122)
at org.eclipse.jetty.websocket.common.events.AbstractEventDriver.incomingFrame(AbstractEventDriver.java:125)
at org.eclipse.jetty.websocket.common.WebSocketSession.incomingFrame(WebSocketSession.java:302)
at org.eclipse.jetty.websocket.common.extensions.AbstractExtension.nextIncomingFrame(AbstractExtension.java:163)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.nextIncomingFrame(PerMessageDeflateExtension.java:92)
at org.eclipse.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension.incomingFrame(PerMessageDeflateExtension.java:66)
at org.eclipse.jetty.websocket.common.extensions.ExtensionStack.incomingFrame(ExtensionStack.java:210)
at org.eclipse.jetty.websocket.common.Parser.notifyFrame(Parser.java:219)
at org.eclipse.jetty.websocket.common.Parser.parse(Parser.java:257)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.read(AbstractWebSocketConnection.java:500)
at org.eclipse.jetty.websocket.common.io.AbstractWebSocketConnection.onFillable(AbstractWebSocketConnection.java:409)
at org.eclipse.jetty.io.AbstractConnection$1.run(AbstractConnection.java:505)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:744)
[2014-04-17 17:40:17.97] testclient7(94.246.80.30) error: Cannot call method public void ClientSocket#onClose(org.eclipse.jetty.websocket.api.Session, int, java.lang.String) with args: [org.eclipse.jetty.websocket.common.WebSocketSession, java.lang.Integer, <null>]
This happens, sometimes for several clients, when disconnecting all the clients at the same time. I'm pretty sure that it has to do with ClientSocket objects disappearing at the same time as a broadcast is made.

Replace:
private ArrayList<ClientSocket> clients = new ArrayList<>();
With:
private List<ClientSocket> clients = new CopyOnWriteArrayList<>();
In your broadcast method just use the for each statement:
for( ClientSocket client : clients )
{
if ( !client.equals(excludedClient) && client.session.isOpen() )
{
// Broadcast...
}
}
This will give you thread safe iteration. ConcurrentModificationException occurs because you are modifying the List at the same time as iterating over it. A CopyOnWriteArrayList copies the internal array on write so modifications do not interfere with iteration. This adds some additional overhead to mutating the list of course, so you may want to think about another method of ensuring thread safety, however I suspect this will be sufficient for your needs. If reads are a lots more common than writes (as is usually the case) then a 'copy on write' data structure will do fine.

Related

Get an error after deploying war file of grails Websocket using jar library

Following code works fine in the development environment but not in production. I got stuck due to this. I have tried to deploy with tomcat v9.0.24 and Tomcat v8.5.32. In the development environment, tomcat v9.0.24 is working but the issue is with production.
org.apache.catalina.core.StandardContext.startInternal Error during ServletContainerInitializer processing javax.servlet.ServletException: java.lang.NoSuchMethodException:org.glassfish.tyrus.server.TyrusServerConfiguration.<init>()
grails-3.3.4 Gradle:
//*********** Websoket *************//
compile 'org.glassfish.tyrus:tyrus-server:1.13.1'
compile 'javax.websocket:javax.websocket-api:1.1'
compile 'org.glassfish.tyrus:tyrus-container-grizzly-server:1.13.1'
Websocket class
import grails.gorm.transactions.Transactional
import grails.util.Environment
import grails.util.Holders
import pawnshoprest.staff.User
import javax.websocket.*
import javax.websocket.server.ServerEndpoint
#ServerEndpoint(value = "/broadcast/{user_id}/{username}")
class WebSocketsService {
//**************** declare client ***************
private static Set<Session> clients = Collections.synchronizedSet(new HashSet<Session>());
#OnOpen
static void onOpen(Session session) {
System.out.println("Connected ... " + session.getId());
clients.add(session);
}
#OnMessage
static String onMessage(String message, Session session){
println("\n")
System.out.println("my Message: " + message);
synchronized(clients){
// Iterate over the connected sessions
// and broadcast the received message
for(Session client : clients){
//if (!client.equals(session)){
System.out.println("************** Session ID *******************");
System.out.println("sender: " + session.getId());
System.out.println("Reciever: " + client.getId());
//************ broadcast message **********************
if(!session.pathParameters.user_id.equals(client.pathParameters.user_id)){
println('message sent to ' + client.pathParameters.username)
client.getBasicRemote().sendText(message);
}
//}
}
}
//return message;
}
#OnClose
static void onClose(Session session, CloseReason closeReason) {
//******** Client Disconnect ****************
System.out.println("Disconnected ... " + session.getId());
clients.remove(session)
}
}
Bootstrap.groovy
class Bootstrap{
def init = { servletContext ->
Server server = new Server("${InetAddress.getLocalHost().getHostAddress()}", 8025, "/websockets", null, WebSocketsService);
try {
server.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
println('\n')
println('**************************** Socket running ****************************')
println("Please press a key to stop the server.");
println('\n')
reader.readLine();
} catch (Exception e) {
e.printStackTrace();
} finally {
println('\n')
println('**************************** Socket Has Been Stopped ****************************')
println('\n')
}
}
}

pubnub processing/java for a noob

I am using processing (IE, JAVA) to publish via pubnub. After reading nearly all the simple startup documentation, I am at a loss. My code is running, but not publishing anything. I have the libraries installed correctly I believe, but no messages are published via my keys.
I know I am just not understanding something really simple here, but I have no idea what that might be. I added a 5s delay to ensure everything finishes before setup stops as suggested in other posts. Right now it runs, but no messages show up in the app with those keys(which I replaced below). All the examples are way over my head and not helping my poor noob brain.
I also setup the same basic code in Python which also runs but does not actually publish the message. Leading me to think I am just missing something very fundamental.
In the end, I want to push data from a UI to an online real-time dashboard. This seems trivially easy, but it is a complete black hole.
Here is my code (it's basically just a copy and paste from the startup code):
import com.pubnub.api.*;
import org.json.*;
Pubnub pubnub = new Pubnub("PUBKEY", "SUBKEY");
void setup()
{
try {
pubnub.subscribe("NSFPhaseII", new Callback() {
#Override
public void connectCallback(String channel, Object message) {
pubnub.publish("NSFPhaseII", "Hello from the PubNub Java SDK", new Callback() {});
}
#Override
public void disconnectCallback(String channel, Object message) {
System.out.println("SUBSCRIBE : DISCONNECT on channel:" + channel
+ " : " + message.getClass() + " : "
+ message.toString());
}
public void reconnectCallback(String channel, Object message) {
System.out.println("SUBSCRIBE : RECONNECT on channel:" + channel
+ " : " + message.getClass() + " : "
+ message.toString());
}
#Override
public void successCallback(String channel, Object message) {
System.out.println("SUBSCRIBE : " + channel + " : "
+ message.getClass() + " : " + message.toString());
}
#Override
public void errorCallback(String channel, PubnubError error) {
System.out.println("SUBSCRIBE : ERROR on channel " + channel
+ " : " + error.toString());
}
});
}
catch (PubnubException e) {
System.out.println(e.toString());
}
delay(5000);
println("done");
}
I am answering my own question here.
It turns out this code has been working all along. I setup two processing sketches in a publish subscribe type setup and could clearly see that one was receiving the publishes from the other.
Then, when I checked on pubnub, the messages registered in my account. I am not really sure if the messages were registering in my account all along and just not updating or if they took some time, but I believe it was working all along and I was just not waiting long enough to see the messages register online.
Like I said. Noobs...

JEE7 + WildFly (HornetQ) - Pause queue from application

We are using WildFly + HornetQ as our application server and JMS message queue, and have the requirement to be able to pause/resume queues from the application. Is this possible?
This can be done using JMX or using the hornetq core management api.
For the purposes of this example, wildfly 8.1.0.Final was used running the standalone-full-ha profile.
Required Maven Dependencies:
<dependency>
<groupId>org.hornetq</groupId>
<artifactId>hornetq-jms-client</artifactId>
<version>2.4.1.Final</version>
</dependency>
<dependency>
<groupId>org.wildfly</groupId>
<artifactId>wildfly-jmx</artifactId>
<version>8.1.0.Final</version>
</dependency>
Here is a test class demonstrating the use of JmsQueueControl via JMX:
package test.jmx.hornetq;
import org.hornetq.api.jms.management.JMSQueueControl;
import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
public class WildflyJmsControl {
public static void main(String[] args) {
try {
//Get a connection to the WildFly 8 MBean server on localhost
String host = "localhost";
int port = 9990; // management-web port
String urlString = System.getProperty("jmx.service.url","service:jmx:http-remoting-jmx://" + host + ":" + port);
JMXServiceURL serviceURL = new JMXServiceURL(urlString);
JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceURL, null);
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
String queueName = "testQueue"; // use your queue name here
String mbeanObjectName = "jboss.as:subsystem=messaging,hornetq-server=default,jms-queue=" + queueName;
ObjectName objectName = ObjectName.getInstance(mbeanObjectName);
JMSQueueControl jmsQueueControl = (JMSQueueControl) MBeanServerInvocationHandler.newProxyInstance(connection, objectName, JMSQueueControl.class, false);
assert jmsQueueControl != null;
long msgCount = jmsQueueControl.countMessages(null);
System.out.println(mbeanObjectName + " message count: " + msgCount);
jmsQueueControl.pause();
System.out.println("queue paused");
jmsQueueControl.resume();
System.out.println("queue resumed");
jmxConnector.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
To access hornetq management via JMS use:
package test.jms.hornetq;
import org.hornetq.api.core.TransportConfiguration;
import org.hornetq.api.core.client.*;
import org.hornetq.api.core.management.ManagementHelper;
import org.hornetq.core.remoting.impl.invm.InVMConnectorFactory;
public class HornetqService {
public void testPauseResumeQueue() {
// this class needs to run in the same jvm as the wildfly server (i.e. not a remote jvm)
try {
ServerLocator locator = HornetQClient.createServerLocatorWithoutHA(new TransportConfiguration(
InVMConnectorFactory.class.getName()));
ClientSession session = locator.createSessionFactory().createSession();
session.start();
ClientRequestor requester = new ClientRequestor(session, "jms.queue.hornetq.management");
String queueName = "testQueue"; // use your queue name here
// get queue message count
ClientMessage message = session.createMessage(false);
ManagementHelper.putAttribute(message, queueName, "messageCount");
ClientMessage reply = requester.request(message);
int count = (Integer) ManagementHelper.getResult(reply);
System.out.println("There are " + count + " messages in exampleQueue");
// pause the queue
message = session.createMessage(false);
ManagementHelper.putOperationInvocation(message, queueName, "pause");
requester.request(message);
// get queue paused
message = session.createMessage(false);
ManagementHelper.putAttribute(message, queueName, "paused");
reply = requester.request(message);
Object result = ManagementHelper.getResult(reply);
System.out.println("result: " + result.getClass().getName() + " : " + result.toString());
// resume queue
message = session.createMessage(false);
ManagementHelper.putOperationInvocation(message, queueName, "resume");
requester.request(message);
// get queue paused
message = session.createMessage(false);
ManagementHelper.putAttribute(message, queueName, "paused");
reply = requester.request(message);
Object result2 = ManagementHelper.getResult(reply);
System.out.println("result2: " + result2.getClass().getName() + " : " + result2.toString());
requester.close();
session.close();
}catch (Exception e){
System.out.println("Error pausing queue" + e.getMessage());
}
}
}
Are you looking for way to stop and and start delivery of messages? If so, then JMS defines connection.Stop method to pause delivery of messages. Message delivery can be resumed with connection.Start method.
So HornetQ JMS client would have these methods implemented. You will need to use these methods.

How do I setup JSR356 websockets in a jetty container

I see lots of tutorials about how to set up JSR356 websockets with an embedded web server of some sort.
But I want to add some websockets to an existing WAR deployed to a stand alone jetty installation, jetty jetty-9.2.3.v20140905, and I can find very little information on this. I can't figure out what is preventing it from working. As I understand it, the annotated server endpoints should be automatically handled by jetty.
ServerEndpoint:
#ServerEndpoint(value = "/myendpoint")
public class MyServerEndpoint {
private Logger logger = Logger.getLogger(this.getClass().getName());
#OnOpen
public void onOpen(Session s) {
logger.info("Server Connected ... " + s.getId());
s.getAsyncRemote().sendText("You are connected to the websocket server");
}
#OnMessage
public void onMessage(String message, Session session) {
// Sent the message back to all connected users
logger.info("Server Session " + session + " Received string message " + message);
for(Session s: session.getOpenSessions()) {
if (s.isOpen()) {
s.getAsyncRemote().sendText(message + " server response");
}
}
}
#OnClose
public void onClose(Session session, CloseReason reason) {
logger.info("Server Session " + session + " closed for reason " + reason);
}
}
ClientEndpoint:
#ClientEndpoint
public class MyClientEndpoint {
private Logger logger = Logger.getLogger(this.getClass().getName());
#OnOpen
public void onOpen(Session s) {
logger.info("Client Connected ... " + s.getId());
s.getAsyncRemote().sendText("hello from the client!");
}
#OnMessage
public void onMessage(String message, Session session) {
logger.info("Client Session " + session + " Received string message " + message);
}
#OnClose
public void onClose(Session session, CloseReason reason) {
logger.info("Client Session " + session + " closed for reason " + reason);
}
}
Here is the code (run inside some existing method) to connect the client to the server
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
String uri = "ws://localhost:8080/myWar/myendpoint";
container.connectToServer(MyClientEndpoint.class, URI.create(uri));
Yet spinning up jetty gives an error on the connectToServer line
2014-10-30 12:26:58.658:WARN:oeja.ServletContainerInitializersStarter:main:
org.eclipse.jetty.websocket.api.InvalidWebSocketException: Unable to instantiate websocket: class java.lang.Class
at org.eclipse.jetty.websocket.jsr356.ClientContainer.newClientEndpointInstance(ClientContainer.java:311)
at org.eclipse.jetty.websocket.jsr356.ClientContainer.connectToServer(ClientContainer.java:172)
All I can think is that the URI is incorrect. If jetty is running on 8080 and my .war is named myWar, and my end point is named myendpoint...is that not the correct URI?
Is there some additional step that must be done to 'activate' the server endpoint to listen for connections? I must be missing something obvious.
Strangely, your error message.
org.eclipse.jetty.websocket.api.InvalidWebSocketException:
Unable to instantiate websocket: class java.lang.Class
Means that the websocket implementation was handed a raw java.lang.Class to instantiate.
That's not going to work.
It also means no attempt was made to connect, as the WebSocket class itself was so bad that a connect was impossible.
Here's a short (valid and working) example showing its use.
package jetty.jsr356;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import javax.websocket.ClientEndpoint;
import javax.websocket.CloseReason;
import javax.websocket.CloseReason.CloseCodes;
import javax.websocket.ContainerProvider;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.WebSocketContainer;
#ClientEndpoint
public class TestClientAnnotatedClass
{
private static CountDownLatch closeLatch = new CountDownLatch(1);
#OnOpen
public void onOpen(Session session)
{
System.out.println("#OnOpen - " + session);
try
{
session.getBasicRemote().sendText("Rock it with Java WebSocket");
}
catch (IOException e)
{
e.printStackTrace();
}
}
#OnMessage
public void onMessage(Session session, String msg)
{
System.out.println("#OnMessage - ["+msg+"]");
try
{
session.close(new CloseReason(CloseCodes.NORMAL_CLOSURE,"Thanks"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
#OnClose
public void onClose(CloseReason close)
{
System.out.println("#OnClose - " + close);
closeLatch.countDown();
}
public static void main(String[] args)
{
try
{
WebSocketContainer ws = ContainerProvider.getWebSocketContainer();
ws.connectToServer(TestClientAnnotatedClass.class,new URI("ws://echo.websocket.org/?encoding=text"));
closeLatch.await();
}
catch (Throwable t)
{
t.printStackTrace();
}
}
}
You'll see output similar to this
2014-10-30 11:34:19.197:INFO::main: Logging initialized #71ms
#OnOpen - WebSocketSession[websocket=JsrAnnotatedEventDriver[websocket=jetty.jsr356.TestClientAnnotatedClass#5cca5f2c],behavior=CLIENT,connection=WebSocketClientConnection#6a2e714b{IDLE}{f=Flusher[queueSize=0,aggregateSize=0,failure=null],g=Generator[CLIENT,validating],p=Parser#55465b1f[ExtensionStack,s=START,c=0,len=0,f=null,p=WebSocketPolicy#7e087bf5[behavior=CLIENT,maxTextMessageSize=65536,maxTextMessageBufferSize=32768,maxBinaryMessageSize=65536,maxBinaryMessageBufferSize=32768,asyncWriteTimeout=60000,idleTimeout=300000,inputBufferSize=4096]]},remote=WebSocketRemoteEndpoint#5f025277[batching=true],incoming=JsrAnnotatedEventDriver[websocket=jetty.jsr356.TestClientAnnotatedClass#5cca5f2c],outgoing=ExtensionStack[queueSize=0,extensions=[],incoming=org.eclipse.jetty.websocket.jsr356.JsrSession,outgoing=org.eclipse.jetty.websocket.client.io.WebSocketClientConnection]]
#OnMessage - [Rock it with Java WebSocket]
#OnClose - CloseReason[1000,Thanks]
Jetty creates instances of annotated client endpoints using reflection (see /jetty/websocket/jsr356/ClientContainer#newClientEndpointInstance()) but Java is unable to instantiate a non-static inner class that way. The actual cause exception is something like java.lang.NoSuchMethodException: <pkg>.OuterClass$InnerClass.<init>() but it is swallowed.
Solution: annotated endpoint classes should be not nested or nested static (inner) classes.

Java SNMP4J Trap application is freezing the GUI

I have an SNMP trap application in Java that aims at listening to an SNMP agent and printing the received SNMP messages on a JTextArea in a JFrame window.
Part I below is my source code showing the content of class TrapReceiver. In this class, the listen method is the place that makes the most of the job. The class is intantiated within a JFrame class on which I intend to show the messages on so mentioned JTeaxtArea. I send the reference of the JTextArea object, the SNMP agent URL and the port into the constructor of the class TrapReceiver and then call the run method of TrapReceiver object to start execution in a seperate thread other than the JFrame instance. Part II below shows how I instantiate the class TrapReeceiver within the JFrame instance.
When I run the application I noticed that the JFrame instance (i.e. GUI) is freezing and no message is being printed on so mentioned JTeaxtArea within the JFrame instance which instantiates the class TrapReeceiver shown in Part I below.
My question is why the JFrame instance (i.e. GUI) is freezing although the TRapReceiver itself is executed as a separate thread? Also, I wonder what could be the possible solution to this freezing issue. Thanks in advance.
P.S.: I have verified that TrapReceiver works fine and can print messages to standard output when running as a stand alone application without GUI but it is this GUI that is freezing somehow due to some possible thread synch issue. I tried to run the TrapReceiver without putting onto a thread and even in this case the GUI was still freezing.
PART I
package com.[Intenionally removed].snmp;
import java.io.IOException;
import javax.swing.JTextArea;
import org.snmp4j.*;
import org.snmp4j.mp.MPv1;
import org.snmp4j.mp.MPv2c;
import org.snmp4j.security.Priv3DES;
import org.snmp4j.security.SecurityProtocols;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TcpAddress;
import org.snmp4j.smi.TransportIpAddress;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.transport.AbstractTransportMapping;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.snmp4j.util.MultiThreadedMessageDispatcher;
import org.snmp4j.util.ThreadPool;
public class TrapReceiver implements CommandResponder, Runnable {
private String targetSnmpAgentURL;
private int targetSnmpAgentPort;
private JTextArea outConsole;
public TrapReceiver() {
}
public TrapReceiver(JTextArea outConsole) {
this.outConsole = outConsole;
}
public TrapReceiver(JTextArea outConsole, String targetSnmpAgentURL, int targetSnmpAgentPort) {
this.targetSnmpAgentURL = targetSnmpAgentURL;
this.targetSnmpAgentPort = targetSnmpAgentPort;
this.outConsole = outConsole;
try {
listen(new UdpAddress(targetSnmpAgentURL + "/" + targetSnmpAgentPort));
} catch (IOException e) {
e.printStackTrace();
}
}
public final synchronized void listen(TransportIpAddress address) throws IOException {
AbstractTransportMapping transport;
if (address instanceof TcpAddress) {
transport = new DefaultTcpTransportMapping((TcpAddress) address);
} else {
transport = new DefaultUdpTransportMapping((UdpAddress) address);
}
ThreadPool threadPool = ThreadPool.create("DispatcherPool", 10);
MessageDispatcher mDispathcher = new MultiThreadedMessageDispatcher(
threadPool, new MessageDispatcherImpl());
// add message processing models
mDispathcher.addMessageProcessingModel(new MPv1());
mDispathcher.addMessageProcessingModel(new MPv2c());
// add all security protocols
SecurityProtocols.getInstance().addDefaultProtocols();
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
// Create Target
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString("public"));
Snmp snmp = new Snmp(mDispathcher, transport);
snmp.addCommandResponder(this);
transport.listen();
System.out.println("Listening on " + address);
try {
this.wait();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
/**
* This method will be called whenever a pdu is received on the given port
* specified in the listen() method
*/
#Override
public synchronized void processPdu(CommandResponderEvent cmdRespEvent) {
//System.out.println("Received PDU...");
outConsole.append("Received PDU...\n");
PDU pdu = cmdRespEvent.getPDU();
if (pdu != null) {
outConsole.append("Trap Type = " + pdu.getType() + "\n");
outConsole.append("Alarm Type: " + pdu.getVariableBindings().get(4) + "\n");
outConsole.append("Alarm Message: " + pdu.getVariableBindings().get(9) + "\n\n");
}
}
#Override
public void run() {
try {
listen(new UdpAddress(targetSnmpAgentURL + "/" + targetSnmpAgentPort));
} catch (IOException e) {
outConsole.append("\nError occured while listening to SNMP messages: \n" + e.getMessage() + "\n\n");
}
}
} //end of class TrapReceiver
PART II
In the following, I run an instance of class TrapReceiver within a thread.
private void jButtonStartListeningSNMPActionPerformed(java.awt.event.ActionEvent evt) {
Thread snmpThread =
new Thread(new TrapReceiver(jTextAreaSNMPAlarmOutput, jTextFieldSnmpAgentUrl.getText().trim(), Integer.parseInt(jTextFieldSnmpAgentPort.getText().trim())));
snmpThread.start()
}
the problem is that you are calling listen() in the TrapReceiver constructor, which happens on the gui thread. you only want to call listen() in the run() method, as that's the part that happens in the new thread.

Categories