Singlethreaded Server trong Java

Table of contents

Singlethreaded server hay server đơn luồng là một kiểu thiết kế server chạy trên một luồng duy nhất. Để phân biệt các kiểu server này, các bạn có thể quay về bài viết trước tại đây. Bài viết này sẽ giới thiệu về cách hiện thực một server đơn luồng trong Java.

Singlethreaded server

Server đơn luồng tuy chưa phải là một thiết kế tốt nhất cho một server trong Java, nhưng nó minh hoạ cho ta một cách rõ ràng về cách server nhận và xử lý request như thế nào. Các kiểu server tối ưu hơn như multithreaded hay threadpool cũng dựa trên tư tưởng của singlethreađed.

Vòng đời của singlethreaded server

Có thể hình dung vòng đời hay vòng lặp của server qua 3 bước sau:

  • Waiting: chờ request từ client
  • Processing: xử lý request từ client khi có request tới
  • Repeating: lặp lại quá trình trên

Khá đơn giản đúng không nào!!

Quá trình 3 bước trên gần như giống với các kiểu server khác trong Java. Sự khác biệt giữa singlethreadedmultithreaded là singledthreaded sử dụng chính luồng nhận request để xử lý, trong khi multithreaded sử dụng các thread khác (worker thread) để xử lý. Việc xử lý request đến từ client trên cùng một thread sẽ không hiệu quả, vì khi đó client chỉ có thể connect khi thread đó đang ở trạng thái waiting, nếu không thì client connnection sẽ không được thiết lập. Multithreaded sử dụng các worker thread để xử lý, giúp client connection có thể thiết lập được.

Hiện thực một singlethreaded server

Dưới đây là một hiện thực đơn giản cho singlethreaded server, xem mã nguồn ví dụ tại đây.

/*
 * License from https://github.com/wearenodev
 */
package wearenodev.java.examples.thread;

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

/**
 *
 * @author harisk
 */
public class SingleThreadedServer implements Runnable {

    private ServerSocket serverSocket;
    private final int serverPort;
    private boolean isStopped = false;

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

    private void processClientRequest(Socket clientSocket) throws Exception {
        InputStream input = clientSocket.getInputStream();
        OutputStream output = clientSocket.getOutputStream();
        long time = System.currentTimeMillis();

        byte[] responseDocument = ("<html><body>"
                + "SingleThreadedServer time: "
                + time
                + "</body></html>").getBytes("UTF-8");

        byte[] responseHeader = ("HTTP/1.1 200 OK\r\n"
                + "Content-Type: text/html; charset=UTF-8\r\n"
                + "Content-Length: " + responseDocument.length
                + "\r\n\r\n").getBytes("UTF-8");

        output.write(responseHeader);
        output.write(responseDocument);
        output.close();
        input.close();
        System.out.println("Request processed: " + time);
    }

    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 on stop server", e);
        }
    }

    private void openServerSocket() {
        try {
            this.serverSocket = new ServerSocket(this.serverPort);
            System.out.println("Server is running on port " + this.serverPort);

        } catch (IOException e) {
            throw new RuntimeException("Cannot open port " + this.serverPort, e);
        }
    }

    @Override
    public void run() {

        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 on accept client connection", e);

            }

            try {
                processClientRequest(clientSocket);

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        System.out.println("Server stopped");
    }

    public static void main(String[] args) {

        SingleThreadedServer server = new SingleThreadedServer(8080);

        /**
         * Run server in single thread
         */
        new Thread(server).start();

        /**
         * Waiting 20s before stopping server
         */
        try {
            Thread.sleep(20 * 1000);

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        /**
         * Stop server and exit processR
         */
        server.stop();
        System.exit(0);
    }

}

Trong ví dụ ở trên, chúng ta tạo một class SingleThreadedServer implement Runnable để có thể thực thi trong một Thread của Java (có thể hiểu Thread trong Java khi thực thi cần một Runnable class để chạy). Ta tạo một thread duy nhất trong process chính để thực thi server này như trong hàm main như sau:

/// ...

    public static void main(String[] args) {

        SingleThreadedServer server = new SingleThreadedServer(8080);

        /**
         * Run server in single thread
         */
        new Thread(server).start();

        /**
         * Waiting 20s before stopping server
         */
        try {
            Thread.sleep(20 * 1000);

        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }

        /**
         * Stop server and exit processR
         */
        server.stop();
        System.exit(0);
    }

/// ...

Trong hàm main này, ta start server trên port 8080 và cho thực thi trong 20s, sau đó thực hiện stop server và stop process chính.

Trong class SingleThreadedServer được hiện thực, ta ghi đè (override) method run() của interface Runnable như sau:

/// ...

    @Override
    public void run() {

        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 on accept client connection", e);

            }

            try {
                processClientRequest(clientSocket);

            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        System.out.println("Server stopped");
    }

/// ...

Ban đầu, ta cần mở port 8080 cho server. Thực hiện vòng lặp kiểm tra xem cờ isStopped của server. Trong từng bước lặp, ta chờ nhận socket từ client, khi có connection thì xử lý request đó.

Đây chỉ là một ví dụ đơn giản hiện thực một singlethreaded server. Trong thực tế chúng ta ít khi sử dụng kiểu thiết kế này vì những nhược điểm và hạn chế của nó. Việc sử dụng đơn luồng khiến server bị block khi đang xử lý một connection nào đó. Do đó multithreaded hay threadpool chính là những kiểu thiết kế tốt hơn, giải quyết vấn đề này của singlethreaded. Các bạn có thể tìm hiểu thêm về các kiểu thiết kế này trong chuỗi bài viết của mình.

Ngoài ra Java cũng có rất nhiều framework cho web server, các bạn cũng có thể tham khảo ở bài viết này.

Tìm hiểu thêm về: