java - 在java中处理套接字

标签 java multithreading sockets thread-safety synchronized

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

据我了解,当输入流结束时,BufferedReader 上的 readln 方法将返回 null。这并不一定意味着套接字已关闭,不是吗?这是什么意思?因此,我想在套接字上运行 close 方法来很好地关闭它。

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

这是我目前的代码,我真的不知道如何正确处理断开连接。目前,我认为如果在套接字等待线路时调用 disconnect 方法,这可能会导致死锁,因为 disconnect 将调用 close 在套接字上。然后,这将在 readLine 上抛出 IOException,这将导致该 catch block 再次调用 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 tutorial on sockets但在我看来,它并没有真正详细地涵盖这些内容。

谢谢!

最佳答案

当我说这可能会导致僵局时,我认为我错了。

会发生什么:

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

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

但是我调查了 socket class它还包含这些方法:

  • shutdownInput()
  • shutdownOutput()

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

    }
}

关于java - 在java中处理套接字,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17775624/

相关文章:

linux - 通过套接字发送 NULL 数据

Java 8 Hijrah 日期错误,当月中的某一天为 30 时

c++ - 线程问题(追自己的尾部)

c# - Task.Run() 和 await Task.Run() 有什么区别?

java - 如何使用静态变量和线程提高 Java 性能?

c - 基本 UDP 客户端/服务器无法工作?

java - Android Socket 将对象转换为ArrayList

java - 使用嵌入在 <Strong> 内的 CSS 选择器识别文本,而 <Strong> 又位于 <label> 标签内

java - 菜单项出现 NullPointerException

java - 授权 header 的 Spring Security OAuth2 CORS 问题