How do I setup JSR356 websockets in a jetty container - java

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.

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')
}
}
}

java, webservice, SOAP, thread and websocket

I have a java client calling a java SOAP webservice deployed on glassfish server 4.1, that same java client also has a websocket connection to a server deployed on the same server.
Basically my architecture is this I have a java client calling a SOAP webservice, and connected to a java websocket server endpoint. Why? You can akin this to a bank, the client calls the SOAP webservice for say to check the account balance, then the websocket serves in notifying the client when a credit/debit has been made on the account.
Problem: In my main method I startup connection with the websocket, after that I call the webservice for a resource. Now the problem is I have to try several times before a call to the webservice is made. Its a very strange bug. I tried initializing the websocket on a different thread but I still get the same problem. Please see the code below
Bank webservice:
//relevant import statements
#WebService(serviceName = "BankService")
public class BankService {
private static final Map<Integer, AccountHolder> accountHolders = new ConcurrentHashMap<Integer, AccountHolder>();
private static AccountHolder customer, merchant;
public BankService(){
customer = new AccountHolder("Customer 1", 1234, 4321, 4000, null, null);
merchant = new AccountHolder("Merchant", 5678, 8765, 1000, null, null);
accountHolders.put(1234, customer);
accountHolders.put(5678, merchant);
}
#WebMethod(operationName = "getBalance")
public String getBalance(#WebParam(name = "acctNum") int acctNum) {
AccountHolder client;
synchronized(accountHolders){
client = accountHolders.get(acctNum);
}
return client.toString(); //returns a string containing balance
}
}
Bank notification websocket:
#ServerEndpoint(value="/n")
public class NotificationServer {
private Logger logger = Logger.getLogger(this.getClass().getName());
#OnOpen
public void onOpen(Session session) throws InterruptedException, IOException {
logger.info("Connected ... " + session.getId());
}
#OnMessage
public void notifyCustomer(String message, Session session) throws InterruptedException, IOException {
session.getBasicRemote().sendText(message);
}
#OnClose....
}
Client
public class Client {
static Client client;
private String getBalance(int acctNum) {
BankService_Service service = new BankWebService.BankService_Service();
BankService port = service.getBankServicePort();
return port.getBalance(acctNum);
}
private void wsc() throws InterruptedException {
BankNotificationClient bnc = new BankNotificationClient();
Thread thread = new Thread(bnc);
thread.start();
}
public static void main(String[] args) throws InterruptedException {
client = new Client();
client.wsc();
Scanner scanner = new Scanner(System.in);
while(!"quit".equals(scanner.next())){
if(scanner.next().equals("check")){
System.out.println(client.getBalance(1234));
}
}
}
}
Bank notification client:
//necessary import statements
import javax.*;
import org.glassfish.tyrus.client.ClientManager;
#ClientEndpoint
public class BankNotificationClient implements Runnable {
private static Session clientSession;
private Logger logger = Logger.getLogger(this.getClass().getName());
ClientManager clientws;
#OnOpen
public void onOpen(Session session) throws InterruptedException {
logger.info("Connected ... " + session.getId());
clientSession = session;
}
#OnMessage
public void onMessage(String message, Session session) {
....
}
#OnClose
public void onClose(Session session, CloseReason closeReason) {
logger.info(String.format("Session %s has closed because %s", session.getId(), closeReason));
}
public void run() {
try {
client = ClientManager.createClient();
client.connectToServer(BankNotificationClient.class, new URI("ws://localhost:8080/BankService/n"));
System.out.println("working" + client.getClass());
} catch (DeploymentException | URISyntaxException e) {
logger.info("Something has gone wrong initializing the socket");
}
}
}
I am using netbeans 8.0.2 as my development IDE. Having deployed the webservice and websocket to glassfish server 4.1. Running this would give the following output
Output:
Aug 05, 2015 11:31:18 PM BankNotificationHandler.BankNotificationClient onOpen
INFO: Connected ... 981de7b4-91b9-43b6-beaf-c219500ee77a
check
check
Dear Customer Customer 1, your account balance is 4000.
check
check
Dear Customer Customer 1, your account balance is 4000.
check
check
Dear Customer Customer 1, your account balance is 4000.
As you can see from the output I have to type and enter "check" twice before I get a result from the webservice. Using a debugger on the first entry of check the code suddenly halts at the if statement in the while loop, then I have to enter "check" again. On entering check it continues from where it stopped in the if statement line and proceeds to give me the desired result.
Please let me know if there is any other information I have left out.
Cheers.

How to implement websocket with Struts 2

I'm currently using Struts 2 as my framework and I need to have a Websocket feature so I can communicate with my client that is accessing it through HTML Websocket.
I have tried to use Java Websocket API (JSR 356) with Java application running on Tomcat 7.0.56. However, when I try it with Struts 2 framework, it does not work.
Some researches that I did suggested that it could have been because of the way Struts 2 maps the URL, but to no avail, I am still unable to communicate with the Websocket endpoint on my server.
Do anyone have any idea how to implement Websocket with Struts 2 framework?
The code that I used for the websocket is as follow:
#ServerEndpoint("/mssendpoint")
public class MSSEndpoint {
public static Logger logger = Logger.getLogger(MSSEndpoint.class);
/* Queue for all open WebSocket sessions */
static Queue<Session> queue = new ConcurrentLinkedQueue<Session>();
static Set<WebsocketListener> listeners = new HashSet<WebsocketListener>();
public static void send(String msg) {
try {
/* Send updates to all open WebSocket sessions */
for (Session session : queue) {
session.getBasicRemote().sendText(msg);
logger.info("Sent: " + msg);
}
}
catch (IOException e) {
logger.error(e.toString());
}
}
#OnOpen
public void openConnection(Session session) {
/* Register this connection in the queue */
queue.add(session);
logger.info("Connection opened.");
}
#OnClose
public void closedConnection(Session session) {
/* Remove this connection from the queue */
queue.remove(session);
logger.info("Connection closed.");
}
#OnError
public void error(Session session, Throwable t) {
/* Remove this connection from the queue */
queue.remove(session);
logger.info(t.toString());
logger.info("Connection error.");
}
#OnMessage
public void onMessage(String message, Session session) {
if (queue.contains(session)) {
notifyListener(message);
}
}
public static void addListener(WebsocketListener listener){
listeners.add(listener);
}
public static void removeListener(WebsocketListener listener){
listeners.remove(listener);
}
public void notifyListener(String message){
for (WebsocketListener listener : listeners) {
listener.onMessage(message);
}
}
}
I have used this exact same code on normal Java Servlet application running on Tomcat 7.0.56 and with a client, I could connect to it.
I used 'Simple Websocket Client' chrome extension as the client.
All I need was to connect to ws://localhost/myprojectname/mssendpoint and it will connect directly.
EDIT2:
I forgot to mention that the error was that when I tried to connect, it will simply say undefined when I use the Websocket Client. Assuming that my Struts 2 project is called cms, by right I should just need to access ws://localhost/myprojectname/mssendpoint. But then it produces that undefined message.

Java Jetty WebSocket Server: Handle broadcasts with asynchronously disconnecting clients

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.

RMI connection failure detection on callback

I'm writing a distributed app by Java RMI. The RMI client registers event handler / callback to RMI server, and the server calls the client's callback function when required. Now the problem is, when network connection failure (for example, Ethernet cable plugged out...), the RMI server and client won't be notified, and the RMI server fails when attempts to call the client's registered callback function.The RMI server cannot notify the RMI client about this issue too. Even worse, when network connection recovers, the RMI client service will still lose contact with RMI server because nobody notify her to reconnect.
My current idea is to implement a ping() method in RMI client in separate thread.
This thread could wake up at regular intervals and check on the server.
if failed, then farce to reconnect.
Any other elegant solutions? Hope you guys can help !
the interface
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MyInterface extends Remote {
public int RegisterEventHandler(RemoteMyEventHandler eventHandler) throws RemoteException;
public void unRegisterEventHandler(int eventHandlerId) throws RemoteException;
}
the RMI Server impelementation
import java.rmi.RemoteException;
import com.me.MyInterface;
public class MyInterfaceImpl implements MyInterface {
{
public void init() {
try {
//... initialize RMI server....
//....
} catch (Exception ex) {
ex.printStackTrace();
}
}
#Override
public int RegisterEventHandler(RemoteMyEventHandler eventHandler)
throws RemoteException {
return MyEventHandlerImp.getInstance().addHandler(eventHandler);
}
#Override
public void unRegisterEventHandler(int eventHandlerId)
throws RemoteException {
MyEventHandlerImp.getInstance().removeHandler(eventHandlerId);
}
}
//handler.notifyEventSnap(events);
the RMI Client implementation
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.Properties;
import org.apache.log4j.Logger;
import com.me.MyInterface;
public class MyService implements NotifyHandler{
{
private MyInterface client;
private MyEventHandler myEventHandler;
private void connectToServer() {
try {
//...
Registry registry = LocateRegistry.getRegistry(rmiHost, rmiPort);
client = (MyInterface) registry.lookup(MyCInterface.class.getName());
} catch (RemoteException er) {
} catch (NotBoundException en) {
} catch (Exception en) {
}
}
private void startService(){
//Attach my event handler
if(client != null)
{
myEventHandler = new MyEventHandler();
myEventHandlerId = client.RegisterEventHandler(myEventHandler);
}
}
}
when network connection failure (for example, Ethernet cable plugged out...), the RMI server and client won't be notified, and the RMI server fails when attempts to call the client's registered callback function.
Err, that is the notification to the server. The server just has to note this and try again later.
The RMI server cannot notify the RMI client about this issue too.
The client doesn't need to know.
Even worse, when network connection recovers, the RMI client service will still lose contact with RMI server because nobody notify her to reconnect.
The client doesn't have to 'reconnect'. There is no connect or reconnect step in RMI. As long as the client's JVM and remote objects remain up and exported respectively, the stubs at the server remain valid and can continue to be used by the server.
You're solving a non-problem.
You seem to be partially implementing a client/server session. This is a token that the server can track to ensure a client is valid. If there is an error while the server is communicating with the client the session should be ended and all references to the client removed.
Your server is already implementing a session with the integer used to unRegisterEventHandler. You should keep track of those integers somewhere like a Map. If the server cannot connect to a client it should simply unregister that client and make the session invalid by removing it from the map. The server should remove all references to the client and not attempt to communicate with the client until a new session is created.
If a client tries to communicate with the server it should get an InvalidException exception from the server. This way the client can attempt to make a new session by calling RegisterEventHandler in the catch block.
I worked on a project that dealt with this problem using a ping like you suggested at https://code.google.com/p/umuc-team-factor/
All client communication with the server was in a looped try catch block like
private void getSession() {
while(isRun()) {
try {
if(server == null) {
Logger.getLogger(JobClient.class.getName()).info("Server is null.");
setupServer();
}
UUID sid = server.getSession(this);
synchronized (this) {
id = sid;
}
Logger.getLogger(JobClient.class.getName()).info("Session id is " + id);
return;
} catch (RemoteException ex) {
Logger.getLogger(JobClient.class.getName()).info("Could not get session from server: " + ex + ". setting up server.");
setupServer();
}
}
}
This try to setup a session with the server until the program is stopped.
All server communication with the client should end the session for the client if there is a RemoteException thrown. c.status() is similar to a ping.
List<UUID> endSessions = new ArrayList<UUID>();
for (UUID id : copy.keySet()) {
ClientCallback c = copy.get(id).client;
try {
ClientStatus status = c.status();
Logger.getLogger(ProcessManager.class.getName()).info("got client status for " + id + ": " + status.getSessionID() + " -" + status.getJobStatus());
if (status.getSessionID() == null || !status.getSessionID().equals(id)) {
endSessions.add(id);
}
} catch (Exception ex) {
endSessions.add(id);
Logger.getLogger(ProcessManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
for (UUID id : endSessions) {
try {
endSession(id);
} catch (SessionExpiredException ex) {
Logger.getLogger(ProcessManager.class.getName()).log(Level.SEVERE, null, ex);
}
}

Categories