【问题标题】:Handle incoming sockets in another thread在另一个线程中处理传入的套接字
【发布时间】:2013-05-07 20:50:25
【问题描述】:

我正在尝试做一些可能很愚蠢的事情,但我认为这是个好主意,所以请耐心等待。我尝试实现它,但遇到了一个尴尬的问题,即线程之间的套接字关闭 - 所以我想对这个案例有一些新的看法。

场景

我想通过套接字将对象从Client 写入Server。可能有多个Client同时与Server通信。

Message 对象通过其处理机制由Server 处理。建议不使用Server 的主线程寻找新的传入连接,而是设置Listener 线程。一旦发现传入的连接,它就会提醒Server,将套接字存储在队列中而不接收数据,因此它可以快速返回侦听。

Server 在自己的时间里拿起等待的套接字,生成一个新线程,读取 Message,然后关闭套接字。

代码

这是我关于如何实施的第一个想法。它有一个根本性的缺陷,我将在下面解释。

忽略公共字段的使用 - 我只是想让你们的代码简短

public class Server {
    public boolean messageWaiting = false;

    public static void main(String[] args) {
        new Server().run();
    }

    public void run() {
        Listener l = new Listener();
        l.listen(this);
        try {
            while (true) {
                System.out.println("I'm happily doing my business!");
                Thread.sleep(1000);
                if (messageWaiting) {
                    acceptMessages(l);
                }
            }
        } catch (InterruptedException die) {}
    }

    private void acceptMessages(Listener l) {
        while (!l.waiting.isEmpty()) {
            try (
                Socket client = l.waiting.poll();
                ObjectInputStream ois = new ObjectInputStream(client.getInputStream())
            ) {
                // Handle messages in new threads! (or a thread pool)
                new Thread() {
                    public void run() {
                        try {
                            System.out.println(ois.readObject());
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }.start();
            } catch (Exception ex) {
                // Oh no! The socket has already been closed!
                ex.printStackTrace();
            }
        }
    }
}

public class Listener {
    public ConcurrentLinkedQueue<Socket> waiting = new ConcurrentLinkedQueue<>();

    public void listen(final Server callback) {
        new Thread() {
            public void run() {
                try (ServerSocket rxSock = new ServerSocket(7500)) {
                    while (!isInterrupted()) {
                        try (Socket client = rxSock.accept()) {
                            // Once a new socket arrives, add it to the waiting queue
                            waiting.add(client);
                            // Alert the server
                            callback.messageWaiting = true;
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }
                    }
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }.start();
    }
}

public class Client {
    public static void main(String[] args) {
        try (
            Socket txSock = new Socket(InetAddress.getLoopbackAddress(), 7500);
            ObjectOutputStream oos = new ObjectOutputStream(txSock.getOutputStream())
        ) {

            oos.writeObject("This is a Message, trust me.");
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

这是怎么回事?

这个:

I'm happily doing my business!
I'm happily doing my business!
java.net.SocketException: Socket is closed
    at java.net.Socket.getInputStream(Unknown Source)
    at Server.acceptMessages(Server.java:30)
    at Server.run(Server.java:20)
    at Server.main(Server.java:9)

这是因为我使用的 Java 7 try 块一旦完成就会关闭套接字。那我为什么不手动做呢?试试自己 - 你最终会得到一个警告,说你只会在 null 对象上调用 close()!

那么,我如何避免在Server 线程启动之前关闭传入套接字的整个问题?或者这是一个坏主意,我应该做点别的?

【问题讨论】:

  • 为什么不在连接打开后立即将套接字传递给线程?
  • 我正在这样做,只是我希望服务器处理消息接收而不是侦听器。我为什么要这样做?因为我的规范需要我为客户端和服务器机器创建静态方法、sendMessage() 和 receiveMessage()。所以服务器必须知道何时调用 receiveMessage() 来获取它的数据。
  • 你在没有使用线程的情况下测试过这段代码吗?
  • 是的(除了监听器由于明显的原因在不同的线程上运行)
  • 您的规范当然不应该下降到那种详细程度,但即使它这样做了,也没有什么能阻止您使用另一个线程。您在对象流方面犯了一个大错误。在套接字的生命周期内,您必须在两端使用相同的对象输入和输出流。

标签: java multithreading sockets java-7


【解决方案1】:

你在Listener的声明

try (Socket client = rxSock.accept()) { ...

client 套接字的 try-with-resources。只要将它添加到队列并退出 try 块,套接字就会自动关闭。

【讨论】:

  • "这是因为我使用的 Java 7 try 块一旦完成就关闭套接字。那么为什么我不手动执行此操作?试试自己 - 你最终会收到一条警告说你只会在一个空对象上调用 close() !”事实是,如果我让它保持打开状态,它会抱怨泄漏,如果我在尝试结束时关闭它,其他线程将无法使用它。
  • 是的,但是套接字会立即关闭,并且在套接字关闭之前线程不会执行。在这种内部情况下,您不需要使用 try-with-resources。
  • 这就是我要表达的意思——我是否使用 try-with-resources 并不重要——就像我不使用一样——只需关闭 catch 子句中的套接字——我结束了出现资源泄漏警告或空对象警告。
  • 其实,我在不关闭套接字的情况下测试它确实看起来不错。只要我的接收代码在处理完所有客户端套接字后关闭它们,就应该没问题!对吗?
  • 正确。客户端套接字应该由处理数据的线程关闭。
猜你喜欢
  • 2016-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-06
  • 2012-07-02
  • 1970-01-01
相关资源
最近更新 更多