杰瑞科技汇

Java HTTP客户端与服务端如何高效通信?

我们也会介绍现代、更推荐的 Java 11+ 内置 HTTP 客户端 java.net.http.HttpClient,以及业界非常流行的 Netty 框架。

Java HTTP客户端与服务端如何高效通信?-图1
(图片来源网络,侵删)

HTTP 服务端

我们将创建一个简单的服务端,它监听指定端口,接收客户端的请求,并返回一个 "Hello, World!" 响应。

核心类

  • java.net.ServerSocket: 用于在服务器上监听客户端的连接请求。
  • java.net.Socket: 代表一个客户端与服务端之间的连接。
  • java.io.InputStream: 从连接中读取客户端发送的数据(请求)。
  • java.io.OutputStream: 向连接中写入数据(响应)。

代码示例

// SimpleHttpServer.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleHttpServer {
    public static void main(String[] args) {
        // 定义服务端监听的端口号
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is listening on port " + port);
            // 无限循环,持续等待客户端连接
            while (true) {
                // accept() 方法会阻塞,直到有客户端连接
                Socket clientSocket = serverSocket.accept();
                System.out.println("New client connected: " + clientSocket.getInetAddress().getHostAddress());
                // 为每个客户端连接创建一个新线程来处理,这样服务端可以同时处理多个请求
                new Thread(new ClientHandler(clientSocket)).start();
            }
        } catch (IOException e) {
            System.err.println("Server exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
    // 内部类,用于处理单个客户端的请求
    static class ClientHandler implements Runnable {
        private final Socket clientSocket;
        public ClientHandler(Socket socket) {
            this.clientSocket = socket;
        }
        @Override
        public void run() {
            try (
                // 获取输入流,用于读取客户端的请求
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                // 获取输出流,用于向客户端发送响应
                OutputStream out = clientSocket.getOutputStream()
            ) {
                // 读取客户端请求的第一行(请求行)
                String requestLine = in.readLine();
                System.out.println("Received request: " + requestLine);
                // 读取请求头,直到遇到一个空行
                String headerLine;
                while ((headerLine = in.readLine()) != null && !headerLine.isEmpty()) {
                    // 可以在这里处理请求头,Content-Length
                    System.out.println("Header: " + headerLine);
                }
                // HTTP 响应
                String httpResponse = "HTTP/1.1 200 OK\r\n" +
                                      "Content-Type: text/plain\r\n" +
                                      "Connection: close\r\n" +
                                      "\r\n" + // 空行,分隔响应头和响应体
                                      "Hello, World!";
                // 将响应写入输出流
                out.write(httpResponse.getBytes());
                out.flush();
                System.out.println("Response sent to " + clientSocket.getInetAddress().getHostAddress());
            } catch (IOException e) {
                System.err.println("Error handling client: " + e.getMessage());
            } finally {
                try {
                    // 关闭客户端连接
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

如何运行

  1. 将代码保存为 SimpleHttpServer.java
  2. 编译: javac SimpleHttpServer.java
  3. 运行: java SimpleHttpServer
  4. 你会看到控制台输出: Server is listening on port 8080
  5. 服务端已经启动并等待连接。

HTTP 客户端

我们将创建一个客户端,它会连接到上面启动的服务端(地址为 localhost:8080),发送一个 HTTP GET 请求,并打印服务端返回的响应。

我们将介绍两种方式:

  1. 传统方式: java.net.HttpURLConnection
  2. 现代方式 (Java 11+): java.net.http.HttpClient

传统 HttpURLConnection

这是在 Java 11 之前最常用的标准 HTTP 客户端 API。

Java HTTP客户端与服务端如何高效通信?-图2
(图片来源网络,侵删)

核心类

  • java.net.URL: 表示要请求的资源地址。
  • java.net.HttpURLConnection: 代表一个 HTTP 连接。

代码示例

// SimpleHttpClient.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class SimpleHttpClient {
    public static void main(String[] args) {
        // 目标 URL
        String urlString = "http://localhost:8080/";
        try {
            URL url = new URL(urlString);
            // 打开一个 HttpURLConnection 连接
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            // 设置请求方法为 GET
            connection.setRequestMethod("GET");
            // 获取响应码
            int responseCode = connection.getResponseCode();
            System.out.println("Sending 'GET' request to URL : " + urlString);
            System.out.println("Response Code : " + responseCode);
            // 如果响应码是 200 (OK),则读取响应体
            if (responseCode == HttpURLConnection.HTTP_OK) {
                // 使用 try-with-resources 自动关闭流
                try (BufferedReader in = new BufferedReader(
                        new InputStreamReader(connection.getInputStream()))) {
                    String inputLine;
                    StringBuilder response = new StringBuilder();
                    // 逐行读取响应
                    while ((inputLine = in.readLine()) != null) {
                        response.append(inputLine);
                    }
                    // 打印完整的响应
                    System.out.println("Response : " + response.toString());
                }
            } else {
                System.out.println("GET request not worked");
            }
        } catch (IOException e) {
            System.err.println("Client exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

现代 HttpClient (推荐,Java 11+)

Java 11 引入了全新的、更现代、更易用的 HttpClient API,它支持异步请求、WebSocket,API 设计更符合现代编程风格。

核心类

  • java.net.http.HttpClient: 客户端实例,可以配置各种参数。
  • java.net.http.HttpRequest: 代表一个 HTTP 请求。
  • java.net.http.HttpResponse: 代表一个 HTTP 响应。

代码示例

// ModernHttpClient.java
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
public class ModernHttpClient {
    public static void main(String[] args) {
        // 目标 URL
        String urlString = "http://localhost:8080/";
        // 1. 创建 HttpClient 实例
        // 可以配置连接超时、代理等
        HttpClient client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_1_1) // 使用 HTTP/1.1
                .connectTimeout(Duration.ofSeconds(5)) // 连接超时 5 秒
                .build();
        // 2. 创建 HttpRequest 实例
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(urlString))
                .header("User-Agent", "Java HttpClient") // 设置请求头
                .GET() // 显式指定 GET 请求
                .build();
        try {
            // 3. 发送请求并同步获取响应
            // 发送是异步的,但 get() 会阻塞直到响应返回
            HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
            // 4. 处理响应
            System.out.println("Status code: " + response.statusCode());
            System.out.println("Response headers: " + response.headers());
            System.out.println("Response body: " + response.body());
        } catch (Exception e) {
            System.err.println("Client exception: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

异步请求示例: HttpClient 的强大之处在于异步处理。

// ... (HttpClient 和 HttpRequest 的创建代码同上)
// 异步发送请求
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
       .thenApply(HttpResponse::body) // 当响应到达时,提取 body
       .thenAccept(System.out::println) // 然后打印 body
       .join(); // 等待异步操作完成,否则程序会立即退出

使用 Netty 框架 (进阶)

对于高性能、高并发的网络应用,直接使用 ServerSocketSocket 会比较复杂,Netty 是一个成熟的、高性能的 NIO 客户端/服务器框架,极大地简化了网络编程。

为什么选择 Netty?

  • 高性能: 基于 NIO (非阻塞 I/O),能轻松处理高并发连接。
  • 功能丰富: 内置了编解码器(如 HTTP、Protobuf、JSON)、SSL/TLS 支持等。
  • API 友好: 提供了清晰、易于使用的 API。

服务端示例 (使用 Netty)

你需要添加 Netty 依赖 (Maven):

Java HTTP客户端与服务端如何高效通信?-图3
(图片来源网络,侵删)
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.86.Final</version> <!-- 请使用最新版本 -->
</dependency>
// NettyHttpServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
public class NettyHttpServer {
    private final int port;
    public NettyHttpServer(int port) {
        this.port = port;
    }
    public void run() throws Exception {
        // EventLoopGroup 是 Netty 的处理核心,类似线程池
        // bossGroup 接受连接
        // workerGroup 处理业务
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // 指定使用 NIO 传输
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     // 添加 HTTP 编解码器,Netty 会自动处理 HTTP 协议的细节
                     ch.pipeline().addLast(new HttpServerCodec());
                     // 添加自定义的业务处理器
                     ch.pipeline().addLast(new SimpleHttpServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128) // 设置队列连接数
             .childOption(ChannelOption.SO_KEEPALIVE, true); // 保持连接
            System.out.println("Netty Server started on port " + port);
            // 绑定端口,开始接收连接
            ChannelFuture f = b.bind(port).sync();
            // 等待服务器 socket 关闭
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        new NettyHttpServer(8080).run();
    }
}

自定义业务处理器 SimpleHttpServerHandler.java:

// SimpleHttpServerHandler.java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.CharsetUtil;
public class SimpleHttpServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        if (msg instanceof FullHttpRequest) {
            FullHttpRequest request = (FullHttpRequest) msg;
            try {
                // 解析请求路径等
                System.out.println("Received request: " + request.uri());
                // 构建响应
                String responseContent = "Hello from Netty!";
                // Netty 提供了方便的工具类来构建 FullHttpResponse
                FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1,
                    HttpResponseStatus.OK,
                    Unpooled.copiedBuffer(responseContent, CharsetUtil.UTF_8)
                );
                // 设置响应头
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
                response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
                // 如果请求是 Keep-Alive,则保持连接
                if (HttpUtil.isKeepAlive(request)) {
                    response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
                }
                // 发送响应
                ctx.writeAndFlush(response);
            } finally {
                // 释放请求资源
                ReferenceCountUtil.release(request);
            }
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

总结与对比

特性 java.net.* (原生) java.net.http.HttpClient (Java 11+) Netty 框架
易用性 服务端复杂,客户端较繁琐 非常简单,API 现代化 较复杂,需要理解 Netty 的概念(Pipeline, Handler)
性能 一般,基于 BIO (阻塞 I/O) 较好,基于 NIO 非常高,专为高并发设计
功能 基础的 HTTP 功能 支持 HTTP/1.1, HTTP/2, WebSocket, 异步 极其丰富,支持多种协议,可扩展性强
依赖 无,JDK 自带 无,JDK 自带 (11+) 需要引入外部库
适用场景 - 简单的脚本工具
- 学习网络协议基础
- 不想引入第三方依赖的项目
- 现代 Java 应用的首选
- 需要异步请求或 WebSocket
- 从旧版 API 迁移
- 高性能 Web 服务
- RPC 框架
- 网络游戏、聊天应用
- 需要处理复杂协议

建议:

  • 对于初学者或简单应用: 从 java.net.HttpURLConnectionjava.net.ServerSocket 开始,能帮助你理解 HTTP 的基本原理。
  • 对于新项目 (Java 11+): 强烈推荐使用 java.net.http.HttpClient,它兼顾了易用性和性能,是 Java 官方力推的现代化 HTTP 客户端。
  • 对于高性能、高并发的生产环境: Netty 是不二之选,它是许多知名开源框架(如 Spring WebFlux, Dubbo, Elasticsearch)的底层网络库。
分享:
扫描分享到社交APP
上一篇
下一篇