简体   繁体   中英

Handling sockets in java

I am creating a socket to a server in java and after the socket is connected it creates a new thread which can access the socket input and output stream and this thread then blocks and processes the input lines when they come in.

I understand that the readln method on the BufferedReader will return null when the input stream ends. This doesn't necessarily mean that the socket is closed though does it? What does this mean? So I would then want to run the close method on the socket to close it nicely.

I also understand that the readln method can throw an IOException and that this is thrown after the close method is called on a socket if it is currently blocking. When else can this be thrown? Could the socket still be open after this is thrown or would it always be closed and ready for garbage collection etc.

This is the code I have at the moment and I don't really know how to handle disconnects properly. At the moment I think this could end up in a deadlock if the disconnect method is called whilst the socket is waiting for a line because disconnect will call close on the socket. This will then throw the IOException on readLine and this will then result in that catch block calling disconnect again.

public class SocketManager {

    private Socket socket = null;
    private PrintWriter out = null;
    private BufferedReader in = null;

    private String ip;
    private int port;

    private Object socketLock = new Object();

    public SocketManager(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public void connect() throws UnableToConnectException, AlreadyConnectedException {
        synchronized(socketLock) {
            if (socket == null || socket.isClosed()) {
                throw (new AlreadyConnectedException());
            }
            try {
                socket = new Socket(ip, port);
                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            } catch (IOException e) {
                throw (new UnableToConnectException());
            }
            new Thread(new SocketThread()).start();
        }
    }

    public void disconnect() throws NotConnectedException {
        synchronized(socketLock) {
            if (isConnected()) {
                throw (new NotConnectedException());
            }
            try {
                socket.close();
            } catch (IOException e) {}
        }
    }

    public boolean isConnected() {
        synchronized(socketLock) {
            return (socket != null && !socket.isClosed());
        }
    }

    private class SocketThread implements Runnable {

        @Override
        public void run() {
            String inputLine = null;
            try {
                while((inputLine = in.readLine()) != null) {
                    // do stuff
                }
                if (isConnected()) {
                    try {
                        disconnect();
                    } catch (NotConnectedException e) {}
                }
            } catch (IOException e) {
                // try and disconnect (if not already disconnected) and end thread
                if (isConnected()) {
                    try {
                        disconnect();
                    } catch (NotConnectedException e1) {}
                }
            }
        }

    }
}

I basically want to know the best way of achieving the following:

  • Writing a connect method that connects to a socket and starts a separate thread listening for input.
  • Writing a disconnect method that disconnects from the socket and terminates the thread that's listening for input.
  • Handling the scenario of the connection to the remote socket being broken.

I have read through the java tutorial on sockets but in my opinion it doesn't really cover these in much detail.

Thanks!

When I said that it could end up as a deadlock I think I was wrong.

What would happen is:

  1. disconnect() called whilst in.readLine() blocking
  2. socket.close() executed.
  3. in.readline() throws IOException.

I was then thinking that the exception handler in the SocketThread would call disconnect whilst disconnect is waiting for that exception to finish. It wouldn't matter through because they are both different threads so the code in disconnect() would continue whilst the exception is being caught in the SocketThread. The SocketThread would then call disconnect() but would then have to wait until the first instance of disconnect() finished. Then disconnect() would execute again but would get the NotConnectedException thrown which would be caught in the SocketThread and nothing would happen. The SocketThread would exit and that's the wanted result.

However I have looked into the socket class and it also contains these methods:

  • shutdownInput()
  • shutdownOutput()

shutdownInput() sends the end EOF symbol into the input stream meaning in.readline() returns null and the loop exits cleanly. shutdownOutput() sends the TCP termination sequence informing the server that it's disconnecting.

Calling both of these before socket.close() makes more sense because it means the thread will exit nicely instead of exiting as a result of an exception being thrown which has more overhead.

So this is the modified code:

public class SocketManager {

    private Socket socket = null;
    private PrintWriter out = null;
    private BufferedReader in = null;

    private String ip;
    private int port;

    private Object socketLock = new Object();

    public SocketManager(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    public void connect() throws UnableToConnectException, AlreadyConnectedException {
        synchronized(socketLock) {
            if (socket == null || socket.isClosed()) {
                throw (new AlreadyConnectedException());
            }
            try {
                socket = new Socket(ip, port);
                out = new PrintWriter(socket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            } catch (IOException e) {
                throw (new UnableToConnectException());
            }
            new Thread(new SocketThread()).start();
        }
    }

    public void disconnect() throws NotConnectedException {
        synchronized(socketLock) {
            if (isConnected()) {
                throw (new NotConnectedException());
            }
            try {
                socket.shutdownInput();
            } catch (IOException e) {}
            try {
                socket.shutdownOutput();
            } catch (IOException e) {}
            try {
                socket.close();
            } catch (IOException e) {}
        }
    }

    public boolean isConnected() {
        synchronized(socketLock) {
            return (socket != null && !socket.isClosed());
        }
    }

    private class SocketThread implements Runnable {

        @Override
        public void run() {
            String inputLine = null;
            try {
                while((inputLine = in.readLine()) != null) {
                    // do stuff (probably in another thread)
                }

                // it will get here if socket.shutdownInput() has been called (in disconnect)
                // or possibly when the server disconnects the clients


                // if it is here as a result of socket.shutdownInput() in disconnect()
                // then isConnected() will block until disconnect() finishes.
                // then isConnected() will return false and the thread will terminate.

                // if it ended up here because the server disconnected the client then
                // isConnected() won't block and return true meaning that disconnect()
                // will be called and the socket will be completely closed

                if (isConnected()) {
                    try {
                        disconnect();
                    } catch (NotConnectedException e) {}
                }
            } catch (IOException e) {
                // try and disconnect (if not already disconnected) and end thread
                if (isConnected()) {
                    try {
                        disconnect();
                    } catch (NotConnectedException e1) {}
                }
            }
        }

    }
}

In order to be sure that all resources associated with the socket are relased you have to call close() method when you finish work with that socket. Typical IO exception handling pattern is that you catch it and then perform best efforts to clean everything calling close() method.

So the only thing you have to do is to ensure that you call close() on every socket during it's lifetime.

You're on the right track. I wouldn't use "readline", only raw read, and "do stuff" should be limited to constructing a queue of received data. Likewise writing replies ought to be a separate thread that empties a queue of data to be sent.

Despite socket's guarantees of integrity, stuff will go wrong and you'll sometimes receive data that doesn't make sense. There's a crapload of stuff below "read" and "write" and no system is perfect or bug free. Add your own wrapper with checksums at the level of YOUR read and write so you can be sure you're receiving what was intended to be sent.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM