Netty使用例子 - @by_TWJ

Netty使用

  • Netty编写HTTP
  • Netty编写HTTPS
  • Netty编写WebSocket
  • Netty编写序列化数据

代码: gitee仓库

1、编码器/解码器

1.1、HTTP编码器/解码器

在这里插入图片描述

1.2、WebSocket编码器/解码器

在这里插入图片描述

1.3、序列化数据编码器/解码器

  1. JDK 序列化
    在这里插入图片描述

  2. JBoss Marshalling 序列化

在这里插入图片描述

  1. Protocol Buffers 序列化

在这里插入图片描述

2、编写例子

构建Netty例子的包

这里使用gradle

    implementation 'io.netty:netty-all:4.1.107.Final'

2.1、Netty编写HTTP服务

创建一个HTTP服务,接收JSON请求,并返回JSON数据

java代码

例子1、

public class NettyHttpServer {
    public static void main(String[] args) {

        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup eventGroup = new NioEventLoopGroup();
        new ServerBootstrap()
                .group(bossGroup,eventGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer(){
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ch.pipeline().addLast(new HttpServerCodec());
//                        ch.pipeline().addLast(new HttpClientCodec());
                        ch.pipeline().addLast(new HttpObjectAggregator(65535));
                        ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpRequest>(){
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
                                // 处理请求信息,并返回json数据
                                String result = requestMappingHandle(fullHttpRequest);

                                ByteBuf byteBuf = Unpooled.wrappedBuffer(result.getBytes(StandardCharsets.UTF_8));
                                FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,byteBuf);
                                fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
                                fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());

                                ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
                            }
                        });
                    }


                })
                .bind(8080)
        ;

    }

    private static String requestMappingHandle(FullHttpRequest fullHttpRequest) {

        ByteBuf byteBuf = fullHttpRequest.content();

        System.out.println("HTTP METHOD:"+fullHttpRequest.method().name());
        System.out.println("HTTP URI:"+fullHttpRequest.uri());
        System.out.println(HttpHeaderNames.CONTENT_TYPE+":"+fullHttpRequest.headers().get(HttpHeaderNames.CONTENT_TYPE));
        System.out.println("接收JSON:\n"+byteBuf.toString(CharsetUtil.UTF_8));
        return "{\"content\": \"测试 ,返回请求body\"}";
    }
}

2.2、Netty编写HTTPS(SSL)服务

编写ssl服务,只需要在 channelPipline 上添加ch.pipeline().addLast(“ssl”, new SslHandler(engine)) 即可。

java代码
public class NettyHttpSSLServer {
    public static void main(String[] args) throws CertificateException, SSLException {

        // Configure SSL. 这里使用自签名的SSL,用来测试
        final SslContext sslCtx = ServerUtil.buildSslContext();


        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup eventGroup = new NioEventLoopGroup();
        new ServerBootstrap()
                .group(bossGroup,eventGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer(){
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        SSLEngine engine = sslCtx.newEngine(ch.alloc());
                        ch.pipeline().addLast("ssl", new SslHandler(engine));
//                        ch.pipeline().addLast("ssl", sslCtx.newHandler(ByteBufAllocator.DEFAULT));
                        ch.pipeline().addLast(new HttpServerCodec());
                        ch.pipeline().addLast(new HttpObjectAggregator(65535));

                        ch.pipeline().addLast(new SimpleChannelInboundHandler<FullHttpRequest>(){
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest) throws Exception {
                                // 处理请求信息,并返回json数据
                                String result = requestMappingHandle(fullHttpRequest);

                                ByteBuf byteBuf = Unpooled.wrappedBuffer(result.getBytes(StandardCharsets.UTF_8));
                                FullHttpResponse fullHttpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,byteBuf);
                                fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
                                fullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());

                                ctx.writeAndFlush(fullHttpResponse).addListener(ChannelFutureListener.CLOSE);
                            }
                        });
                    }


                })
                .bind(8080)
        ;

    }

    private static String requestMappingHandle(FullHttpRequest fullHttpRequest) {

        ByteBuf byteBuf = fullHttpRequest.content();

        System.out.println("HTTP METHOD:"+fullHttpRequest.method().name());
        System.out.println("HTTP URI:"+fullHttpRequest.uri());
        System.out.println(HttpHeaderNames.CONTENT_TYPE+":"+fullHttpRequest.headers().get(HttpHeaderNames.CONTENT_TYPE));
        System.out.println("接收JSON:\n"+byteBuf.toString(CharsetUtil.UTF_8));
        return "{\"content\": \"测试 ,返回请求body\"}";
    }
}



/**
 * 证书生成
 */
public final class ServerUtil {

    // 启动的时候通过 -Dssl=true 来切换是否使用ssl
    private static final boolean SSL = System.getProperty("ssl") != null;

    private ServerUtil() {
    }

    public static SslContext buildSslContext() throws CertificateException, SSLException {
        if (!SSL) {
            return null;
        }
        // 自签名的证书
        SelfSignedCertificate ssc = new SelfSignedCertificate();
        return SslContextBuilder
                .forServer(ssc.certificate(), ssc.privateKey())
                .build();
    }
}

2.3、Netty编写WebSocket服务

2.3.1、普通的websocket服务

请求地址: ws://127.0.0.1:8080/websocket

public class WebSocketServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup eventGroup = new NioEventLoopGroup();
        new ServerBootstrap()
                .group(bossGroup,eventGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new WebSocketServerChannelInitializer())
                .bind(8080).sync()
                .channel().closeFuture().sync()
        ;
    }
}
@Slf4j
public class WebSocketServerChannelInitializer extends ChannelInitializer<Channel> {



    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(
                new HttpServerCodec(),
                new HttpObjectAggregator(65536),
                new WebSocketServerProtocolHandler("/websocket"),
                new TextFrameHandler(),
                new BinaryFrameHandler(),
                new ContinuationFrameHandler()

        );
    }

    private class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {

            log.info("TextFrameHandler {}",msg.text());
        }
    }

    private class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
            log.info("BinaryFrameHandler {}",2);
        }
    }

    private class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
            log.info("ContinuationFrameHandler {}",3);
        }
    }
}
2.3.2、聊天室websocket服务

请求地址: ws://127.0.0.1:8080/ws

ChannelConfigs、ChatServer、ChatServerChannelInitializer、HttpRequestHandler(http服务,跳过websocket服务)

1. ChannelConfigs.java

public class ChannelConfigs {
    /**
     * 定义一个channel组,管理所有的channel * GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
     */
    private  static ChannelGroup channelGroup;

    /**
     * 存放用户与Chanel的对应信息,用于给指定用户发送消息
     */
    private static ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();

    private static Object object = new Object();
    /**
     * 获取channel组
     */
    public static ChannelGroup getChannelGroup() {
        if(channelGroup==null){
            synchronized (object){
                if(channelGroup==null){
                    channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
                    return channelGroup;
                }else{
                    return channelGroup;
                }
            }
        }else{
            return channelGroup;
        }
    }

    /**
     * 获取用户channel map
     */
    public static ConcurrentHashMap<String, Channel> getUserChannelMap() {
        return userChannelMap;
    }
}

2. ChatServer.java

public class ChatServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup eventGroup = new NioEventLoopGroup();
        new ServerBootstrap()
                .group(bossGroup,eventGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChatServerChannelInitializer())
                .bind(8080).sync()
                .channel().closeFuture().sync()
        ;
    }
}

3. ChatServerChannelInitializer.java


public class ChatServerChannelInitializer extends ChannelInitializer<Channel> {

    @Override
    protected void initChannel(Channel ch) throws Exception {
        ch.pipeline().addLast(
                new HttpServerCodec(),
                new ChunkedWriteHandler(),
                new HttpObjectAggregator(65536),
                new HttpRequestHandler("/ws"),
                new WebSocketServerProtocolHandler("/ws"),
                new TextWebSocketFrameHandler()

        );
    }

    public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { //1
        private final ChannelGroup group;

        public TextWebSocketFrameHandler() {
            group = ChannelConfigs.getChannelGroup();
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {    //2
            if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {

                ctx.pipeline().remove(HttpRequestHandler.class);    //3

                group.writeAndFlush(new TextWebSocketFrame("Client " + ctx.channel() + " joined"));//4

                group.add(ctx.channel());    //5
            } else {
                super.userEventTriggered(ctx, evt);
            }
        }

        @Override
        public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
            group.writeAndFlush(msg.retain());    //6
        }
    }


}

4. HttpRequestHandler.java

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {    //1
    private final String wsUri;
    private static final File INDEX;

    static {
        URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
        try {
            String path = location.toURI() + "index.html";
            path = !path.contains("file:") ? path : path.substring(5);
            INDEX = new File(path);
        } catch (URISyntaxException e) {
            throw new IllegalStateException("Unable to locate index.html", e);
        }
    }

    public HttpRequestHandler(String wsUri) {
        this.wsUri = wsUri;
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if (wsUri.equalsIgnoreCase(request.getUri())) {
            ctx.fireChannelRead(request.retain());                    //2
        } else {
            if (HttpHeaders.is100ContinueExpected(request)) {
                send100Continue(ctx);                                //3
            }

            RandomAccessFile file = new RandomAccessFile(INDEX, "r");//4

            HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8");

            boolean keepAlive = HttpHeaders.isKeepAlive(request);

            if (keepAlive) {                                        //5
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
                response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.write(response);                    //6

            if (ctx.pipeline().get(SslHandler.class) == null) {        //7
                ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
            } else {
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);            //8
            if (!keepAlive) {
                future.addListener(ChannelFutureListener.CLOSE);        //9
            }
        }
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

2.4、Netty编写序列化

2.4.1、使用Protobuf序列化

引入的包
这里使用gradle构建


    implementation 'com.google.protobuf:protobuf-java:4.26.0-RC3'
    implementation 'com.google.protobuf:protobuf-java-util:4.26.0-RC3'
java代码

Student.proto

//未指定则使用proto2
syntax = "proto2";

//生成 proto 文件所在包路径
package com.wxw.notes.protobuf.proto;

//生成 proto 文件所在包路径
option java_package = "bean.proto";

//生成 proto 文件名
option java_outer_classname="StudentProto";

message Student{
  //自身属性
  optional string name = 1;
  optional int32 age = 2;
  optional string classes = 3;
}

NettyProtocolBuffersServer.java 服务端

public class NettyProtocolBuffersServer {
    public static void main(String[] args) throws CertificateException, SSLException, InterruptedException {



        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup eventGroup = new NioEventLoopGroup();
        new ServerBootstrap()
                .group(bossGroup,eventGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<Channel>() {
                    @Override
                    protected void initChannel(Channel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();

                        Protobuf 的编码器和解码器
                        pipeline.addLast(new ProtobufVarint32FrameDecoder());
                        pipeline.addLast(new ProtobufDecoder(StudentProto.Student.getDefaultInstance()));
                        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
                        pipeline.addLast(new ProtobufEncoder());

                        pipeline.addLast(new SimpleChannelInboundHandler<StudentProto.Student>() {
                            @Override
                            protected void channelRead0(ChannelHandlerContext ctx, StudentProto.Student msg) throws Exception {
                                System.out.println("name:"+msg.getName());
                                System.out.println("age:"+msg.getAge());
                                System.out.println("classes:"+msg.getClasses());
                                StudentProto.Student student = StudentProto.Student.newBuilder()
                                        .setAge(100)
                                        .setClasses("北京市西城区")
                                        .setName("大江东去浪淘尽")
                                        .build();
                                ctx.writeAndFlush(student);

                            }
                        });

                    }
                })
                .bind(8080).sync()
                .channel().closeFuture().sync()
        ;
//        System.out.println("Server start at port : " + "8080");

    }


}

NettyProtocolBuffersClient.java 客户端

public class NettyProtocolBuffersClient {
    public static void main(String[] args) throws CertificateException, SSLException, InterruptedException {


        startServer();

    }

    private static void startServer() {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class) // 这里通道是客户端通道,而不是服务端的NioServerSocketChannel
                    .handler(new ChannelInitializer<Channel>(){

                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            //Protobuf 的编码器和解码器
                            pipeline.addLast(new ProtobufVarint32FrameDecoder());
                            pipeline.addLast(new ProtobufDecoder(StudentProto.Student.getDefaultInstance()));
                            pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
                            pipeline.addLast(new ProtobufEncoder());

                            pipeline.addLast(new SimpleChannelInboundHandler<StudentProto.Student>() {
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    System.out.println("连通后,发送消息");

                                    StudentProto.Student student = StudentProto.Student.newBuilder()
                                            .setAge(28)
                                            .setClasses("高三")
                                            .setName("张三")
                                            .build();
                                    ctx.channel().writeAndFlush(student);
                                }

                                @Override
                                protected void channelRead0(ChannelHandlerContext ctx, StudentProto.Student msg) throws Exception {
                                    System.out.println("接收消息");
                                    System.out.println("name:" + msg.getName());
                                    System.out.println("age:" + msg.getAge());
                                    System.out.println("classes:" + msg.getClasses());

                                    ctx.channel().close().addListener((ChannelFutureListener) future -> {
                                        if (future.isSuccess()) {
                                            // Channel关闭成功
                                            System.out.println("Channel关闭成功");
                                        } else {
                                            // Channel关闭失败
                                            System.out.println("Channel关闭失败");
                                        }
                                    });
                                }
                            });
                        }
            });
            //与对应的url建立连接通道
            ChannelFuture channelFuture = bootstrap.connect("localhost",8080).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

3、tomcat与Netty对比

我们了解了Netty,他的性能更强,那么我们是否可以替代tomcat呢?
实际上tomcat也有基于NIO的优化,并且还有个APR模型,那我们何必用Netty替代tomcat呢。

4.1、tomcat

参考文章:Tomcat的BIO、NIO、APR模式对比与性能测试结果

tomcat支持三种运行模式

  • BIO 阻塞式(tomcat7以下默认)
  • NIO 非阻塞式(tomcat8及以上默认, springboot默认)
  • APR(Apache Portable Runtime)

APR:Tomcat将以JNI(Java Native Interface)的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作,从而大大提高Tomcat对静态文件的处理性能。Tomcat apr是在Tomcat上运行高并发应用的首选模式。

3.2、tomcat与Netty区别

tomcat 是使用Http协议通讯的,它是一个容器。
Netty 是一个异步事件驱动的网络应用框架,可以使用自定义协议,或者用已有的Http协议等。

相关文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值