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);
}
}
Related
Currently, I'm implementing SFTP server which using ssh-core and apache mina-core.
I want to filter ip remote address. I implemented class CustomFilterAdapter extends IoFilterAdapter and I check if Ip remote is not in range of whitelist, i will not create session
public void sessionCreated(NextFilter nextFilter, IoSession session) throws Exception
{
log.debug(String.format("Start check access for ip %s.", ((InetSocketAddress)session.getRemoteAddress()).getAddress().getHostAddress()));
try
{
isAllowAccess(session);
}
catch (Exception ex)
{
log.error("Access dinied. " + ex.getMessage());
return;
}
super.sessionCreated(nextFilter, session);
}
However, It's not work. It's not run my code.
What steps i was missed or I implemented wrong filter.
So I currently have a lot of code, it will be difficult to break it all down into an SSCCE but maybe I will attempt to do so later if necessary.
Anyways, here is the gist: I have two processes communicating via RMI. It works. However I want to be able continue if the communcation if the host process (JobViewer) exits and then returns all in the life of the client process (Job).
Currently I have the binded name saved to a file everytime a Job starts up, and the JobViewer opens this file on startup. It works great, the correct binded name works. However, I get a NotBoundException every time I try to resume communication with a Job that I know for fact is still running when the JobViewer restarts.
My JobViewer implements an interface that extends Remote with the following methods:
public void registerClient(String bindedName, JobStateSummary jobSummary) throws RemoteException, NotBoundException;
public void giveJobStateSummary(JobStateSummary jobSummary) throws RemoteException;
public void signalEndOfClient(JobStateSummary jobSummary) throws RemoteException;
And my Job also implements a different interface that extends Remote with the following methods:
public JobStateSummary getJobStateSummary() throws RemoteException;
public void killRemoteJob() throws RemoteException;
public void stopRemoteJob() throws RemoteException;
public void resumeRemoteJob() throws RemoteException;
How do I achieve this? Here is some of my current code that inits the RMI if it helps...
JobViewer side:
private Registry _registry;
// Set up RMI
_registry = LocateRegistry.createRegistry(2002);
_registry.rebind("JOBVIEWER_SERVER", this);
Job side:
private NiceRemoteJobMonitor _server;
Registry registry = LocateRegistry.getRegistry(hostName, port);
registry.rebind(_bindedClientName, this);
Remote remoteServer = registry.lookup(masterName);
_server = (NiceRemoteJobMonitor)remoteServer;
_server.registerClient(_bindedClientName, _jobStateSummary);
I get a NotBoundException every time I try to resume communication with a Job that I know for fact is still running when the JobViewer restarts.
That can only happen if the JobViewer didn't rebind itself when it started up. More usually you get a NoSuchObjectException when you use a stale stub, i.e. a stub whose remote object has exited. In this case you should reaquire the stub, i.e. redo the lookup().
Why is the client binding itself to a Registry? If you want to register a callback, just pass this to the registerClient() method instead of the bind-name, and adjust its signature accordingly (using the client's remote interface as the parameter type). No need to have the server doing a lookup to the client Registry. No need for a client Registry at all.
My solution was to have the Job ping the JobViewer every so often:
while (true) {
try {
_server.ping();
// If control reaches here we were able to successfully ping the job monitor.
} catch (Exception e) {
System.out.println("Job lost contact with the job monitor at " + new Date().toString() + " ...");
// If control reaches we were unable to ping the job monitor. Now we will loop until it presumably comes back to life.
boolean foundServer = false;
while (!foundServer) {
try {
// Attempt to register again.
Registry registry = LocateRegistry.getRegistry(_hostName, _port);
registry.rebind(_bindedClientName, NiceSupervisor.this);
Remote remoteServer = registry.lookup(_masterName);
_server = (NiceRemoteJobMonitor)remoteServer;
_server.registerClient(_bindedClientName, _jobStateSummary);
// Ping the server for good measure.
_server.ping();
System.out.println("Job reconnected with the job monitor at " + new Date().toString() + " ...");
// If control reaches here we were able to reconnect to the job monitor and ping it again.
foundServer = true;
} catch (Exception x) {
System.out.println("Job still cannot contact the job monitor at " + new Date().toString() + " ...");
}
// Sleep for 1 minute before we try to locate the registry again.
try {
Thread.currentThread().sleep(PING_WAIT_TIME);
} catch (InterruptedException x) {
}
} // End of endless loop until we find the server again.
}
// Sleep for 1 minute after we ping the server before we try again.
try {
Thread.currentThread().sleep(PING_WAIT_TIME);
} catch (InterruptedException e) {
}
} // End of endless loop that we never exit.
I've been plying with RMI recently and while I managed to make it work on locahost I've been having all sorts of problem when trying to use a remote server. Here's the basic code I'm trying to run:
Server:
public class RmiServer extends UnicastRemoteObject implements RmiServerIntf {
public static final String MESSAGE = "Hello world";
public RmiServer() throws RemoteException {
}
public String getMessage() {
return MESSAGE;
}
public static void main(String args[]) {
System.out.println("RMI server started");
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
System.out.println("Security manager installed.");
} else {
System.out.println("Security manager already exists.");
}
try {
LocateRegistry.createRegistry(1099);
System.out.println("java RMI registry created.");
} catch (RemoteException e) {
e.printStackTrace();
}
try {
RmiServer obj = new RmiServer();
Naming.rebind("rmi://localhost/RmiServer", obj);
System.out.println("PeerServer bound in registry");
} catch (Exception e) {
e.printStackTrace();
}
}
}
Remote class interface:
public interface RmiServerIntf extends Remote {
public String getMessage() throws RemoteException;
}
Client:
public class RmiClient {
RmiServerIntf obj = null;
public String getMessage() {
try {
obj = (RmiServerIntf)Naming.lookup("rmi://54.229.66.xxx/RmiServer");
return obj.getMessage();
} catch (Exception e) {
e.printStackTrace();
return e.getMessage();
}
}
public static void main(String args[]) {
if (System.getSecurityManager() == null) {
System.setSecurityManager(new RMISecurityManager());
}
RmiClient cli = new RmiClient();
System.out.println(cli.getMessage());
}
}
rmi.policy file:
grant {
permission java.security.AllPermission;
};
I compiled the classes and created a stub for the server. Then I placed client, stub, interface and policy on my machine and server, stub, interface and policy on the remote machine. The remote server being a Linux machine I made all the files executable. I also added a rule on the local firewall allowing port 1099, and opened all ports on the remote machine
After this I navigated to the server's directory on the remote machine and inserted the following command:
java -Djava.security.policy=rmi.policy RmiServer
This didn't give me problems so I went back to the local machine and entered
java -Djava.security.policy=rmi.policy RmiClient
I wait, and wait and I get the error message:
Connection refused to host: 172.31.xx.xx; nested exception is: java.net.ConnectException: Connection timed out: connect
I've been fighting with these connection errors all day yesterday and this is as far as I got. I'm sure there's only one very small thing I'm still doing wrong but I just can't find what it is.
This may not solve your problem, but I've had similar issues with JPPF (via Java RMI) on Linux. The solution was to ensure that the ephemeral port range on the Client-side machine covered only ports that were allowable by the Client-side's local firewall. E.g., if your firewall allows ports 48000 to 64000 to be connected to by an external machine, ensure that your ephemeral port range also falls within 48000 to 64000. Give that a try and let us know what happens.
System.setProperty("java.rmi.server.hostname","10.0.3.73");
Please use the above statements in your RMIServer side code, and try and connect from remote client again. It worked for me
I am trying to teach myself some networking in Java using the Kryonet library. The following code is almost identical to the code in the kyronet tutorial. https://code.google.com/p/kryonet/#Running_a_server
The client is successfully sending the message "Here is the request!" to the server (the server is printing it out) however the client is not receiving any response from the server even though the server is sending one.
I've tried unsuccessfully to fix it, can anyone see or suggest a possible problem/solution with the code?
(The code follows)
Client
public class Client_test {
Client client = new Client();
public Client_test() {
Kryo kryo = client.getKryo();
kryo.register(SomeRequest.class);
kryo.register(SomeResponse.class);
client.start();
try {
client.connect(50000, "127.0.0.1", 54555, 54777);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
client.addListener(new Listener() {
public void received (Connection connection, Object object) {
if (object instanceof SomeResponse) {
SomeResponse response = (SomeResponse)object;
System.out.println(response.text);
}
}
});
SomeRequest request = new SomeRequest();
request.text = "Here is the request!";
client.sendTCP(request);
}
}
Server
public class ServerGame {
Server server = new Server();
public ServerGame() {
Kryo kryo = server.getKryo();
kryo.register(SomeRequest.class);
kryo.register(SomeResponse.class);
server.start();
try {
server.bind(54555, 54777);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
server.addListener(new Listener() {
public void received (Connection connection, Object object) {
if (object instanceof SomeRequest) {
SomeRequest request = (SomeRequest)object;
System.out.println(request.text);
SomeResponse response = new SomeResponse();
response.text = "Thanks!";
connection.sendTCP(response);
}
}
});
}
}
Response & Request classes
public class SomeRequest {
public String text;
public SomeRequest(){}
}
public class SomeResponse {
public String text;
public SomeResponse(){}
}
After many hours watching youtube videos and sifting through the web I found the answer. Which I will post on here as it seems that quite a few people have had this problem so I would like to spread the word.
Basically the client would shut down immediately, before it could receive and output the message packet. This is because "Starting with r122, client update threads were made into daemon threads, causing the child processes to close as soon as they finish initializing.", the solution is "Maybe you could use this? new Thread(client).start();".
So basically instead of using
client.start();
to start the client thread you must use
new Thread(client).start();
Which I believe stops the thread being made into a daemon thread which therefore stops the problem.
Source: https://groups.google.com/forum/?fromgroups#!topic/kryonet-users/QTHiVmqljgE
Yes, inject a tool like Fiddler in between the two so you can see the traffic going back and forth. It's always easier to debug with greater transparency, more information.
I'm trying to use server side code based on java NIO(non blocking) from 'The Rox Java NIO Tutorial'. There are lot of incoming socket connections and I would like to accept only 100. So if there are 100 active connections then new ones should be rejected/refused. But how to do that? There is only method ServerSocketChannel.accept() which returns SocketChannel object. Using that object I can call socketChannel.socket().close(), but connection is already open. Here is part of the code:
#Override
public void run() {
while (true) {
try {
// Wait for an event one of the registered channels
this.selector.select();
// Iterate over the set of keys for which events are available
Iterator selectedKeys = this.selector.selectedKeys().iterator();
while (selectedKeys.hasNext()) {
SelectionKey key = (SelectionKey) selectedKeys.next();
selectedKeys.remove();
if (!key.isValid()) {
continue;
}
// Check what event is available and deal with it
if (key.isAcceptable()) {
this.accept(key);
} else if (key.isReadable()) {
this.read(key);
} else if (key.isWritable()) {
this.write(key);
}
}
} catch (Exception e) {
logger.warn("Reading data", e);
}
}
}
and accept() mehod:
private void accept(SelectionKey key) throws IOException {
// For an accept to be pending the channel must be a server socket channel.
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
// Accept the connection and make it non-blocking
if (noOfConnections < MAX_CONNECTIONS) {
SocketChannel socketChannel = serverSocketChannel.accept();
Socket socket = socketChannel.socket();
socket.setKeepAlive(true);
socketChannel.configureBlocking(false);
// Register the new SocketChannel with our Selector, indicating
// we'd like to be notified when there's data waiting to be read
socketChannel.register(this.selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);//listener for incoming data: READ from client, WRITE to client
noOfConnections++;
logger.info("Accepted: " + socket.getRemoteSocketAddress().toString());
} else {
// REJECT INCOMING CONNECTION, but how?
logger.warn("Server is full: " + noOfConnections + " / " + MAX_CONNECTIONS);
}
}
If connection is not accepted then accept() method is being called over and over.
Thanks for help!
There is no way to accomplish that, but I doubt that that's what you really want, or at least what you really should do.
If you want to stop accepting connections, change the interestOps in the server socket channel's selection key to zero, and change it back to OP_ACCEPT when you are ready to accept again. In the interim, isAcceptable() will never be true, so the problem you describe won't occur.
However that won't cause further connections to be refused: it will just leave them on the backlog queue where in my opinion and that of the designers of TCP they belong. There will be another failure behaviour if the backlog queue fills up: its effect in the client is system-dependent: connection refusals and/or timeouts.
I think any tuning of a backlog queue hardly ever would be a good solution. But probably, you can just stop listening.
Well, I managed this problem next way:
Pending-state connections on socket are in kind of "middle_state", that mean you cannot control/reject them.
Backlog socket parameter may be used/ignored/treated in different way by specific VM.
That mean you have to accept particular connection to receive associated object and operate it.
Use one thread to accept connection, pass accepted connection to second thread for processing.
Create some variable for number of active connections.
Now, while number of active connections is less than wished maximum, accept connection, rise the number by 1, and pass to second thread for processing.
Otherwise, accept connection and close that immediately.
Also, in connection process thread, than finished, decrease the number of active connections by 1 to point there is one more free channel available.
EDT: Just made the "stub" for server machanism for Java.Net NIO.
May be adapted for OP needs:
package servertest;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Servertest extends Thread {
final int MAXIMUM_CONNECTIONS = 3;
int connectionnumber = 0;
/**
* #param args the command line arguments
* #throws java.io.IOException
*/
public static void main(String[] args){
new Servertest().start();
}
#Override
public void run() {
try {
ServerSocket sc = new ServerSocket(33000, 50, InetAddress.getLoopbackAddress());
while (sc.isBound()) {
Socket connection = sc.accept();
if(connectionnumber<=MAXIMUM_CONNECTIONS){
new ClientConnection(connection).start();
connectionnumber++;
} else {
//Optionally write some error response to client
connection.close();
}
}
} catch (IOException ex) {
Logger.getLogger(Servertest.class.getName()).log(Level.SEVERE, null, ex);
}
}
private class ClientConnection extends Thread{
private Socket connection;
public ClientConnection(Socket connection) {
this.connection=connection;
}
#Override
public void run() {
try {
//make user interaction
connection.close();
} catch (IOException ex) {
Logger.getLogger(Servertest.class.getName()).log(Level.SEVERE, null, ex);
}
connectionnumber--;
}
}
}