I am using Google AppEngine's Channel API. I am having some issue to re-start the lost connection due to the user's network connection. When you loose the internet connection, channel call onError but it will not call onClose. As far as JavaScript object is concerned, the channel socket is open.
How do you handle lost connection due to the internet issue? I am thinking of 1) by trigger to close the channel and re-open it when RPC unrelated to the channel somewhere in the application succeeds for the first time (which indicates the internet is alive again) or 2) Use timer that runs all the time and pings the server for network status (which was the point of introducing the long polling to avoid consuming unwanted resource this way). Any other ideas would be great.
Observation:
When the internet connection is dead, onError is called in incremental interval (10sec, 20sec, 40sec) twice. Once the internet connection is back, channel does not resume connection. It stops working without any indication that it is dead.
Thanks.
When you see the javascript console, presumably you will see "400 Unknown SID Error".
If so, here is my workaround for this. This is a service module for AngularJS, but please look at the onerror callback. Please try this workaround and let me know if it works or not.
Added: I neglected to answer your main question, but in my opinion, it is hard to determine if you're connected to the internet unless actually pinging the "internet". So probably you may want to use some retry logic similar to the following code with some tweak. In the following example, I just retrying 3 times, but you can do it more with some back offs. However, I think the best way to handle this is, when the app consume the retry max count, you can indicate the user that the app lost the connection, ideally showing a button or a link to re-connect to the channel service.
And, you can also track the connection on the server side, see:
https://developers.google.com/appengine/docs/java/channel/#Java_Tracking_client_connections_and_disconnections
app.factory('channelService', ['$http', '$rootScope', '$timeout',
function($http, $rootScope, $timeout) {
var service = {};
var isConnectionAlive = false;
var callbacks = new Array();
var retryCount = 0;
var MAX_RETRY_COUNT = 3;
service.registerCallback = function(pattern, callback) {
callbacks.push({pattern: pattern, func: callback});
};
service.messageCallback = function(message) {
for (var i = 0; i < callbacks.length; i++) {
var callback = callbacks[i];
if (message.data.match(callback.pattern)) {
$rootScope.$apply(function() {
callback.func(message);
});
}
}
};
service.channelTokenCallback = function(channelToken) {
var channel = new goog.appengine.Channel(channelToken);
service.socket = channel.open();
isConnectionAlive = false;
service.socket.onmessage = service.messageCallback;
service.socket.onerror = function(error) {
console.log('Detected an error on the channel.');
console.log('Channel Error: ' + error.description + '.');
console.log('Http Error Code: ' + error.code);
isConnectionAlive = false;
if (error.description == 'Invalid+token.' || error.description == 'Token+timed+out.') {
console.log('It should be recovered with onclose handler.');
} else {
// In this case, we need to manually close the socket.
// See also: https://code.google.com/p/googleappengine/issues/detail?id=4940
console.log('Presumably it is "Unknown SID Error". Try closing the socket manually.');
service.socket.close();
}
};
service.socket.onclose = function() {
isConnectionAlive = false;
console.log('Reconnecting to a new channel');
openNewChannel();
};
console.log('A channel was opened successfully. Will check the ping in 20 secs.');
$timeout(checkConnection, 20000, false);
};
function openNewChannel(isRetry) {
console.log('Retrieving a clientId.');
if (isRetry) {
retryCount++;
} else {
retryCount = 0;
}
$http.get('/rest/channel')
.success(service.channelTokenCallback)
.error(function(data, status) {
console.log('Can not retrieve a clientId');
if (status != 403 && retryCount <= MAX_RETRY_COUNT) {
console.log('Retrying to obtain a client id')
openNewChannel(true);
}
})
}
function pingCallback() {
console.log('Got a ping from the server.');
isConnectionAlive = true;
}
function checkConnection() {
if (isConnectionAlive) {
console.log('Connection is alive.');
return;
}
if (service.socket == undefined) {
console.log('will open a new connection in 1 sec');
$timeout(openNewChannel, 1000, false);
return;
}
// Ping didn't arrive
// Assuming onclose handler automatically open a new channel.
console.log('Not receiving a ping, closing the connection');
service.socket.close();
}
service.registerCallback(/P/, pingCallback);
openNewChannel();
return service;
}]);
Related
I have a server which streams the data for a given request below is the method which does that function
#Override
public void getChangeFeed(ChangeFeedRequest request, StreamObserver<ChangeFeedResponse> responseObserver) {
long queryDate = request.getFromDate();
long offset = request.getPageNo();
ChangeFeedResponse changeFeedResponse = processData(responseObserver, queryDate, offset);
while(true){
if(changeFeedResponse!=null && !changeFeedResponse.getFinalize()){
responseObserver.onNext(changeFeedResponse);
changeFeedResponse = processData(responseObserver, changeFeedResponse.getToDate(), changeFeedResponse.getPageNo());
}else{
break;
}
}
responseObserver.onNext(changeFeedResponse);
responseObserver.onCompleted();
}
When the client get disconnected the server still keeps on processing, this might be issue when multiple clients are fetching the data. Need to know how to tell server to stop processing
There's two fairly-equivalent ways. One is to use the Context, which is cancelled when the RPC is completed/cancelled:
while(!Context.current().isCancelled()){ // THIS LINE CHANGED
if(changeFeedResponse!=null && !changeFeedResponse.getFinalize()){
responseObserver.onNext(changeFeedResponse);
changeFeedResponse = processData(responseObserver, changeFeedResponse.getToDate(), changeFeedResponse.getPageNo());
}else{
break;
}
}
The other would be to use the ServerCallStreamObserver:
// THE NEXT TWO LINES CHANGED
ServerCallStreamObserver scso = (ServerCallStreamObserver) responseObserver;
while(!scso.isCancelled()){
if(changeFeedResponse!=null && !changeFeedResponse.getFinalize()){
responseObserver.onNext(changeFeedResponse);
changeFeedResponse = processData(responseObserver, changeFeedResponse.getToDate(), changeFeedResponse.getPageNo());
}else{
break;
}
}
Both approaches can also provide notification when a cancellation occurs, but polling is easiest in your case.
We're having some trouble trying to implement a Pool of SftpConnections for our application.
We're currently using SSHJ (Schmizz) as the transport library, and facing an issue we simply cannot simulate in our development environment (but the error keeps showing randomly in production, sometimes after three days, sometimes after just 10 minutes).
The problem is, when trying to send a file via SFTP, the thread gets locked in the init method from schmizz' TransportImpl class:
#Override
public void init(String remoteHost, int remotePort, InputStream in, OutputStream out)
throws TransportException {
connInfo = new ConnInfo(remoteHost, remotePort, in, out);
try {
if (config.isWaitForServerIdentBeforeSendingClientIdent()) {
receiveServerIdent();
sendClientIdent();
} else {
sendClientIdent();
receiveServerIdent();
}
log.info("Server identity string: {}", serverID);
} catch (IOException e) {
throw new TransportException(e);
}
reader.start();
}
isWaitForServerIdentBeforeSendingClientIdent is FALSE for us, so first of all the client (we) send our identification, as appears in logs:
"Client identity String: blabla"
Then it's turn for the receiveServerIdent:
private void receiveServerIdent() throws IOException
{
final Buffer.PlainBuffer buf = new Buffer.PlainBuffer();
while ((serverID = readIdentification(buf)).isEmpty()) {
int b = connInfo.in.read();
if (b == -1)
throw new TransportException("Server closed connection during identification exchange");
buf.putByte((byte) b);
}
}
The thread never gets the control back, as the server never replies with its identity. Seems like the code is stuck in this While loop. No timeouts, or SSH exceptions are thrown, my client just keeps waiting forever, and the thread gets deadlocked.
This is the readIdentification method's impl:
private String readIdentification(Buffer.PlainBuffer buffer)
throws IOException {
String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
if (ident.isEmpty()) {
return ident;
}
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
"Server does not support SSHv2, identified as: " + ident);
return ident;
}
Seems like ConnectionInfo's inputstream never gets data to read, as if the server closed the connection (even if, as said earlier, no exception is thrown).
I've tried to simulate this error by saturating the negotiation, closing sockets while connecting, using conntrack to kill established connections while the handshake is being made, but with no luck at all, so any help would be HIGHLY appreciated.
: )
I bet following code creates a problem:
String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
if (ident.isEmpty()) {
return ident;
}
If the IdentificationStringParser.parseIdentificationString() returns empty string, it will be returned to the caller method. The caller method will keep calling the while ((serverID = readIdentification(buf)).isEmpty()) since the string is always empty. The only way to break the loop would be if call to int b = connInfo.in.read(); returns -1... but if server keeps sending the data (or resending the data) this condition is never met.
If this is the case I would add some kind of artificial way to detect this like:
private String readIdentification(Buffer.PlainBuffer buffer, AtomicInteger numberOfAttempts)
throws IOException {
String ident = new IdentificationStringParser(buffer, loggerFactory).parseIdentificationString();
numberOfAttempts.incrementAndGet();
if (ident.isEmpty() && numberOfAttempts.intValue() < 1000) { // 1000
return ident;
} else if (numberOfAttempts.intValue() >= 1000) {
throw new TransportException("To many attempts to read the server ident").
}
if (!ident.startsWith("SSH-2.0-") && !ident.startsWith("SSH-1.99-"))
throw new TransportException(DisconnectReason.PROTOCOL_VERSION_NOT_SUPPORTED,
"Server does not support SSHv2, identified as: " + ident);
return ident;
}
This way you would at least confirm that this is the case and can dig further why .parseIdentificationString() returns empty string.
Faced a similar issue where we would see:
INFO [net.schmizz.sshj.transport.TransportImpl : pool-6-thread-2] - Client identity string: blablabla
INFO [net.schmizz.sshj.transport.TransportImpl : pool-6-thread-2] - Server identity string: blablabla
But on some occasions, there were no server response.
Our service would typically wake up and transfer several files simultaneously, one file per connection / thread.
The issue was in the sshd server config, we increased maxStartups from default value 10
(we noticed the problems started shortly after batch sizes increased to above 10)
Default in /etc/ssh/sshd_config:
MaxStartups 10:30:100
Changed to:
MaxStartups 30:30:100
MaxStartups
Specifies the maximum number of concurrent unauthenticated connections to the SSH daemon. Additional connections will be dropped until authentication succeeds or the LoginGraceTime expires for a connection. The default is 10:30:100. Alternatively, random early drop can be enabled by specifying the three colon separated values start:rate:full (e.g. "10:30:60"). sshd will refuse connection attempts with a probability of rate/100 (30%) if there are currently start (10) unauthenticated connections. The probability increases linearly and all connection attempts are refused if the number of unauthenticated connections reaches full (60).
If you cannot control the server, you might have to find a way to limit your concurrent connection attempts in your client code instead.
I'm trying to implement simple Android firewall app through VPN service.
Basic scheme looks like this:
I'm trying to simply forward packets through my VPN service. So I've started with implementing ToyVpn Google example (time to time taking a glance at this example) for my purposes, but it's not working: when my VPN is running I see that packets are sent, but I've got no response.
App is written on Kotlin, but it's not complicated for Java lovers.
My Builder is configured with IP and route as shown below:
val builder = Builder()
builder.addAddress("10.0.0.2", 32).addRoute("0.0.0.0", 0)
mInterface = builder.establish()
After that I'm setting a tunnel a way it's shown in ToyVpn, but my VPN server destination IP address is local:
val tunnel = DatagramChannel.open()
if (!protect(tunnel.socket())) {
throw IllegalStateException("Cannot protect the tunnel");
}
tunnel.connect(InetSocketAddress("127.0.0.1", 55555))
tunnel.configureBlocking(false)
protect(tunnel.socket())
After that I'm trying to forward packets from in and out FileInputStream's (also taken from ToyVpn):
val `in` = FileInputStream(mInterface!!.fileDescriptor)
val out = FileOutputStream(mInterface!!.fileDescriptor)
var timer = 0
while (true) {
var idle = true
var length = `in`.read(packet.array())
if (length > 0) {
packet.limit(length)
tunnel.write(packet);
packet.clear()
idle = false
if (timer < 1) {
timer = 1
}
}
length = tunnel.read(packet)
if (length > 0) {
if (packet.get(0).toInt() !== 0) {
out.write(packet.array(), 0, length)
}
packet.clear()
idle = false
if (timer > 0) {
timer = 0
}
}
if (idle) {
Thread.sleep(100)
timer += if (timer > 0) 100 else -100
if (timer < -15000) {
packet.put(0.toByte()).limit(1)
for (i in 0..2) {
packet.position(0)
tunnel.write(packet)
}
packet.clear()
timer = 1
}
if (timer > 20000) {
throw IllegalStateException("Timed out")
}
}
Thread.sleep(50)
}
My first question: What am I doing wrong? The code is clear and should work properly, but it does not!
Basicly I want my service to fitler packets by IP address. I know that huge apps like tPacketCapture parse packets from the scratch and than reconstruct them.
My second question: Is there a way to make packet filtering simplier if I want to fetch only the IP address?
P.S.: Actualy I've already asked a similar question about it here a few days ago, but error had another nature related to Kotlin language, which app is developed on. Also I've seen this and this questions, but the answers there didn't help me.
I have configured the analog local phone with cisco adapter, so I can make any outbound call from SIP phone. But I can't achieve this by AMI which calls to outbound channel through trunk then plays prompt.
manager.conf:
[asteriskjava]
secret = asteriskjava
deny = 0.0.0.0/0.0.0.0
permit = 127.0.0.1/255.255.255.0
read = all
write = all
extensions.conf:
[bulk]
exten => 8,1,Playback(thank-you-cooperation)
exten => h,1,Hangup
source code:
public class HelloManager
{
private ManagerConnection managerConnection;
public HelloManager() throws IOException
{
ManagerConnectionFactory factory = new ManagerConnectionFactory(
"localhost", "asteriskjava", "asteriskjava");
this.managerConnection = factory.createManagerConnection();
}
public void run() throws IOException, AuthenticationFailedException,
TimeoutException
{
OriginateAction originateAction;
ManagerResponse originateResponse;
originateAction = new OriginateAction();
originateAction.setChannel("SIP/405/7000000");
originateAction.setContext("bulk");
originateAction.setExten("8");
originateAction.setPriority(new Integer(1));
originateAction.setAsync(true);
// connect to Asterisk and log in
managerConnection.login();
// send the originate action and wait for a maximum of 30 seconds for Asterisk
// to send a reply
originateResponse = managerConnection.sendAction(originateAction, 30000);
// print out whether the originate succeeded or not
System.out.println("---" + originateResponse.getResponse());
// and finally log off and disconnect
managerConnection.logoff();
}
}
Where 405 is the UserID of CISCO adapter for outgoing calls, 7000000 is a sample cell phone number.
Here is the logs:
== Manager 'asteriskjava' logged on from 127.0.0.1
== Manager 'asteriskjava' logged off from 127.0.0.1
== Using SIP RTP CoS mark 5
> Channel SIP/405-0000000c was answered.
-- Executing [8#bulk:1] Playback("SIP/405-0000000c", "thank-you-cooperation") in new stack
-- <SIP/405-0000000c> Playing 'thank-you-cooperation.gsm' (language 'en')
-- Auto fallthrough, channel 'SIP/405-0000000c' status is 'UNKNOWN'
-- Executing [h#bulk:1] Hangup("SIP/405-0000000c", "") in new stack
== Spawn extension (bulk, h, 1) exited non-zero on 'SIP/405-0000000c'
I think SIP/405 is answering, executing Playback then hangs up, not redirecting to sample number.
Any suggestions?
EDIT: How can I configure my cisco adapter in order to redirect outgoing calls, not to answer and make the bridge?
You have configure ring, answer and busy recognition on your ATA.
Asterisk work as you requested as far as i can see from your trace.
If adapter not calling, you have check with your adapater settings. For example it can be calling in tone, why you line expect it is pulse.
Also can be incorrect adapter type for your task. For calling out via PSTN line you need FXO adapter,not FXS.
I have:
MqttAsyncClient mq;
...
mq = new MqttAsyncClient(myServer1,"app1");
mq.connect();
...
//(1)
//doing something with mq (pub/sub)
...
mq.disconnect();
mq.close();
//(2)
Now I am using a Monitoring Console and I see:
In (1), 3 Mqtt threads:
MQTT REC, MQTT SND and MQTT Call
In (2), 2 Mqtt threads:
MQTT SND and MQTT Call
After further seconds only 1 thread
MQTT CALL
The CALL thread will never be stopped.
How come ?
Ensure the the asyncclient had been disconnect before invoking close() method, otherwise the thred of async will blocking forever. you can deal like this(just same with #Tom&#Mehmet Ince):
IMqttToken token = mq.disconnect();
int count = 0;
while (count++<5) {
if (token.isComplete()) {
mq.close();
break;
}
try {
Thread.sleep(2000l);
} cath(Exception e) {
//TODO
}
}
if (count > 5) {
mq.disconnectForcibly();
mq.close();
}
I think use should use : MqttClient client = new MqttClient... then call client.connect();
Because, It will invoke the code : aClient.connect(options, null, null).waitForCompletion(getTimeToWait()); (you can check the source code)
so it can make sure the connection is really completed.