贝利信息

使用 Java Socket 构建 HTTP 服务器并处理并发请求

日期:2025-09-23 00:00 / 作者:聖光之護

本文旨在指导开发者使用 Java Socket 构建一个简单的 HTTP 服务器,并解决在高并发场景下可能出现的问题。文章将深入探讨 HTTP 协议中的 Keep-Alive 机制,并提供相应的代码示例,帮助读者理解如何正确处理 HTTP 请求,从而构建一个稳定可靠的 HTTP 服务器。

使用 Java Socket 创建 HTTP 服务器

使用 Java Socket 创建 HTTP 服务器涉及监听指定端口、接收客户端连接、处理请求和发送响应。以下是一个基本的示例,展示了如何使用 ServerSocket 和 Socket 类来创建一个简单的 HTTP 服务器:

import java.net.ServerSocket;
import java.net.Socket;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class SimpleHttpServer {

    public static void main(String[] args) throws IOException {
        int port = 8080;
        ServerSocket serverSocket = new ServerSocket(port);
        ExecutorService executor = Executors.newFixedThreadPool(4); // 使用线程池处理并发请求

        System.out.println("Server listening on port " + port);

        while (true) {
            try {
                Socket clientSocket = serverSocket.accept(); // 接受客户端连接
                System.out.println("Client connected: " + clientSocket.getInetAddress().getHostAddress());

                executor.submit(() -> handleClient(clientSocket)); // 提交任务给线程池
            } catch (IOException e) {
                System.err.println("Error accepting client connection: " + e.getMessage());
            }
        }
    }

    private static void handleClient(Socket clientSocket) {
        try (clientSocket; // 使用 try-with-resources 确保 Socket 关闭
             OutputStream outputStream = clientSocket.getOutputStream()) {

            String response = "HTTP/1.1 200 OK\r\n" +
                    "Content-Type: text/plain\r\n" +
                    "Content-Length: 12\r\n" +
                    "Connection: close\r\n" + // 显式关闭连接
                    "\r\n" +
                    "Hello World!";

            outputStream.write(response.getBytes(StandardCharsets.UTF_8));
            outputStream.flush();

            System.out.println("Response sent to client.");

        } catch (IOException e) {
            System.err.println("Error handling client: " + e.getMessage());
        }
    }
}

这段代码创建了一个监听 8080 端口的服务器,并使用一个固定大小的线程池来处理并发请求。 handleClient 方法负责生成一个简单的 HTTP 响应并将其发送回客户端。 Connection: close header 被添加到响应中,以显式地告诉客户端在发送响应后关闭连接。

解决并发请求失败的问题:Keep-Alive 机制

在高并发场景下,如果服务器没有正确处理 HTTP Keep-Alive 机制,可能会导致部分请求失败。HTTP Keep-Alive 允许客户端和服务器在单个 TCP 连接上发送和接收多个 HTTP 请求/响应,从而减少了建立和关闭连接的开销。

问题分析:

在初始代码中,服务器发送的响应头 HTTP/1.1 200 OK 默认启用了 Keep-Alive。 这意味着客户端在收到响应后,会尝试在同一个连接上发送后续请求。 然而,服务器的代码并没有处理后续请求的逻辑,导致客户端在第二次尝试发送请求时失败。

解决方案:

  1. 禁用 Keep-Alive: 最简单的解决方案是禁用 Keep-Alive,通过将 HTTP 版本更改为 HTTP/1.0 或在响应头中添加 Connection: close 来强制关闭连接。 虽然简单,但这会牺牲性能。

    String response = "HTTP/1.0 200 OK\r\n" + // 或者 "HTTP/1.1 200 OK\r\nConnection: close\r\n"
            "Content-Type: text/plain\r\n" +
            "Content-Length: 12\r\n" +
            "\r\n" +
            "Hello World!";
  2. 支持 Keep-Alive: 为了充分利用 HTTP Keep-Alive 的优势,服务器需要能够解析请求,并在同一个连接上处理多个请求。 这需要更复杂的逻辑来读取请求头,确定请求的结束位置,并根据请求内容生成相应的响应。

    以下是一个支持 Keep-Alive 的示例代码:

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.nio.charset.StandardCharsets;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class KeepAliveHttpServer {
    
        public static void main(String[] args) throws IOException {
            int port = 80

    80; ServerSocket serverSocket = new ServerSocket(port); ExecutorService executor = Executors.newFixedThreadPool(4); System.out.println("Server listening on port " + port); while (true) { try { Socket clientSocket = serverSocket.accept(); System.out.println("Client connected: " + clientSocket.getInetAddress().getHostAddress()); executor.submit(() -> handleClient(clientSocket)); } catch (IOException e) { System.err.println("Error accepting client connection: " + e.getMessage()); } } } private static void handleClient(Socket clientSocket) { try ( Socket socket = clientSocket; BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); OutputStream outputStream = socket.getOutputStream() ) { while (true) { // 读取请求头 StringBuilder requestHeader = new StringBuilder(); String line; while ((line = reader.readLine()) != null && !line.isEmpty()) { requestHeader.append(line).append("\r\n"); } if (requestHeader.length() == 0) { // 连接关闭 System.out.println("Client disconnected."); break; } System.out.println("Request Header:\n" + requestHeader); // 简单响应 String response = "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: 12\r\n" + "\r\n" + "Hello World!"; outputStream.write(response.getBytes(StandardCharsets.UTF_8)); outputStream.flush(); } } catch (IOException e) { System.err.println("Error handling client: " + e.getMessage()); } } }

    这个改进后的版本会持续读取和处理来自同一连接的请求,直到客户端关闭连接。它通过循环读取请求头,然后发送响应来实现 Keep-Alive。 当客户端关闭连接时,reader.readLine() 会返回 null,循环结束,连接关闭。

注意事项:

总结

使用 Java Socket 构建 HTTP 服务器需要理解 HTTP 协议的细节,特别是 Keep-Alive 机制。 通过禁用 Keep-Alive 或正确处理 Keep-Alive 连接,可以解决并发请求失败的问题。 在实际应用中,还需要考虑请求解析、连接超时、错误处理等因素,以构建一个稳定可靠的 HTTP 服务器。 使用线程池可以提高服务器的并发处理能力。