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
评论区留言,解答