netty的使用和相关知识

2019-05-12  本文已影响0人  无聊之园

1.不直接使用nio的理由:

直接使用nio你需要额外处理很多问题:网络闪断、客户端重复接入、客户端安全认证、消息的编解码、半包读写等。
nio使用复杂,还有小bug。

2.netty的简单例子:

netty使用比较简单,不同的场景对一个不同的hadler。

public class MyServer {

    public static void main(String[] args) {
        // EventLoopGroup包含了一组线程,bosstrap用于多线程接受客户端accept连接
        EventLoopGroup  bossstrap = new NioEventLoopGroup();
      // workstrap用于多线程处理连接后的channel读写 
       EventLoopGroup  workerstrap = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
         // 绑定线程组,之后,设置创建的channel为nioserverSocketChannel了,//之后绑定日志处理handler,之后绑定自定义serverInitializer,handler使用bosstrap线程组,childhandler使用workerstrap线程组
        serverBootstrap.group(bossstrap, workerstrap)
                .channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new MyServerInitializer());
        try {
               // 绑定端口,异步返回
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            // channelFuture类似于juc的Future,线程执行后的获取结果,线程没有执行完,则堵塞。channelFuture等服务端链路关闭之后回调。
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossstrap.shutdownGracefully();
            workerstrap.shutdownGracefully();
        }


    }
}
// 绑定的channel初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 责任链模式,添加解码器。
        // LengthFieldBasedFrameDecoder是对固定长度消息头步(记录了消息体长度),消息体的这种数据进行解码,防止粘包拆包。
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        // 把消息进行消息头、消息体的形式进行打包
        pipeline.addLast(new LengthFieldPrepender(4));
       // byte转strig的解码器
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        // 自定义处理器
        pipeline.addLast(new MyServerHandle());
    }
}
public class MyServerHandle extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + ", " + msg);
        ctx.channel().writeAndFlush("from server:" + UUID.randomUUID());
    }

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

客户端

public class MyClient {

    public static void main(String[] args) {
        // 客户端只有一个连接通道,所以只需要一个处理通道的读写线程池就可以
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            // 对应服务端的ServerBootstrap
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new MyClientInitalizer());
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }

    }
}
public class MyClientInitalizer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        pipeline.addLast(new LengthFieldPrepender(4));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));

        pipeline.addLast(new MyClientHandle());
    }
}
public class MyClientHandle extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output:" + msg);
        ctx.writeAndFlush("from client: " + LocalDateTime.now());
    }

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

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("来自客户端的问候");
    }
}

3. netty相关的问题

netty有很多种编码节码,组成了netty的丰富的功能,比如netty做http服务器,netty做websocket,都有对应的编码器封装。

1.粘包拆包:

tcp流,和发送的多条数据,有可能会粘在一起,或者只发送了一半。

解决思路:1.消息定长。2.加分隔符,比如回车符。3.消息头,消息体,消息头包含消息长度。4.更复杂的应用协议。

netty对粘包拆包提供很多解码器:LineBasedFrameDecoder,利用分隔符来处理粘包拆包。还有上面例子的LengthFieldBasedFrameDecoder,利用消息头消息体的形式。

2.系列化

java自带的系列化机制的缺点。

1.无法跨语言。
2.系列化后的字节数太大。
3.系列化性能低。

xml和json:
系列化后字节数大,性能低。

其他的系列化框架
google protobuf:跨多种语言,性能高,结构化数据。

使用步骤:编写prot文件,使用prot编译器,编译文件,生成java文件,用生成java类对对象进行编码节码。

thift:跟protobuf类似,比protobuf功能更丰富。

netty对各种系列化都有对应的编码解码类,直接使用就可以。

3. 零拷贝

其实就是,直接开辟堆外内存,省去了java堆到内核的一层拷贝。

上一篇 下一篇

猜你喜欢

热点阅读