Java:停止线程化TCP服务器的好方法?

5
我有以下TCP客户端-服务器通信结构:
  • 在服务器启动时,服务器启动接受器线程,该线程接受客户端连接并将ServerSocket传递给它。
  • 当客户端连接到达时,接受器线程在ServerSocket上调用accept()并将客户端套接字提交给工作线程(通过执行器/线程池),然后提供客户端套接字。
  • 工作线程循环从客户端套接字流中读取数据,处理并发送回复。
问题是如何优雅地停止整个系统?我可以通过关闭ServerSocket来停止接受器线程。这将导致accept()阻塞调用抛出SocketException。但是如何停止工作线程?它们从流中读取数据,而此调用是阻塞的。根据this,流不会抛出InterruptedException,因此无法使用interrupt()中断工作线程。
看起来我需要从另一个线程关闭工作套接字,对吗?为此,套接字应该成为公共字段,或者应该提供一个方法在工作线程中关闭它。这样做好吗?还是我的整个设计有缺陷?
3个回答

3

您的模型运行正常。中断非可中断结构(如IO)的最佳方法是关闭套接字。当然,如果IO函数不对中断做出反应,则在进入阻塞状态之前可以处理它,但您实际上没有太多好的选择。


2

在关闭服务器时,您不能只是简单地停止它。由于需要确保一致性,清理过程可能会花费一些时间。

想象一下一个数据库服务器,如果您在其执行事务时直接关闭它,可能会导致数据不一致。这就是为什么通常需要一段时间来关闭服务器的原因。

  • 您必须首先停止服务器中接受新连接的功能。
  • 然后,您可以等待当前的工作线程完成它们的工作,然后关闭服务器并正式关闭。
  • 或者您可以强制使工作线程关闭其与客户端的连接(可能使用某种标志),这可能意味着需要进行一些清理以保持数据一致性,例如撤销事务或对文件或内存中所做的任何更改。

在服务器端关闭与客户端的连接应该会导致客户端方面的EOF,根据我的理解。

[编辑-1]

我研究了一下这个问题,因为我已经有一段时间没有使用套接字,并且我觉得这个问题很有趣。正如其他人指出的那样,我认为唯一的选择是关闭socket,而根据Javadocs,这将自动关闭输入和输出流。

如果存在线程处于等待状态或睡眠状态而不是IO阻塞的机会,我认为仍然建议对给定套接字的相应工作线程发出Thread.interrupt(),因为无法确定每个线程的阻塞状态。

public static class IOServerWorker implements Runnable{

        private Socket socket;

        public IOServerWorker(Socket socket){
            this.socket = socket;
        }

        @Override
        public void run() {
            String line = null;
            try{
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while( (line = reader.readLine())!=null){
                    System.out.println(line);
                }
                reader.close();
            }catch(IOException e){
                //TODO: do cleanup here
                //TODO: log | wrap | rethrow exception
            }
        }
    }

我非常理解这个问题。"然后你可以等待当前的工作线程完成它们的工作" - 如果你再读一遍我的问题,你会发现这是一个问题。工作线程可能会被从套接字流中读取而阻塞。问题是如何优雅地中断它们。标志在这里不起作用,因为工作线程在阻塞I/O状态时无法检查它。 - Vladislav Rastrusny
“你不能简单地停止服务器。” 实际上,如果您的服务器设计正确(包括ACID事务等),仅停止它是可以工作的。导致数据库提交的更改被认为已经发生,而未发生的更改从未发生过。将数据库与业务逻辑和Web前端分离并使其良好运行的原因之一就是这一点。 - Donal Fellows

2

我建议使用一个布尔标记,工作线程定期检查。将该标记称为shouldStop,如果设置为true,则工作线程会清理资源并退出。按照这种方法可以实现一些清理代码,以避免资源未被释放等问题。


最佳实践是使用Thread.interrupted()。这是标准解决方案,在线程池中可以正确工作。 - Andrey Vityuk
@Andrey Vityuk,Thread.interrupted实际上不是很好的做法,因为它会重置中断标志。你可能想要使用Thread.interrupt或Thread.isInterrupted。话虽如此,正如我在答案中所说,许多IO结构并没有考虑线程中断,因此(尽管我部分同意您的评论),在这种情况下并不适用。 - John Vint
@Andrey 当一个线程被阻塞在读取套接字时,中断它将不会起作用。关闭套接字并捕获SocketException异常以进行一些清理是关闭线程的唯一方法。 - Gabriel Llamas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接