0

我正在用java创建一个到服务器的套接字,在连接套接字后它会创建一个新线程,该线程可以访问套接字输入和输出流,然后这个线程在输入行进入时阻塞并处理它们。

我知道readlnBufferedReader 上的方法将在输入流结束时返回 null 。这并不一定意味着套接字已关闭,不是吗?这是什么意思?所以我想close在套接字上运行该方法以很好地关闭它。

我也知道该readln方法可以抛出一个 IOException ,并且close如果当前阻塞,则在套接字上调用该方法后抛出该异常。这还能扔到什么时候?抛出此套接字后套接字是否仍处于打开状态,或者它是否始终处于关闭状态并准备好进行垃圾收集等?

这是我目前拥有的代码,我真的不知道如何正确处理断开连接。目前我认为如果在disconnect套接字等待一行时调用该方法,这可能会导致死锁,因为disconnect将调用close套接字。然后这将引发IOExceptiononreadLine并且这将导致该 catch 块disconnect再次调用。

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) {}
                }
            }
        }

    }
}

我基本上想知道实现以下目标的最佳方法:

  • 编写连接到套接字并启动一个单独的线程来监听输入的连接方法。
  • 编写一个断开与套接字断开连接并终止正在侦听输入的线程的断开方法。
  • 处理与远程套接字的连接断开的情况。

我已经阅读了有关套接字的 java 教程,但在我看来,它并没有真正详细介绍这些内容。

谢谢!

4

3 回答 3

2

当我说它最终可能会陷入僵局时,我认为我错了。

会发生什么:

  1. in.readLine()阻塞时调用 disconnect()
  2. socket.close() 执行。
  3. in.readline() 抛出 IOException。

然后我在想 SocketThread 中的异常处理程序会调用断开连接,而断开连接正在等待该异常完成。没有关系,因为它们都是不同的线程,因此 disconnect() 中的代码将继续,同时异常被 SocketThread 捕获。然后 SocketThread 将调用 disconnect() 但必须等到 disconnect() 的第一个实例完成。然后 disconnect() 将再次执行,但会抛出 NotConnectedException,这将被 SocketThread 捕获,并且什么也不会发生。SocketThread 将退出,这就是想要的结果。

但是我查看了套接字类,它还包含以下方法:

  • 关机输入()
  • 关机输出()

shutdownInput()将结束 EOF 符号发送到输入流中,这意味着in.readline()返回 null 并且循环干净地退出。shutdownOutput()发送 TCP 终止序列,通知服务器它正在断开连接。

之前调用这两个socket.close()更有意义,因为这意味着线程将很好地退出,而不是由于抛出具有更多开销的异常而退出。

所以这是修改后的代码:

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) {}
                }
            }
        }

    }
}
于 2013-07-21T21:15:51.370 回答
0

为了确保与套接字关联的所有资源都被释放,您必须在完成该套接字的工作时调用 close() 方法。典型的 IO 异常处理模式是您捕获它,然后尽最大努力清理调用 close() 方法的所有内容。

因此,您唯一需要做的就是确保在每个套接字的生命周期内调用 close()。

于 2013-07-21T18:50:17.353 回答
0

你在正确的轨道上。我不会使用“readline”,只使用原始读取,而“do stuff”应该仅限于构造接收数据的队列。同样,写回复应该是一个单独的线程,它清空要发送的数据队列。

尽管套接字保证了完整性,但还是会出错,有时您会收到没有意义的数据。“读”和“写”下面有一大堆东西,没有一个系统是完美的或没有错误的。在您的读写级别添加您自己的带有校验和的包装器,这样您就可以确保您收到了要发送的内容。

于 2013-07-21T18:55:45.977 回答