【问题标题】:How to stop server in Client Server multithreading如何在客户端服务器多线程中停止服务器
【发布时间】:2019-02-11 16:22:21
【问题描述】:

我正在用 java 实现一个多线程的客户端-服务器应用程序。我想在这个程序中实现 JDBC 并且我希望我的服务器在启动时从数据库中检索数据。我会将这些数据存储在我的collection 实例中,对数据执行操作,当服务器完成执行时,我需要将数据存储回数据库。问题是服务器处于无限循环等待客户端,我无法弄清楚如何让服务器停止。

这是我的服务器程序:

import java.io.*;
import java.text.*;
import java.util.*;
import java.net.*;

public class Server 
{
    public static void main(String[] args) throws IOException 
    {
        // server is listening on port 5056
        ServerSocket ss = new ServerSocket(5056);

        // running infinite loop for getting
        // client request
        while (true) 
        {
            Socket s = null;

            try {
                // socket object to receive incoming client requests
                s = ss.accept();

                System.out.println("A new client is connected : " + s);

                // obtaining input and out streams
                DataInputStream dis = new DataInputStream(s.getInputStream());
                DataOutputStream dos = new DataOutputStream(s.getOutputStream());

                System.out.println("Assigning new thread for this client");

                // create a new thread object
                Thread t = new ClientHandler(s, dis, dos);

                // Invoking the start() method
                t.start();
            }
            catch (Exception e) {
                s.close();
                e.printStackTrace();
            }
        }
    }
}

// ClientHandler class
class ClientHandler extends Thread 
{
    DateFormat fordate = new SimpleDateFormat("yyyy/MM/dd");
    DateFormat fortime = new SimpleDateFormat("hh:mm:ss");
    final DataInputStream dis;
    final DataOutputStream dos;
    final Socket s;


    // Constructor
    public ClientHandler(Socket s, DataInputStream dis, DataOutputStream dos) 
    {
        this.s = s;
        this.dis = dis;
        this.dos = dos;
    }

    @Override
    public void run() 
    {
        String received;
        String toreturn;
        while (true) {
            try {
                // Ask user what he wants
                dos.writeUTF("What do you want?[Date | Time]..\n"+
                        "Type Exit to terminate connection.");

                // receive the answer from client
                received = dis.readUTF();

                if(received.equals("Exit"))
                { 
                    System.out.println("Client " + this.s + " sends exit...");
                    System.out.println("Closing this connection.");
                    this.s.close();
                    System.out.println("Connection closed");
                    break;
                }

                // creating Date object
                Date date = new Date();

                // write on output stream based on the
                // answer from the client
                switch (received) {         
                    case "Date" :
                        toreturn = fordate.format(date);
                        dos.writeUTF(toreturn);
                        break;

                    case "Time" :
                        toreturn = fortime.format(date);
                        dos.writeUTF(toreturn);
                        break;

                    default:
                        dos.writeUTF("Invalid input");
                        break;
                }
            } 
            catch (IOException e) {
                e.printStackTrace();
            }
        }

        try
        {
            // closing resources
            this.dis.close();
            this.dos.close();            
        }
        catch(IOException e){
            e.printStackTrace();
        }
    }
}

这是我的客户程序:

import java.io.*;
import java.net.*;
import java.util.Scanner;

// Client class
public class Client 
{
    public static void main(String[] args) throws IOException 
    {
        try
        {
            Scanner scn = new Scanner(System.in);

            // getting localhost ip
            InetAddress ip = InetAddress.getByName("localhost");

            // establish the connection with server port 5056
            Socket s = new Socket(ip, 5056);

            // obtaining input and out streams
            DataInputStream dis = new DataInputStream(s.getInputStream());
            DataOutputStream dos = new DataOutputStream(s.getOutputStream());

            // the following loop performs the exchange of
            // information between client and client handler
            while (true) 
            {
                System.out.println(dis.readUTF());
                String tosend = scn.nextLine();
                dos.writeUTF(tosend);

                // If client sends exit,close this connection 
                // and then break from the while loop
                if(tosend.equals("Exit"))
                {
                    System.out.println("Closing this connection : " + s);
                    s.close();
                    System.out.println("Connection closed");
                    break;
                }

                // printing date or time as requested by client
                String received = dis.readUTF();
                System.out.println(received);
            }

            // closing resources
            scn.close();
            dis.close();
            dos.close();
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

【问题讨论】:

  • 谁有能力在您的设计中关闭服务器?是客户吗?它是一个特殊的客户端(即管理员客户端)吗?还是你在服务器端关闭?
  • @Impurity 它既不是客户也不是特殊客户。我在服务器端关闭了
  • 为什么用 JDBC 来标记它?除了您提到想要使用 JDBC 之外,我在您的问题(也不是您的代码)中看不到为什么这实际上与您的问题相关。
  • @MarkRotteveel 它是由 Impurity 在编辑中添加的。我没有包括
  • @Impurity 你为什么这样做?除非有明确的原因,否则您不应更改问题的范围或标记,而这里没有。

标签: java multithreading server client


【解决方案1】:

概述

好问题!重申一下上面 cmets 中所说的内容,您正在寻找服务器端关闭。有一些方法可以处理这种情况,我可以用一个简短的例子来解释它。

执行服务器

我将运行一个基于this 示例的修改示例。下面找到服务器实现。

class NetworkService implements Runnable {
    private final ServerSocket serverSocket;
    private final ExecutorService pool;
    private final AtomicBoolean shouldExit;

    public NetworkService(int port, int poolSize) throws IOException {
        serverSocket = new ServerSocket(port);
        pool = Executors.newFixedThreadPool(poolSize);
        shouldExit = new AtomicBoolean(false); // Thread-safe boolean
    }

    public void run() { // run the service
        try {

           // While we should not exit
           while(!shouldExit.get()) {
             try {
                 pool.execute(new ClientHandler(serverSocket.accept()));
             } catch (SocketException e) {
                 if(shouldExit.get()) break; // Poison pill has been delivered, lets stop
                 // Error handling
             }
           }
        } catch (IOException ex) {
           pool.shutdown();
        }

       // Clean up the thread pool
       shutdownAndAwaitTermination();
    }
}

class ClientHandler implements Runnable {
    private final Socket socket;
    ClientHandler (Socket socket) { this.socket = socket; }
    public void run() {
        ...
    }
    ...
 }

在这里,您将修改您当前的服务器代码以恐吓此结构。你目前有类似的妆容,但我们在这里添加了ExecutorService

一个 Executor,提供管理终止的方法和可以生成 Future 以跟踪一个或多个异步任务的进度的方法。

通过将您的 ClientHandler 分派到 ExecutorService,您正在使用 ThreadPool。虽然这有很多好处,但最重要的是您可以更好地控制多线程服务,ThreadPool 将管理线程利用率,应用程序效率将大大提高。

以下是尝试关闭和终止所有剩余线程的方法:

void shutdownAndAwaitTermination(ExecutorService pool) {
    pool.shutdown(); // Disable new tasks from being submitted
    try {
        // Wait a while for existing tasks to terminate
        if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
            pool.shutdownNow(); // Cancel currently executing tasks
            // Wait a while for tasks to respond to being cancelled
        if (!pool.awaitTermination(60, TimeUnit.SECONDS))
            System.err.println("Pool did not terminate");
        }
    } catch (InterruptedException ie) {
        // (Re-)Cancel if current thread also interrupted
        pool.shutdownNow();
        // Preserve interrupt status
        Thread.currentThread().interrupt();
    }
}

现在,问题仍然是我们如何关闭服务器?上面的代码显示了一个改进的结构,但仍然存在阻塞serverSocket.accept()的问题!

解决方案

在考虑这种情况时,我想到了两个想法; CLI 或 GUI。两者具有相同的语义,最终由您决定。出于解释的目的,我将参考 CLI 方法。

毒丸

如果您实现一个处理来自 CLI 的所有传入命令的 new Thread(),则此线程将充当毒丸。这个想法是向目标提供毒丸,以便可以唤醒/执行和死亡。该线程会将shouldExit 原子布尔值更改为true 并创建一个new Socket(serverSocket.getInetAddress(), serverSocket.getLocalPort()).close(); 以连接到ServerSocket 并立即关闭它。在上面的代码中,应用程序将不再阻塞serverSocket.accept()。相反,它将进入SocketExceptions 的try catch 并测试是否使用了毒丸;如果是则让我们清理,如果不是让错误处理。

超时

您还可以在ServerSocket 上设置超时,这样每次在该时间间隔内无法与myServer.setSoTimeout(2000); 建立连接时,它都会抛出异常。这将抛出一个InterruptedIOException 并且可以类似于通过 CLI 命令更改标志的毒丸进行处理,并检查它是否应该在 catch 块中退出。如果它应该退出,让我们清理,如果不是让错误处理。

【讨论】:

  • The operator ! is undefined for the argument type(s) AtomicBoolean while 循环中的 shouldExit 出现此错误
  • 在我的代码中,我有一个 main 方法,用于创建 ClientHandler 的实例。您示例中的 NetworkServices 类实现了 Runnable。但是要执行服务器,我需要一个 main 方法。 main方法怎么写?
  • @EdmundFitzgerald 我修复了您遇到的错误
  • @EdmundFitzgerald 我将您的服务器类与您的 src 中的 main 保持一致。然后,我将从那里开始运行 NetworkService 并设置您的 GUI 或 CLI。
【解决方案2】:

您可以将pattern flag 与 volatile 布尔变量一起使用,您应该将其置于“while”中 - 当处理完成时,将其设置为 false,服务器将停止。

另一种方式 - 使用 thread pools 并等待它们在服务器的主线程中完成。

【讨论】: