Netty教程 / 第 90 节

第9章:常用协议支持

本章导读

Netty 内置了对多种常用协议的支持,包括 HTTP、WebSocket、SSL/TLS 等。本章将讲解如何使用这些内置支持快速构建应用。


9.1 HTTP 协议支持

9.1.1 HTTP 服务器

public class HttpServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            
                            // HTTP 编解码器
                            pipeline.addLast(new HttpServerCodec());
                            
                            // HTTP 聚合器(将多个HTTP消息聚合成一个完整的HTTP请求或响应)
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            
                            // 业务Handler
                            pipeline.addLast(new HttpServerHandler());
                        }
                    });
            
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("HTTP服务器启动: http://localhost:8080");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

class HttpServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
        // 获取请求信息
        String uri = request.uri();
        HttpMethod method = request.method();
        
        System.out.println("收到请求: " + method + " " + uri);
        
        // 构建响应
        String content = "<html><body><h1>Hello Netty HTTP!</h1></body></html>";
        ByteBuf buf = Unpooled.copiedBuffer(content, CharsetUtil.UTF_8);
        
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK,
                buf
        );
        
        // 设置响应头
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
        
        // 发送响应
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

9.1.2 HTTP 客户端

public class HttpClient {
    public static void main(String[] args) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            
                            // HTTP 编解码器
                            pipeline.addLast(new HttpClientCodec());
                            
                            // HTTP 聚合器
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            
                            // 业务Handler
                            pipeline.addLast(new HttpClientHandler());
                        }
                    });
            
            ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
}

class HttpClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
    
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        // 构建HTTP请求
        FullHttpRequest request = new DefaultFullHttpRequest(
                HttpVersion.HTTP_1_1,
                HttpMethod.GET,
                "/"
        );
        
        request.headers().set(HttpHeaderNames.HOST, "localhost");
        request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);
        
        // 发送请求
        ctx.writeAndFlush(request);
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) {
        System.out.println("状态: " + response.status());
        System.out.println("响应内容: " + response.content().toString(CharsetUtil.UTF_8));
    }
}

9.2 WebSocket 协议

9.2.1 WebSocket 服务器

public class WebSocketServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            
                            // HTTP 编解码器
                            pipeline.addLast(new HttpServerCodec());
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            
                            // WebSocket 协议处理器
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                            
                            // 业务Handler
                            pipeline.addLast(new WebSocketServerHandler());
                        }
                    });
            
            ChannelFuture future = bootstrap.bind(8080).sync();
            System.out.println("WebSocket服务器启动: ws://localhost:8080/ws");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

class WebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
        String text = msg.text();
        System.out.println("收到消息: " + text);
        
        // 回复消息
        ctx.writeAndFlush(new TextWebSocketFrame("服务器收到: " + text));
    }
    
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        System.out.println("客户端连接: " + ctx.channel().id());
    }
    
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        System.out.println("客户端断开: " + ctx.channel().id());
    }
}

9.2.2 WebSocket 客户端(HTML)

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Client</title>
</head>
<body>
    <h1>WebSocket 测试</h1>
    <input type="text" id="message" placeholder="输入消息">
    <button onclick="send()">发送</button>
    <div id="output"></div>
    
    <script>
        const ws = new WebSocket('ws://localhost:8080/ws');
        
        ws.onopen = function() {
            console.log('连接成功');
            document.getElementById('output').innerHTML += '<p>连接成功</p>';
        };
        
        ws.onmessage = function(event) {
            console.log('收到消息:', event.data);
            document.getElementById('output').innerHTML += '<p>收到: ' + event.data + '</p>';
        };
        
        ws.onclose = function() {
            console.log('连接关闭');
            document.getElementById('output').innerHTML += '<p>连接关闭</p>';
        };
        
        function send() {
            const message = document.getElementById('message').value;
            ws.send(message);
            document.getElementById('output').innerHTML += '<p>发送: ' + message + '</p>';
        }
    </script>
</body>
</html>

9.3 HTTPS/SSL/TLS

9.3.1 生成证书

# 生成自签名证书
keytool -genkey -alias netty -keyalg RSA -keystore netty.jks -keysize 2048 -validity 365

9.3.2 HTTPS 服务器

public class HttpsServer {
    public static void main(String[] args) throws Exception {
        // 加载证书
        SslContext sslContext = SslContextBuilder
                .forServer(new File("netty.crt"), new File("netty.key"))
                .build();
        
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            
                            // SSL Handler(必须在最前面)
                            pipeline.addLast(sslContext.newHandler(ch.alloc()));
                            
                            // HTTP 编解码器
                            pipeline.addLast(new HttpServerCodec());
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            
                            // 业务Handler
                            pipeline.addLast(new HttpServerHandler());
                        }
                    });
            
            ChannelFuture future = bootstrap.bind(8443).sync();
            System.out.println("HTTPS服务器启动: https://localhost:8443");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

9.4 Protocol Buffers 集成

9.4.1 定义 .proto 文件

syntax = "proto3";

option java_package = "com.netty.tutorial.proto";
option java_outer_classname = "UserProto";

message User {
    int32 id = 1;
    string name = 2;
    int32 age = 3;
}

9.4.2 使用 Protobuf

// 服务端
pipeline.addLast(new ProtobufVarint32FrameDecoder());
pipeline.addLast(new ProtobufDecoder(UserProto.User.getDefaultInstance()));
pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
pipeline.addLast(new ProtobufEncoder());
pipeline.addLast(new ProtobufHandler());

class ProtobufHandler extends SimpleChannelInboundHandler<UserProto.User> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, UserProto.User user) {
        System.out.println("收到用户: " + user.getName() + ", 年龄: " + user.getAge());
        
        // 回复
        UserProto.User response = UserProto.User.newBuilder()
                .setId(user.getId())
                .setName("Server")
                .setAge(0)
                .build();
        
        ctx.writeAndFlush(response);
    }
}

// 客户端发送
UserProto.User user = UserProto.User.newBuilder()
        .setId(1)
        .setName("Alice")
        .setAge(25)
        .build();

ctx.writeAndFlush(user);

9.5 JSON 编解码

9.5.1 使用 Jackson

// 添加依赖
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.14.0</version>
</dependency>

// JSON 编码器
public class JsonEncoder extends MessageToByteEncoder<Object> {
    
    private static final ObjectMapper mapper = new ObjectMapper();
    
    @Override
    protected void encode(ChannelHandlerContext ctx, Object msg, ByteBuf out) throws Exception {
        byte[] json = mapper.writeValueAsBytes(msg);
        out.writeInt(json.length);
        out.writeBytes(json);
    }
}

// JSON 解码器
public class JsonDecoder extends ByteToMessageDecoder {
    
    private static final ObjectMapper mapper = new ObjectMapper();
    private final Class<?> clazz;
    
    public JsonDecoder(Class<?> clazz) {
        this.clazz = clazz;
    }
    
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        if (in.readableBytes() < 4) {
            return;
        }
        
        in.markReaderIndex();
        int length = in.readInt();
        
        if (in.readableBytes() < length) {
            in.resetReaderIndex();
            return;
        }
        
        byte[] json = new byte[length];
        in.readBytes(json);
        
        Object obj = mapper.readValue(json, clazz);
        out.add(obj);
    }
}

// 使用
pipeline.addLast(new JsonDecoder(User.class));
pipeline.addLast(new JsonEncoder());

9.6 本章小结

本章我们学习了常用协议支持:

HTTP:HttpServerCodec、HttpObjectAggregator
WebSocket:WebSocketServerProtocolHandler
HTTPS/SSL/TLS:SslContext、SslHandler
Protocol Buffers:高效的二进制序列化
JSON:自定义 JSON 编解码器

关键要点

  1. Netty 内置了对主流协议的支持
  2. HTTP 和 WebSocket 可以在同一个端口上共存
  3. SSL Handler 必须放在 Pipeline 的最前面
  4. Protocol Buffers 性能优于 JSON
  5. 选择合适的序列化方案很重要

上一章第8章:粘包与拆包解决方案
下一章第10章:EventLoop与线程模型