Netty一周上手(一)

Netty一周上手(一)

一.快速上手Netty,TCP

[准备环节]开发过的跳过这个
配置环境:

Java 1.8
(使用JDK 11会有如下反射警告,但不影响具体使用)
java.lang.UnsupportedOperationException: Reflective setAccessible(true) disabled
Maven 作者使用的Maven,也可以使用Grade

Maven pom:

<dependency>
	<groupId>io.netty</groupId>
	<artifactId>netty-all</artifactId>
	<version>4.1.51.Final</version>
</dependency>

如果是Spring项目,pom

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
   
  <properties>
        <java.version>8</java.version>
    </properties>

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.1.5</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.51.Final</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.21</version>
        </dependency>
        <dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java</artifactId>
            <version>3.9.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
推荐使用Druid连接池,支持XA事物,多数据源可以整合JTA做轻量级分布式事物

正文开始:快速HelloWord

public class NettyDemo {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        try {
            serverBootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,150)
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new MessageHandler());
                        }
                    });
            ChannelFuture cf = serverBootstrap.bind(9999).sync();
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }

    }

    public static class MessageHandler extends ChannelInboundHandlerAdapter{

		@Override
        public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello".getBytes()));
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            ByteBuf byteBuf = (ByteBuf) msg;
            String s = byteBuf.toString(CharsetUtil.UTF_8);
            System.out.print(s);
            ctx.channel().eventLoop().execute(()->{
                ctx.writeAndFlush(Unpooled.copiedBuffer("Receive".getBytes()));
            });
            ctx.channel().eventLoop().scheduleAtFixedRate(()->{
                System.out.println("HelloWorld");
            },2,2, TimeUnit.SECONDS);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            ctx.writeAndFlush(Unpooled.copiedBuffer("Exception".getBytes()));
        }
    }
}

解释说明:
Netty默认以Tcp连接方式进行,以上代码为服务端。tcp方式测试,无需编写客户端,使用Telnet即可,windows系统下在控制版面-打开或关闭windows功能-勾选telnet client,然后重启windows。在cmd中即可使用telnet命令。
使用方式

cmd> telnet localhost 9999
cmd> ctrl+]
cmd> telnet send hello

在linux下安装telnet

sh> telnet localhost 9999
sh> hello

代码解读

EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();

创建2个EventLoopGroup,这是2个线程组,默认为CPU核心数*2
里面采用的是Reactor主(boss),从(work)多线程模式。
Loop在线程中为while循环,结合Selector和SelectionKey进行dispatch
ServerBootstrap服务器操作对象,客户端为Bootstrap

serverBootstrap.group(bossGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,150)
                    .option(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            pipeline.addLast(new MessageHandler());
                        }
                    });

group :配置boss,work线程组,无参方法已经过时
channel: 配置ServerSocketChannel升级NioServerSocketChannel
SO_BACKLOG: 设置积压消息数量
SO_KEEPALIVE: 设置存活检测
childHandler: 设置work group的处理器
SocketChannel: 通过ServerSocketChannel accept后会得到SocketChannel
ChannelPipeline:channel管道,管理channel,已经控制hanlder list顺序。
addLast:把处理器加在最后

ChannelFuture cf = serverBootstrap.bind(9999).sync();
cf.channel().closeFuture().sync();

绑定端口,同步等待。获得netty异步模型的channelFuture。此channelFuture可以异步监听,比如关闭时回调handler。如果get,为同步

shutdownGracefully

进行安全关闭

handler说明:
ChannelInboundHandlerAdapter 入站handler适配器(抽象类)
ChannelOutboundHandlerAdapter 出站handler适配器(抽象类)
server:入站—客户端->服务端
出战—服务端->客户端
client:入站—服务端->客户端
出战—客户端->服务端
平时一般使用SimpleChannelInboundHandler
常用方法:channelRead0(ChannelHandlerContext ctx, I msg) 读取入站消息并处理
channelRegistered(ChannelHandlerContext ctx) 客户端连接,注册进行处理
channelReadComplete(ChannelHandlerContext ctx) 读取入站消息完毕并处理
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 处理异常捕获
userEventTriggered(ChannelHandlerContext ctx, Object evt) 捕获事件触发,比如IdleStateEvent

ChannelHandlerContext:channel上下文对象,可以通过该对象访问pipline,channel,channelHandler等。
Object msg 消息对象,默认为ByteBuf,通常通过encoder或者decoder转为String或ProtoBuf.Message对象,http或websocket为HttpObject

ctx.channel().eventLoop().execute(()->{
                ctx.writeAndFlush(Unpooled.copiedBuffer("Receive".getBytes()));
            });

执行task任务,向channel发送ByteBuf对象,并刷新缓冲流,此执行为异步,但是会放入task队列执行
unpooled 复制byte[]获得ByteBuf

ByteBuf解释
Nio 为ByteBuffer对象,数据缓冲区;Netty优化了ByteBuffer,自己搞了ByteBuf。底层为数组对象,拥有读取指针,终止位置等对象。在FileChannel的tr

 ctx.channel().eventLoop().scheduleAtFixedRate(()->{
                System.out.println("HelloWorld");
            },2,2, TimeUnit.SECONDS);

第一个2为首次延迟2s发送
第二个2为每次延长2s发送
scheduleAtFixedRate固定的定时任务

二.编码器与解码器

Netty 传过来的ByteBuf如何转为自己相要的数据,如String,Object
思考问题:
1.tcp一次发送数据不全,只有一半数据
2.tcp一次发生数据过多,与后面数据粘合

如何解决:
1.定长截取
2.特殊格式,如json,protobuf
3.\r\n结尾

Netty编解码器实现抽象类ByteToMessageCodec
abstract class ByteToMessageCodec extends ChannelDuplexHandler
public class ChannelDuplexHandler extends ChannelInboundHandlerAdapter implements ChannelOutboundHandler

由此可见此类在入站和出站时都会触发
有兴趣的同鞋自己可以实现此抽象类,自定义玩玩,没有我直接将工作中怎么玩
一般操作 tcp+protoBuf http+json (json的比较简单,不推荐用fastjson,太坑。建议使用jackson,ObjectMapper建议缓存以加快速度。二者效率一般情况都相差不大)
ProtoBuf为google设计的序列化方案,有点是现在是最快的,缺点是不可读,操作有点麻烦。
Json比较中庸,可读性好,速度较快
Xml数据最完整,速度最慢

ProtoBuf怎么玩?
第一步:下载Protobuf生成器 选择java版本
第二步:安装idea插件Protobuf Generator,Pojo to Proto,Protocol Buffer Editor
tool配置proto生成器,设置为java的。右键列表有生产编译选项
第三步:编写Pojo使用Pojo to Proto转.proto文件
第四步:参照以下网站修改Proto格式
https://developers.google.cn/protocol-buffers/docs/javatutorial
类似如下:

syntax = "proto3";
option java_package = "pojo";
option java_outer_classname = "UserDO";

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

第五步:生成Java文件


准备工作完成后

ServerBootstrap serverBootstrap = new ServerBootstrap();
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ChannelFuture channelFuture = serverBootstrap.group(bossGroup, workGroup).childOption(ChannelOption.SO_BACKLOG, 100)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new ProtobufEncoder())
                                    .addLast(new ProtobufDecoder(Any.getDefaultInstance()))
                                    .addLast(new ServerHandler());
                        }
                    }).bind(9999).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
   public static class ServerHandler extends SimpleChannelInboundHandler<Any> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, Any msg) throws Exception {
            UserDO.User user = msg.unpack(UserDO.User.class);
            System.out.println(user.getId());
            System.out.println(user.getName());
            System.out.println(user.getAge());
        }
    }

ProtobufEncoder 编码器,可以发送任意的Protobuf.Message
ProtobufDecoder 解码器,可以解码指定类型的Protobuf
Any.unpack 解包,Any和UserDO.User对象为Protobuf.Message对象


下篇预告:用Netty的方式打开Http
评论区留言,解答

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值