首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java 簡易 HTTP 服務器實現:阻塞 IO 與 NIO 兩種架構完整代碼

     編輯:Java 綜合教學

前言

從零手寫 HTTP 服務器是理解網路程式設計、Servlet 容器原理的最佳途徑。本文提供基於傳統阻塞 IO(BIO)與非阻塞 IO(NIO)兩種架構的完整實現代碼,對比兩種模型差異。

核心類設計

三大核心類職責分離:

  • Request:解析 HTTP 請求行、頭部、提取 URI
  • Response:構建 HTTP 響應、靜態資源讀取、404 處理
  • Server:監聽端口、分發請求、生命週期管理

架構一:傳統阻塞 IO (BIO)

HttpServer 主類

public class HttpServer {
    public static final String WEB_ROOT = System.getProperty("user.dir") + "\\src\\test\\resources\\webroot";
    public static final int SERVER_PORT = 8888;
    public static final String SERVER_IP = "127.0.0.1";

    public static void main(String[] args) {
        new HttpServer().await();
    }

    public void await() {
        try (ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 1, InetAddress.getByName(SERVER_IP))) {
            while (true) {
                try (Socket socket = serverSocket.accept();
                     InputStream in = socket.getInputStream();
                     OutputStream out = socket.getOutputStream()) {
                    
                    Request request = new Request(in);
                    request.read();  // 解析請求
                    
                    Response response = new Response(out);
                    response.setRequest(request);
                    response.response(); // 處理響應
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Request 解析

public class Request {
    private InputStream in;
    private String uri;

    public Request(InputStream in) { this.in = in; }

    public void read() {
        StringBuffer sb = new StringBuffer();
        byte[] buffer = new byte[2048];
        try {
            int count = in.read(buffer);
            for (int i = 0; i < count; i++) {
                sb.append((char) buffer[i]);
            }
            takeUri(sb);
        } catch (IOException e) { e.printStackTrace(); }
    }

    private void takeUri(StringBuffer sb) {
        int idx1 = sb.indexOf(" ");
        if (idx1 != -1) {
            int idx2 = sb.indexOf(" ", idx1 + 1);
            if (idx2 > idx1) {
                uri = sb.substring(idx1 + 1, idx2);
                // 簡單路由:僅允許 index.html 和 test.jpg
                if (!(uri.endsWith("/index.html") || uri.endsWith("/test.jpg"))) {
                    uri = "/404.html";
                }
            }
        }
    }
    // getter...
}

Response 處理

public class Response {
    private static final int BUFFER_SIZE = 1024;
    private OutputStream out;
    private Request request;

    public Response(OutputStream out) { this.out = out; }
    public void setRequest(Request request) { this.request = request; }

    public void response() {
        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            if (file.exists()) {
                try (FileInputStream fis = new FileInputStream(file)) {
                    byte[] buffer = new byte[BUFFER_SIZE];
                    int count;
                    while ((count = fis.read(buffer)) > 0) {
                        out.write(buffer, 0, count);
                    }
                }
            } else {
                // 404 響應
                String resp = "HTTP/1.1 404 File Not Found\r\n" +
                              "Content-Type: text/html\r\n" +
                              "Content-Length: 24\r\n\r\n" +
                              "

File Not Found!

"; out.write(resp.getBytes()); } } catch (IOException e) { e.printStackTrace(); } } }

架構二:非阻塞 IO (NIO) - Reactor 模式

HttpNIOServer 主類

public class HttpNIOServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    private Request request;
    private static final int SERVER_PORT = 8888;
    private boolean shutdown = false;

    public static void main(String[] args) {
        new HttpNIOServer().start();
    }

    public HttpNIOServer() {
        try {
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); // 非阻塞模式
            serverSocketChannel.socket().bind(new InetSocketAddress(SERVER_PORT));
            
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            System.out.println("NIO Server init on port " + SERVER_PORT);
        } catch (IOException e) { e.printStackTrace(); }
    }

    public void start() {
        try {
            while (!shutdown) {
                selector.select(); // 阻塞等待就緒事件
                Set keys = selector.selectedKeys();
                Iterator it = keys.iterator();
                
                while (it.hasNext()) {
                    SelectionKey key = it.next();
                    it.remove();
                    handleRequest(key);
                }
            }
        } catch (IOException e) { e.printStackTrace(); }
    }

    private void handleRequest(SelectionKey key) {
        try {
            if (key.isAcceptable()) {
                // 新連接接受
                ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                SocketChannel sc = ssc.accept();
                sc.configureBlocking(false);
                sc.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // 讀取請求
                SocketChannel sc = (SocketChannel) key.channel();
                ByteBuffer buffer = ByteBuffer.allocate(2048);
                StringBuffer sb = new StringBuffer();
                
                while (sc.read(buffer) > 0) {
                    buffer.flip();
                    int limit = buffer.limit();
                    for (int i = 0; i < limit; i++) {
                        sb.append((char) buffer.get(i));
                    }
                    buffer.clear();
                }
                
                if (sb.length() > 0) {
                    request = new Request();
                    request.takeUri(sb);
                    sc.register(selector, SelectionKey.OP_WRITE);
                }
            } else if (key.isWritable()) {
                // 寫入響應
                SocketChannel sc = (SocketChannel) key.channel();
                Response response = new Response(sc);
                response.setRequest(request);
                response.responseNIO();
                sc.register(selector, SelectionKey.OP_READ); // 繼續監聽讀取
            }
        } catch (IOException e) { e.printStackTrace(); }
    }
}

NIO 版 Response

public class Response {
    // ... 構造方法接受 SocketChannel
    public Response(SocketChannel osc) { this.osc = osc; }

    public void responseNIO() {
        try {
            File file = new File(HttpServer.WEB_ROOT, request.getUri());
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            
            if (file != null && file.exists()) {
                try (FileInputStream fis = new FileInputStream(file)) {
                    while (fis.read(buffer.array()) > 0) {
                        osc.write(buffer);
                        buffer.clear();
                    }
                }
            } else {
                String resp = "HTTP/1.1 404 File Not Found\r\n" +
                              "Content-Type: text/html\r\n" +
                              "Content-Length: 24\r\n\r\n" +
                              "

File Not Found!

"; osc.write(ByteBuffer.wrap(resp.getBytes())); } } catch (IOException e) { e.printStackTrace(); } } }

BIO vs NIO 核心差異對比

維度BIO (HttpServer)NIO (HttpNIOServer)
程式模型同步阻塞,一連接一執行緒同步非阻塞,單執行緒多路複用
擴展性受限於執行緒數,C10K 問題單執行緒支援萬級連接
代碼複雜度簡單直觀狀態機管理、事件驅動、較複雜
記憶體佔用每連接一棧幀共用 Selector、緩衝區可控
適用場景連接數少、邏輯簡單高併發、長連接、即時通訊

測試驗證

  1. 啟動服務器:java HttpServer 或 java HttpNIOServer
  2. 瀏覽器訪問:https://localhost:8888/index.html
  3. 預期:正確返回 webroot 目錄下靜態資源
  4. 訪問不存在資源:返回 404 頁面

專案結構建議

http-server/
├── src/
│   └── main/java/org/example/httpserver/
│       ├── HttpServer.java
│       ├── HttpNIOServer.java
│       ├── Request.java
│       └── Response.java
└── src/test/resources/webroot/
    ├── index.html
    ├── test.jpg
    └── 404.html

原始碼倉庫

https://github.com/windwant/http-test

總結

手寫 HTTP 服務器能深度理解:

  • HTTP 協議解析與響應構建細節
  • BIO/NIO/AIO 三種 IO 模型本質差異
  • Reactor 模式事件驅動架構
  • Tomcat/Jetty 等容器底層原理

建議學習路徑:先跑通 BIO 版本理解流程 → 改造 NIO 版本體會非阻塞難點 → 研究 Netty 框架學習生產級最佳實踐。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved