private JSONObject _invoke(
			String ip,				  // ip or dns name of remote host
			int port,                 // port that RPC is listening on on the remote host
			String serviceName,       // name of the remote service
			String method,            // name of that service's method to invoke
			JSONObject userRequest,   // arguments to send to remote method
			int socketTimeout,        // max time to wait for reply
			boolean tryAgain          // true if an invocation failure on a persistent connection should cause a re-try of the call, false to give up
	) throws Exception {
		HashMap<String, Integer> key = new HashMap<String, Integer>();
		key.put(ip, port);
		Socket socket =  socketCache.get(key);
		boolean firstConnect = false;
		
		//stop current timer once _invoke is called
		timer.cancel();

		if (socket == null){
			firstConnect = true;
			socket = new Socket(ip, port);
			socket.setKeepAlive(true);
			socket.setReuseAddress(true);
			socketCache.put(key, socket);
		}

		TCPMessageHandler tcpMessageHandlerSocket = new TCPMessageHandler(socket);
		tcpMessageHandlerSocket.setTimeout(socketTimeout);
		tcpMessageHandlerSocket.setNoDelay(true);

		/* Initial control handshake: calling RPC service */
		if (firstConnect){
			// send connect msg
			JSONObject option = new JSONObject().put("connection", connection);
			RPCMessage rpcConnect = new RPCMessage();
			JSONObject jsonConnect = rpcConnect.marshall();
			jsonConnect.put("action", "connect").put("type", "control").put("options", option);
			persistentConnection = option.getString("connection").equalsIgnoreCase("keep-alive");
			RPCMessage connectMSG = RPCMessage.unmarshall(jsonConnect.toString());
			tcpMessageHandlerSocket.sendMessage(connectMSG.marshall());

			// read response msg
			JSONObject connectResponse = tcpMessageHandlerSocket.readMessageAsJSONObject();
			int callid = connectResponse.getInt("callid");
			String type = connectResponse.getString("type");
			if ( ! type.equalsIgnoreCase("OK") || callid != connectMSG.id()){
				throw new Exception("Bad connect response: '"  + "'");
			}
		}
		/* RPC Invocation */
		// send invoke msg
		RPCMessage rpcInvoke = new RPCMessage();
		JSONObject jsonInvoke = rpcInvoke.marshall();

		jsonInvoke.put("app", serviceName).put("args", userRequest)
		.put("type", "invoke").put("method", method);

		RPCMessage invokeMSG = RPCMessage.unmarshall(jsonInvoke.toString());
		tcpMessageHandlerSocket.sendMessage(invokeMSG.marshall());

		// read response invocation msg
		JSONObject invokeResponse = tcpMessageHandlerSocket.readMessageAsJSONObject();
		String type = invokeResponse.getString("type");
		int callid = invokeResponse.getInt("callid");
		if ( ! type.equalsIgnoreCase("OK") || callid != invokeMSG.id() || !invokeResponse.has("value"))
			throw new Exception("Bad invoke response");

		//if initially the header send to service without requiring persistent connection
		//close socket here

		if (!persistentConnection){
			socket.close();
		}
		
		timer = new Timer();
		timer.schedule(new Task(), DELAY);
		
		return invokeResponse.getJSONObject("value");
	}