实现一个简单的多线程Web服务器

在这个案例中,我们将探讨如何使用Java语言实现一个简单的多线程Web服务器。该服务器使用了Java的Socket和多线程技术,可以监听指定端口,接受客户端的HTTP请求,并处理这些请求。我们将分为两个主要部分:WebServer 类和 HttpRequest 类。

WebServer 类

import java.io.*;
import java.net.*;

public class WebServer {

    public static void main(String args[]) {
        int port = 6789;
        ServerSocket server = null;
        Socket socket = null;

        try {
            // 创建一个ServerSocket来监听指定端口
            server = new ServerSocket(port);
            System.out.println("正在监听端口:" + server.getLocalPort());

            while (true) {
                // 当有新的连接请求时,创建一个新的socket,并为其启动一个新线程处理请求
                socket = server.accept();
                new HttpRequest(socket).run();
            }

        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

在这里,我们创建了一个 ServerSocket 实例,用于监听指定的端口(在这里是6789)。在一个无限循环中,我们等待新的连接请求,并为每个请求创建一个新的 Socket,然后启动一个新的线程来处理这个请求。

HttpRequest 类

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

public class HttpRequest implements Runnable {
    // 回车换行,根据HTTP规范,回车换行作为Response消息头部行的结束
    static String CRLF = "\r\n";

    Socket socket;

    public HttpRequest(Socket socket) throws Exception {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            processRequest();
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void processRequest() throws Exception {
        InputStream is = socket.getInputStream();
        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        // 读取请求行
        String requestLine = br.readLine();
        System.out.println("------------请求报文-------------\n\n" + requestLine);

        String headerLine = null;

        // 读取所有头部行
        while ((headerLine = br.readLine()).length() != 0) {
            System.out.println(headerLine);
        }

        StringTokenizer tokens = new StringTokenizer(requestLine);

        // 解析请求行,获取请求方法和文件名
        String method = tokens.nextToken();
        String fileName = tokens.nextToken();
        fileName = "." + fileName;

        FileInputStream fis = null;
        boolean fileExists = true;

        try {
            fis = new FileInputStream(fileName);
        } catch (FileNotFoundException e) {
            fileExists = false;
        }

        // 构建响应消息的状态行、头部行和实体行
        String statusLine;
        String contentTypeLine;
        String entityBody = null;

        if (fileExists) {
            statusLine = "HTTP/1.1 200 OK";
            contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
        } else {
            statusLine = "HTTP/1.1 404 Not Found";
            contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
            entityBody = "<HTML><HEAD><TITLE>Not Found</TITLE></HEAD><BODY>404 Not Found</BODY></HTML>";
        }

        // 向客户端发送响应消息的状态行和头部行
        os.writeBytes(statusLine);
        os.writeBytes(contentTypeLine);
        os.writeBytes(CRLF);

        System.out.println("------------部分响应报文-------------\n\n" + statusLine + "\n" + contentTypeLine);

        // 如果文件存在,将文件内容通过socket输出流发送到客户端
        if (fileExists) {
            sendBytes(fis, os);
            fis.close();
        }
        // 如果文件不存在,向客户端发送404错误页面
        else {
            os.writeBytes(entityBody);
        }

        // 关闭流和socket
        os.close();
        br.close();
        socket.close();
    }

    // 获取MIME类型
    public static String contentType(String fileName) {
        // 省略了一些代码...
    }

    // 发送字节
    public static void sendBytes(FileInputStream fis, OutputStream os) throws Exception {
        byte[] buffer = new byte[1024];
        int bytes;

        while ((bytes = fis.read(buffer)) != -1) {
            os.write(buffer, 0, bytes);
        }
    }
}

在 HttpRequest 类中,我们处理了HTTP请求的各个阶段。我们读取了请求行和头部行,解析请求行以获取请求方法和文件名。然后,我们构建了响应消息的状态行、头部行和实体行,将它们通过 OutputStream 发送给客户端。

完整代码如下:

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

public class WebServer {

    public static void main(String args[]) {
        int port = 6789;
        ServerSocket server = null;
        Socket socket = null;

        try {
            // 创建一个ServerSocket来监听指定端口
            server = new ServerSocket(port);
            System.out.println("正在监听端口:" + server.getLocalPort());

            while (true) {
                // 当有新的连接请求时,创建一个新的socket,并为其启动一个新线程处理请求
                socket = server.accept();
                new HttpRequest(socket).run();
            }

        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

class HttpRequest implements Runnable {
    Socket socket;
    static String CRLF = "\r\n";

    public HttpRequest(Socket socket) throws Exception {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            processRequest();
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    public void processRequest() throws Exception {
        InputStream is = socket.getInputStream();
        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        // 读取请求行
        String requestLine = br.readLine();
        System.out.println("------------请求报文-------------\n\n" + requestLine);

        String headerLine = null;

        // 读取所有头部行
        while ((headerLine = br.readLine()).length() != 0) {
            System.out.println(headerLine);
        }

        StringTokenizer tokens = new StringTokenizer(requestLine);

        // 解析请求行,获取请求方法和文件名
        String method = tokens.nextToken();
        String fileName = tokens.nextToken();
        fileName = "." + fileName;

        FileInputStream fis = null;
        boolean fileExists = true;

        try {
            fis = new FileInputStream(fileName);
        } catch (FileNotFoundException e) {
            fileExists = false;
        }

        String statusLine = null;
        String contentTypeLine = null;
        String entityBody = null;

        // 构建响应消息的状态行、头部行和实体行
        if (fileExists) {
            statusLine = "HTTP/1.1 200 OK";
            contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
        } else {
            statusLine = "HTTP/1.1 404 Not Found";
            contentTypeLine = "Content-type: " + contentType(fileName) + CRLF;
            entityBody = "<HTML><HEAD><TITLE>Not Found</TITLE></HEAD><BODY>404 Not Found</BODY></HTML>";
        }

        // 向客户端发送响应消息的状态行和头部行
        os.writeBytes(statusLine);
        os.writeBytes(contentTypeLine);
        os.writeBytes(CRLF);

        System.out.println("------------部分响应报文-------------\n\n" + statusLine + "\n" + contentTypeLine);

        // 如果文件存在,将文件内容通过socket输出流发送到客户端
        if (fileExists) {
            sendBytes(fis, os);
            fis.close();
        }
        // 如果文件不存在,向客户端发送404错误页面
        else {
            os.writeBytes(entityBody);
        }

        // 关闭流和socket
        os.close();
        br.close();
        socket.close();
    }

    // 根据文件名获取对应的MIME类型
    public static String contentType(String fileName) {
        if (fileName.endsWith(".htm") || fileName.endsWith(".html")) {
            return "text/html";
        } else if (fileName.endsWith(".txt")) {
            return "text/plain";
        } else if (fileName.endsWith(".jpg")) {
            return "image/jpg";
        } else if (fileName.endsWith(".php")) {
            return "text/x-php";
        } else if (fileName.endsWith(".js")) {
            return "text/javascript";
        } else if (fileName.endsWith(".rff") || fileName.endsWith(".rtfd")) {
            return "text/plain";
        } else if (fileName.endsWith(".py")) {
            return "text/x-python";
        } else if (fileName.endsWith(".rb")) {
            return "text/x-ruby";
        } else if (fileName.endsWith(".sh")) {
            return "text/x-shellscript";
        } else if (fileName.endsWith(".pl")) {
            return "text/x-perl";
        } else if (fileName.endsWith(".sql")) {
            return "text/x-sql";
        }
        return "application/octet-stream";
    }

    // 将文件内容通过输出流发送到客户端
    public static void sendBytes(FileInputStream fis, OutputStream os) throws Exception {
        byte[] buffer = new byte[1024];
        int bytes = 0;
        while ((bytes = fis.read(buffer)) != -1) {
            os.write(buffer, 0, bytes);
        }
    }
}