Java中的多线程服务器

时间:2020-01-09 10:36:12  来源:igfitidea点击:

本文介绍了用Java实现的简单多线程服务器。该代码基于单线程服务器上文本中描述的单线程服务器。主要区别在于服务器循环。与其在接受客户端连接的同一线程中处理传入请求,不如将连接切换到将处理该请求的工作线程。

注意:此代码使用"每个连接线程"设计,我们大多数人最初认为这比线程池服务器的效率低。但是,请阅读此博客文章,然后再三思:编写Java多线程服务器,旧有的是新的。

这是服务器循环在多线程版本中的外观:

while(! isStopped()){
        Socket clientSocket = null;
        try {
            clientSocket = this.serverSocket.accept();
        } catch (IOException e) {
            if(isStopped()) {
                System.out.println("Server Stopped.") ;
                return;
            }
            throw new RuntimeException(
                "Error accepting client connection", e);
        }
        
            new Thread(
            new WorkerRunnable(
            clientSocket, "Multithreaded Server")
            ).start();
        
    }

从单线程服务器到此处的循环中唯一的变化是粗体代码:

new Thread(
    new WorkerRunnable(
        clientSocket, "Multithreaded Server")
).start();

与其在接受客户端连接的同一线程中处理传入请求,不如将连接切换到处理该请求的工作线程。这样,侦听传入请求的线程会在serverSocket.accept()调用中花费尽可能多的时间。这样,由于侦听线程不在accept()调用之内,从而使拒绝访问服务器的客户端的风险降到最低。

这是WorkerRunnable类的代码,该代码传递给工作线程构造函数:

package servers;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;

/**

 */
public class WorkerRunnable implements Runnable{

    protected Socket clientSocket = null;
    protected String serverText   = null;

    public WorkerRunnable(Socket clientSocket, String serverText) {
        this.clientSocket = clientSocket;
        this.serverText   = serverText;
    }

    public void run() {
        try {
            InputStream input  = clientSocket.getInputStream();
            OutputStream output = clientSocket.getOutputStream();
            long time = System.currentTimeMillis();
            output.write(("HTTP/1.1 200 OK\n\nWorkerRunnable: " +
this.serverText + " - " +
time +
"").getBytes());
            output.close();
            input.close();
            System.out.println("Request processed: " + time);
        } catch (IOException e) {
            //report exception somewhere.
            e.printStackTrace();
        }
    }
}

多线程服务器的优势

与单线程服务器相比,多线程服务器的优点总结如下:

  • accept()调用之外花费的时间更少。
  • 长时间运行的客户端请求不会阻止整个服务器

如前所述,调用serverSocket.accept()的线程在此方法调用中花费的时间越多,服务器的响应速度就越快。只有当侦听线程在" accept()"调用内部时,客户端才能连接到服务器。否则,客户只会得到一个错误。

在单线程服务器中,长时间运行的请求可能会使服务器长时间不响应。对于多线程服务器,情况并非如此,除非长时间运行的请求占用了所有CPU时间和/或者网络带宽。

多线程服务器代码

这是MultiThreadedServer的完整代码:

package servers;

import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;

public class MultiThreadedServer implements Runnable{

    protected int          serverPort   = 8080;
    protected ServerSocket serverSocket = null;
    protected boolean      isStopped    = false;
    protected Thread       runningThread= null;

    public MultiThreadedServer(int port){
        this.serverPort = port;
    }

    public void run(){
        synchronized(this){
            this.runningThread = Thread.currentThread();
        }
        openServerSocket();
        while(! isStopped()){
            Socket clientSocket = null;
            try {
                clientSocket = this.serverSocket.accept();
            } catch (IOException e) {
                if(isStopped()) {
                    System.out.println("Server Stopped.") ;
                    return;
                }
                throw new RuntimeException(
                    "Error accepting client connection", e);
            }
            new Thread(
                new WorkerRunnable(
                    clientSocket, "Multithreaded Server")
            ).start();
        }
        System.out.println("Server Stopped.") ;
    }

    private synchronized boolean isStopped() {
        return this.isStopped;
    }

    public synchronized void stop(){
        this.isStopped = true;
        try {
            this.serverSocket.close();
        } catch (IOException e) {
            throw new RuntimeException("Error closing server", e);
        }
    }

    private void openServerSocket() {
        try {
            this.serverSocket = new ServerSocket(this.serverPort);
        } catch (IOException e) {
            throw new RuntimeException("Cannot open port 8080", e);
        }
    }

}

这是运行它的代码:

MultiThreadedServer server = new MultiThreadedServer(9000);
new Thread(server).start();

try {
    Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("Stopping Server");
server.stop();

服务器运行时,我们可以使用普通的Web浏览器访问它。使用地址http:// localhost:9000 /