Skip to content

Websocket closes after being open #441

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Gillardo opened this issue May 8, 2017 · 23 comments
Open

Websocket closes after being open #441

Gillardo opened this issue May 8, 2017 · 23 comments
Labels

Comments

@Gillardo
Copy link

Gillardo commented May 8, 2017

Anyone have a problem with websockets? I have created my class but once i connect to the websocket, it close after about 2-3 seconds, with a code of 1006.

Here is my code, if anyone knows of anything i have done wrong.

import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import fi.iki.elonen.NanoWSD;

public class MyHttpServer extends NanoWSD {
    private Context context;
    private static final int PORT = 8765;
    private List<MyWebSocket> connections = new ArrayList<MyWebSocket>();

    public MyHttpServer(Context context) throws IOException {
        super(PORT);
        this.context = context;
    }

    public List<MyWebSocket> getConnections() {
        return this.connections;
    }

    @Override
    protected WebSocket openWebSocket(IHTTPSession handshake) {
        return new MyWebSocket(handshake, this);
    }

    @Override
    public Response serveHttp(IHTTPSession session) {
        String url = session.getUri();

        // some custom code here for serving web responses

        return super.serve(session);
    }
}

And i have also created a webSocket class

import fi.iki.elonen.NanoWSD;

public class MyWebSocket extends NanoWSD.WebSocket {

    MyHttpServer httpServer;
    NanoHTTPD.IHTTPSession httpSession;

    public MyWebSocket(NanoHTTPD.IHTTPSession handshakeRequest, MyHttpServer httpServer) {
        super(handshakeRequest);
        this.httpSession = handshakeRequest;
        this.httpServer = httpServer;
    }

    @Override
    protected void onOpen() {
        httpServer.getConnections().add(this);
    }

    @Override
    protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) {
        this.httpServer.getConnections().remove(this);
    }

    @Override
    protected void onMessage(NanoWSD.WebSocketFrame message) {

    }

    @Override
    protected void onPong(NanoWSD.WebSocketFrame pong) {

    }

    @Override
    protected void onException(IOException exception) {

    }
}

I am guessing this is all ok?? Why would the websocket be disconnecting after a couple seconds?

@LordFokas
Copy link
Member

You MUST ping websockets. It's part of the protocol. That's how the server knows the client is still there.
Whoever created this implementation did not add any sort of automatical pinging, so you must do it manually.

@HussainYousuf
Copy link

@Gillardo What is your javascript implementation for webscoket

@LordFokas
Copy link
Member

I'll just add that if you ping the socket from the server side clients will automatically pong. It has proven to be enough for me.

@Gillardo
Copy link
Author

My websocket javscript implementation is very simple, its used for angular2 and looks like this.

import { Injectable } from '@angular/core';
import { Device } from './models/device';
import { Http } from '@angular/http';
import { DbService } from './db.service';

@Injectable()
export class WebsocketService {

    private url: string;
    private ws: WebSocket = null;

    constructor(
        private http: Http,
        private dbService: DbService) {

    }

    connect(device: Device) {
        if (this.ws == null || this.ws.readyState == WebSocket.CLOSED) {
            this.url = device.url;
            this.ws = new WebSocket(device.url.replace(/http:/g, 'ws:') + ':8766');

            // When a connection is made
            this.ws.onopen = () => { this.onOpen(); } ;

            // When data is received
            this.ws.onmessage = (e) => { this.onMessage(e); };

            // A connection could not be made
            this.ws.onerror = (e) => { this.onError(e); };

            // A connection was closed
            this.ws.onclose = (e) => { this.onClose(e); };
        }
    }

    disconnect() {
        if (this.ws) {
            this.ws.close();
        }
    }

    private onOpen() {
        console.log('Opened connection ');
    }

    private onMessage(event) {
        console.log('message received');
    }

    private onError(event) {
        console.log(event);
    }

    private onClose(reason: CloseEvent) {
        console.log(reason);
    }
}

@Gillardo
Copy link
Author

All i need is for the client to open a connection and listen for a message. Once a message is received, the client will do something. This works if i use java_websocket but i would rather use 1 framework, then 2

@LordFokas
Copy link
Member

Have you checked for exceptions on the server side?

@DerTomm
Copy link

DerTomm commented Jul 15, 2018

I have to bring this question up again: As far as I see none of the NodeJS oder Browser implementations of WebSocket clients seem to send out a ping - which causes java.net.SocketTimeoutException: Read timed out exceptions and connections breaks every five seconds on the server side. And as far as I see the server does not send any ping frames either. So could you please tell us how to properly keep a Websocket connection open?

@LordFokas
Copy link
Member

On my implementation I spawn a thread that every n seconds loops the list of active sockets and pings them all.

@Gillardo
Copy link
Author

@LordFokas be very interested in that code 😄

@LordFokas
Copy link
Member

LordFokas commented Jul 15, 2018

BE ADVISED THIS MIGHT BE SUB-OPTIMAL, THIS WAS SOMEWHAT OF A HACK THAT WORKED AND I KEPT

package mohawk.http.servers;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import mohawk.api.http.IWebSocketHandler;
import mohawk.http.HandlerRegistry;
import mohawk.http.MohawkWebSocket;
import fi.iki.elonen.NanoWSD;

public class MohawkWSD extends NanoWSD{
	private static final byte[] PING_PAYLOAD = "1337DEADBEEFC001".getBytes();
	private List<MohawkWebSocket> toAdd, toRemove;
	private Thread wsPinger;
	
	public MohawkWSD(String hostname, int port) {
		super(hostname, port);
	}
	
	@Override
	public void start() throws IOException {
		super.start();
		toRemove = new LinkedList<>();
		toAdd = new LinkedList<>();
		wsPinger = new Thread(new Runnable(){
			@Override public void run(){
				List<MohawkWebSocket> active = new LinkedList<>();
				long nextTime = System.currentTimeMillis();
				while(MohawkWSD.this.isAlive()){
					nextTime += 4000L;
					while(System.currentTimeMillis() < nextTime){
						try{ Thread.sleep(nextTime - System.currentTimeMillis()); }
						catch(InterruptedException ignored){}
					}
					synchronized(toAdd){
						active.addAll(toAdd);
						toAdd.clear();
					}
					synchronized(toRemove){
						active.removeAll(toRemove);
						toRemove.clear();
						for(MohawkWebSocket ws : active){
							try{ ws.ping(PING_PAYLOAD); }
							catch(Exception e){ toRemove.add(ws); }
						}
					}
				}
			}
		});
		wsPinger.setDaemon(true);
		wsPinger.start();
	}
	
	@Override
	public final MohawkWebSocket openWebSocket(IHTTPSession handshake) {
		IWebSocketHandler handler = HandlerRegistry.WS_REGISTRY.getHandler(handshake.getUri());
		MohawkWebSocket socket = new MohawkWebSocket(this, handshake, handler);
		synchronized(toAdd){
			if(!toAdd.contains(socket))
				toAdd.add(socket);
		}
		return socket;
	}
	
	public final void onSocketClose(MohawkWebSocket socket){
		synchronized(toRemove){
			if(!toRemove.contains(socket))
				toRemove.add(socket);
		}
	}
}

I also override the sockets like this, to keep track of missed pings:

	@Override
	public void ping(byte[] payload) throws IOException {
		super.ping(payload);
		this.ping++;
		if(ping - pong > 3) close(CloseCode.GoingAway, "Missed too many ping requests.", false);
	}
	
	@Override
	protected void onPong(WebSocketFrame pong){
		this.pong++;
	}

@LordFokas
Copy link
Member

You see, my goal with the changes I attempted to introduce to NanoHTTPD in 3.0 were exactly aiming to allow setting up the system like a pipeline, where you can change components independently to add or modify behavior seamlessly in each stage. It still has a long way to go, and I'm not sure which version of the code I'm using here, it might be a commit somewhere between versions, but this should work regardless.

@DerTomm
Copy link

DerTomm commented Jul 15, 2018

@LordFokas Thanks for providing your code - now I think I understand the behaviour a little bit better.

@LordFokas
Copy link
Member

Feel free to give me feedback on it ;)

@ItsHarper
Copy link

ItsHarper commented Aug 2, 2018

It's probably better to use a SecheduledExecutorService(Executors.newSingleThreadScheduledExecutor()) and call scheduleAtFixedRate() instead of implementing the thread yourself. (I'm also working on implementing websockets with NanoHTTPD for a project).

@LordFokas
Copy link
Member

@NoahAndrews that's for when you want to write proper code that performs. I just hacked at it until it worked and stopped there, had way more pressing matters to attend to :p

@ItsHarper
Copy link

Yeah, I get it. You did say this though:

Feel free to give me feedback on it ;)

@LordFokas
Copy link
Member

Ah yes, I meant something more like if it didn't work as expected so I could have a look at my own mess

@ItsHarper
Copy link

Gotcha gotcha. I'll probably post a link to my implementation once it's finished and publicly viewable, for anyone else who stumbles upon this issue.

@corwin-of-amber
Copy link

I am also having the same difficulties. Is there a cleaner way to deal with the need to ping the sockets, than the one @LordFokas posted? It seems like a lot of trouble just to pacify NanoWSD into not closing the connection. I think perhaps this should be bumped from a question to a bug?

@corwin-of-amber
Copy link

corwin-of-amber commented Oct 18, 2019

I simplified the code a bit by using java.util.Timer (Java 7), which seems to work well on Android.

public class MyWebSocket extends WebSocket {

    private TimerTask ping = null;

    ....

    @Override
    protected void onOpen() {
        if (ping == null) {
            ping = new TimerTask() {
                @Override
                public void run(){
                    try { ping(PING_PAYLOAD); }
                    catch (IOException e) { ping.cancel(); }
                }
            };
            new Timer().schedule(ping, 1000, 2000);
        }
    }

    @Override
    protected void onClose(NanoWSD.WebSocketFrame.CloseCode code, String reason, boolean initiatedByRemote) {
        if (ping != null) ping.cancel();
    }

    ....
}

@spartacusrex99
Copy link

spartacusrex99 commented May 22, 2020

(Sorry - i posted this on another thread.. which is CLOSED.. soo.. may help here too )

Same thing here.. Simple FIX.

If you follow the class trail you end up at Line 165 of NanoHTTPD..

public static final int SOCKET_READ_TIMEOUT = 5000;

..Just change that.

OR.. better.. If you dig a little deeper you'll notice

public void start(final int timeout) throws IOException {
start(timeout, true);
}

And just the plain start() is called in the echosocket.. just switch it for the one where you specify a timeout.

Set to 0 for no timeout.

It works.

Big Up LordFokas for sharing this great little project with all of us.. :)

@morimoriysmoon
Copy link

I hope this PR is helpful for someone in trouble with WebSocket.
#618

@jzlhll
Copy link

jzlhll commented May 11, 2025

Exception(Read timed out) and "close the connection".

The reason is:
first: It must ping periodically;
second: readTimeout must be bigger than your ping interval time.

Do like this:

const val HEARTBEAT_INTERVAL: Long = 2 * 60 * 1000
 //bigger than heartbeat interval
const val WEBSOCKET_READ_TIMEOUT = HEARTBEAT_INTERVAL + 30


//Start your server like this:
websocketServer.start(WEBSOCKET_READ_TIMEOUT, false)


//in your XXWebSocket onOpen(),  ping periodically:
override fun onOpen() {
    httpServer.connections.add(this)
    httpServer.heartbeatScope.launch { //you can change into singleThreadExecutor or other.
        while (isActive) {
            try {
                ping(PING_PAYLOAD)
            } catch (e: IOException) {
                onException(e)
            }
            delay(HEARTBEAT_INTERVAL)
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants