Netty—线程模型,核心组件和编解码
文章目录
优点
- 设计优雅,适用于各种传输类型的统一API阻塞和非阻塞Socket,基于灵活大且可扩展的实际爱你模型,可以清晰地分离关注点;高度可定制地线程模型,但线程,一个或多个线程池
- 使用方便,详细记录地javadoc
- 高性能,吞吐量更高,延迟更低;减少资源消耗,最小化不必要地内存复制
- 安全,完整地SSL/TLS和startTLS支持
线程模型
目前存在的线程模型:
- 传统阻塞I/O服务模型
- Reactor模式(反应器模式或者分发者模式)
- 根据Reactor的数量和处理资源池线程的数量不同,分为3种
- 单Reactor单线程
- 单Reactor多线程
- 主从Reactor多线程
- 根据Reactor的数量和处理资源池线程的数量不同,分为3种
- Netty线程模式(主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)
传统阻塞I/O服务模型的缺点:
- 当并发数很大,就会创建大量的线程,占用很大系统资源
- 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源的浪费。
Reactor模式,针对传统IO模型缺点的解决方案
- 基于IO复用模型,多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理
- 基于线程池复用线程资源,不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。
Reactor模式核心组成
- Reactor:Reactor在一个单独线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应
- Handler:处理程序执行IO事件要完成的实际事件
单Reactor单线程
优点:
- 模型简单,没有多线程,进程通信,竞争的问题,全部在一个线程中完成
缺点:
- 性能问题,只有一个线程,整个进程无法处理娶其他连接事件,容易导致性能瓶颈
- 可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障
单Reactor多线程
模型说明
- Reactor对象通过select监控客户端请求事件,收到事件收,通过dispatch进行分发
- 如果是建立连接事件,则通过acceptor通过accpet处理连接事件,然后创建一个hadler对象处理完成连接后的各种事件
- 如果不是连接请求,则由reactor分发调用俩姐对应的handler来处理
- handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的work线程池的某个线程处理业务
- worker线程池会分配独立线程完成真正的业务,并将结果返回给handler
优点:
- 可以充分利用多核cpu的处理能力
缺点:
- 多线程数据共享和访问比较复杂,reactor处理的事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈。
主从Reactor模式
方案说明:
- Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过acceptor处理连接事件。
- 当accept处理连接事件后,MainReactor将连接分配给SubReactor
- subreactor将连接事件加入到连接队列进行监听,并创建handler进行各种事件处理
- 当有新事件发生时,subreactor就会调用对应的handler处理
- handler通过read读取数据,分发给后面的worker线程池处理
- worker线程池分配独立的worker线程进行业务处理,并返回结果。
- handler收到响应的结果后,在通过send将结果返回给client
优点:
- 父线程与子线程的数据交互简单职责明确,父线程只需接收新连接,子线程完成后续的业务处理
- reactor主线程只需要把新连接传给子线程,子线程无需返回数据
缺点:
- 编程复杂度高
Netty模型
说明:
- netty抽象出两组线程池
- BossGroup专门负责接收客户端的连接
- WorkGroup专门负责网络的读写
- 都是NioEventLoopGroup,相当于事件循环组,含有多个事件循环,每一个事件循环是NioEventLoop
- NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网络通讯
- NioEventLoopGroup可以含有多个线程,即可以含有多个NioEventLoop
- Boss NioEventLoop循环执行步骤
- 轮询accept事件
- 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个Work NioEventLoop上的selector
- 处理任务队列的任务,即runAllTask
- 每个Work NioEventLoop循环执行的步骤
- 轮询read,write事件
- 处理IO事件,在对应的NioSocketChannel处理
- 处理任务队列的任务,即NioSocketChannel
- 每个Work NioEventLoop处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获得对应的通道,管道中维护了很多处理器。
示例:
server:
public class NettyServer {
public static void main(String[] args) throws InterruptedException {
//创建BossGroup 和WorkGroup
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
//创建服务器断案的启动对象,配置参数
ServerBootstrap bootstrap = new ServerBootstrap();
//使用链式编程来配置
bootstrap.group(boosGroup,workGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作为服务器的通道实现
.option(ChannelOption.SO_BACKLOG,128) //设置线程队列得到的连接个数
.childOption(ChannelOption.SO_KEEPALIVE,true) //设置保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyServerHandler());
}
}); //给workGroup的EventLoop对应的管道设置处理器
System.out.println("server is ready ...");
//启动服务器,并绑定端口
ChannelFuture channelFuture = bootstrap.bind(8888).sync();
//关闭通道进行监听
channelFuture.channel().closeFuture().sync();
}
}
serverHandler:
/*
自定义的handler需要继承netty规定好的某个handlerAdapter
* */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
//读取数据实际(这里可以读取客户端发送的消息)
/*
* ChannelHandlerContext ctx上下文对象,含有管道(pipeline),通道(channel),地址
* Object msg 就是客户端发送的数据
* */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("上下文对象为 : " + ctx);
ByteBuf byteBuf=(ByteBuf) msg;
System.out.println("客户端发送的消息是: " + byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("客户端地址 :" + ctx.channel().remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//将数据写入缓存,并刷新
System.out.println("已经读取完毕");
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,client",CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//发生异常,关闭通道
ctx.channel().close();
}
}
client:
public class NettyClient {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
System.out.println("client is ok");
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
channelFuture.channel().close().sync();
}
}
clientHandler:
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
//当通道就绪就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client ctx:" + ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server".toString(), CharsetUtil.UTF_8));
}
//当通道有读取事件,触发该方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf=(ByteBuf) msg;
System.out.println("server send info :" + byteBuf.toString(CharsetUtil.UTF_8));
System.out.println("server address : "+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
任务队列
-
用户自定义的普通任务
-
ctx.channel().eventLoop().execute(new Runnable() { @Override public void run() { try { System.out.println("开始休眠"); Thread.sleep(3000); ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server", CharsetUtil.UTF_8)); System.out.println("休眠结束"); } catch (InterruptedException e) { e.printStackTrace(); } } });
-
-
用户自定义定时任务
-
ctx.channel().eventLoop().schedule(new Runnable() { @Override public void run() { ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server2",CharsetUtil.UTF_8)); } },5, TimeUnit.SECONDS);
-
-
非当前Reactor线程调用Channel的各种方法
方案再说明
- NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的scoket网络通道。
- NioEventLoop内部采用串行化设计,从消息的读取—>解码—>处理—>编码—>发送,始终由IO线程NioEventLoop负责
- NioEventLoopGroup下包含多个NioEventLoop
- 每个NioEventLoop中包含一个selector,一个taskQueue
- 每个NioEventLoop的selector上可以注册监听多个NioChannel
- 每个NioChannel只会绑定在一个NioEventLoop上
- 每个NioChannel都会 绑定有一个自己的ChannelPipeline
Future说明
- 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,比如检索计算等
- ChannelFuture是一个接口,我们可以设置监听器,当监听的事件发生时,就会通知到监听器
异步模型
工作原理示意图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoOMGSJd-1619253120266)(E:\Typora\img\20190430191016658.png)]
说明:
- 在使用netty编程时,拦截操作和转换出入站数据只需要提供callback或利用future即可。这使得链式操作简单,高效,并有利于编写可重用的,通用的代码。
- netty框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来。
Future-Listener机制
-
当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的channelFuture来获取操作执行的状态,注册监听函数执行完成后的操作。
-
常用方法:
- isDone():判断操作是否完成
- isSuccess():判断操作是否成功
- getCase():获取已完成,但操作失败的原因
- isCancelled():判断操作是否被取消
- addListener():添加监听器
-
示例:
-
channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (channelFuture.isSuccess()) { System.out.println("监听8888端口成功"); }else { System.out.println("监听8888端口失败"); } } });
-
-
优点:
- 相比于传统的阻塞IO,执行IO操作后线程会被阻塞住,直到操作完成;异步处理的好处就是不会操作线程阻塞,线程在IO操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量。
http服务程序示例
TestHttpServer:
public class TestHttpServer {
public static void main(String[] args) {
EventLoopGroup boosGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boosGroup,workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new TestHttpServerChannelInitializer());
serverBootstrap.bind(8888).sync();
System.out.println("服务器开启...");
}catch (Exception e){
e.printStackTrace();
}finally {
//让服务器一直运行,就不需要关闭bossgroup和workgroup
// System.out.println("服务器关闭...");
// boosGroup.shutdownGracefully();
// workGroup.shutdownGracefully();
}
}
}
TestHttpServerChannelInitializer:
public class TestHttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new TestHttpServerHandler());
}
}
TestHttpServerHandler
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
//判断请求是否是request
if (msg instanceof HttpRequest) {
System.out.println("msg 类型:" + msg.getClass());
}
System.out.println(ctx.channel().remoteAddress());
//返回消息给客户端
ByteBuf buf= Unpooled.copiedBuffer("this is a msg", CharsetUtil.UTF_8);
//构建一个http的响应
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,buf.readableBytes());
//将消息返回
ctx.channel().writeAndFlush(response);
}
}
资源过滤:在handler中进行资源的过滤(示例如下)
if (msg instanceof HttpRequest) {
System.out.println("msg 类型:" + msg.getClass());
HttpRequest request= (HttpRequest) msg;
URI uri = new URI(request.uri());
if ("/favicon.ico".equals(uri.getPath())) {
System.out.println("浏览器请求了图标,这里不做任何响应");
return;
}
}
核心模块组件
-
BootStrap,ServerBootStrap
- BootStrap意思是引导,一个netty应用通常由一个bootstrap开始,主要作用是配置整个netty程序,串联各个组件,netty中bootstrap类是客户端程序的启动引导类,serverbootstrap是服务端启动的引导类
-
Future,channelFuture
- netty中所有IO操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和channelFutures,他们可以注册一个监听,当操作执行成功或者失败监听会自动触发注册的监听事件
- 常用方法:
- channel():返回当前正在进行IO操作的通道
- sync():等待异步操作执行完毕
-
channel
- netty网络通信的组件,能够用于执行网络的IO操作
- 通过channel可获得当前网络连接的通道的状态
- 通过channel可获得网络连接的配置参数
- channel提供异步的网络IO操作,异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成
- 调用立即返回一个channelFuture实例,通过注册监听器到channelFuture上,可以IO操作成功,失败,消失时回调通知通知调用方
- 支持关联IO操作对应的处理程序
- 不同协议,不同阻塞类型的连接都有不同的channel与之对应
-
channelHandler
- channelHandler是一个接口,处理 IO事件或者拦截IO操作,并将其转发到其channelPipeline(业务处理链)中的下一个处理程序
- channelHandler本身没有提供太多的方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类
- ChannelInboundHandlerAdapter用于处理入站操作
- ChanneloutboundHandlerAdapter用于处理出站操作
- channelDupexHandler用于处理入站和出站操作(一般不建议使用,容易混淆)
-
channelPipeline和pipeline
- channelPipeline是一个handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿netty的链(也可以理解为channelPipeline是保存channelHandler的list,用于处理和拦截channel的入站事件和出站事件)
- channelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及channel中各个的channelHandler如何交互
- 一个channel包含一个channelPipeline,而channelPipeline中又维护了一个又channelHandlerContext组成的双向链表,并且每个channelHandlerContext中又关联着一个channelHandler
- 入站事件和出站事件在一个双向链表中,入站事件从链表head完后传递到最后一个入站的handler,出站事件从链表的tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。
-
channelHandlerContext
- 保存channel相关的所有上下文信息,同时关联一个channelHandler对象
- channelHandlerContext中包含一个具体的事件处理器channelHandler,同时channelHandlerContext中也绑定了对应的pipeline和channel的信息,方便对channelHandler进行调用
-
channelOption
- netty在创建channel实例后,一般需要设置channelOption参数
- ChannelOption.SO_BACKLOG
- 对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求时顺序处理的。所以同一时间只能处理一个客户端请求。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定队列的大小
- ChannelOption.SO_KEEPALIVE
- 一直保持连接的活动状态
- ChannelOption.SO_BACKLOG
- netty在创建channel实例后,一般需要设置channelOption参数
-
EventLoopGroup和NioEventLoopGroup
- EventLoopGroup是一组抽象,netty为了更好的利用多核cpu资源,一般会有多个eventLoop同时工作,每个EventLoop维护着一个selector实例
- EventLoopGroup提供next接口,可以从组里按照一定的规则获取其中一个eventLoop来处理任务
- BossEventLoopGroup通常是一个单线程的eventLoop,eventLoop维护了一个注册了serverSocketChannel的selector实例BossEventLoop不断轮询slector将连接事件分离出来。
- 然后将接收到的socketChannel转交给WorkEventLoopGroup
- WorkEventLoopGroup会由next选择其中一个eventLoopGroup来将这个socketChannel注册到其维护的selector并对其后续的IO事件进行处理。
-
Unpooled类
-
netty提供一个专门用来操作缓冲区(netty的数据容器)的工具类
-
常用方法:
- copiedBuffer(CharSequence string, Charset charset):通过给定的数据和字符编码返回一个byteBuf对象
- 通过上述方法获取到的buffer并不是netty中的byteBuffer,而是unpooled类中的内部类,其实际容量是大于字符串长度的。
-
注意:
- 在netty中buffer不需要flip进行读写切换,因为底层维护了一个readerIndex和writerIndex
- 写入时(writeByte),writerIndex会随之变化
- 读取时(readByte),readerIndex会随之变化
- 获取指定位置的数据(getByte),readerIndex不会变化
- 通过readerIndex和writerIndex,将buffer分为三个区域
- 0==>readerIndex:已经读取的区域
- readerIndex==>writerIndex:可以读取的区域
- writerIndex==>capacity:可写区域
-
Netty练习:群聊功能的实现
GroupChatServer:
public class GroupChatServer {
private int port;
public GroupChatServer(int port){
this.port=port;
}
public void run() throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//添加解码器
pipeline.addLast(new StringDecoder());
//添加编码器
pipeline.addLast(new StringEncoder());
//添加自定义处理器
pipeline.addLast(new GroupChatServerHandler());
}
});
System.out.println("group server is already run");
ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
// channelFuture.channel().close();
}catch (Exception e){
e.printStackTrace();
}
// finally {
// bossGroup.shutdownGracefully();
// workGroup.shutdownGracefully();
// }
}
public static void main(String[] args) {
try {
new GroupChatServer(8888).run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
GroupChatServerHandler:
public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {
//定义一个channel组,管理所有的channel
//GlobalEventExecutor.INSTANCE是全局的事件执行器,是一个单例
private static ChannelGroup channelGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
//表示连接建立,一旦建立,第一个被执行
//将当前的channel加入到channelGroup中
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.add(channel);
//将该客户加入聊天的信息推送给在线的客户端
/*
writeAndFlush 不需要我们自己遍历channelGroup,他会自行遍历channelGroup,然后发送数据
* */
channelGroup.writeAndFlush("[client]:"+channel.remoteAddress()+" join chat\n");
}
//表示该客户处理连接状态
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[client]:" + channel.remoteAddress() + " online");
}
//表示该客户处于离线状态
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[client]:" + channel.remoteAddress() + " outline");
}
//表示该客户退出群聊
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
channelGroup.writeAndFlush("[client]:"+channel.remoteAddress()+" already out");
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Channel channel = ctx.channel();
//群聊发送信息时,要排除自己
channelGroup.forEach(ch->{
if (channel!=ch) {
ch.writeAndFlush("[client]:"+channel.remoteAddress()+"send info "+msg+"\n");
}else {
ch.writeAndFlush("[self]:"+channel.remoteAddress()+"send info "+msg+"\n");
}
});
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
GroupChatClient:
public class GroupChatClient {
private final String host;
private final int port;
public GroupChatClient(String host,int port){
this.host=host;
this.port=port;
}
public void run() throws InterruptedException {
NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringEncoder());
pipeline.addLast(new StringDecoder());
pipeline.addLast(new GroupChatClientHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
Channel channel = channelFuture.channel();
System.out.println("----------" + channel.remoteAddress() + "----------");
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()){
String msg = scanner.nextLine();
channel.writeAndFlush(msg+"\r\n");
}
}
public static void main(String[] args) throws InterruptedException {
GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1", 8888);
groupChatClient.run();
}
}
GroupChatClientHandler:
public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("get info: " + msg);
}
}
Netty心跳检测机制
IdleStateHandler(long readerIdleTime, l ong writerIdleTime, long allIdleTime,TimeUnit unit)
IdleStateHandler:是netty提供的处理空闲状态的处理器,当IdleStateHandler触发后,就会传递给管道的下一个handler去处理,通过触发下一个handler的userEventTiggered,在该方法去处理IdlestateEvent。
参数说明:
- readerIdleTime:表示多长时间没有读,就会发送一个心跳检测包,检测是否连接
- writerIdleTime:表示多长时间没有写,就会发送一个心跳检测包,检测是否连接
- allIdleTime:表示多长时间没有读写,就会发送一个心跳检测包,检测是否连接
当IdleStateHandler触发后,下一个handler的处理,
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event=(IdleStateEvent) evt;
String type=null;
switch (event.state()){
case READER_IDLE:
type="no_read";
break;
case WRITER_IDLE:
type="no_write";
break;
case ALL_IDLE:
type="no_all";
break;
}
}
}
}
WebSocket长连接
WebSocketTest:
public class WebSocketTest {
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.handler(new LoggingHandler(LogLevel.INFO))
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//因为基于HTTP协议,使用http的编码和解码
pipeline.addLast(new HttpServerCodec());
//http是以块的方式写
pipeline.addLast(new ChunkedWriteHandler());
/*
因为http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
这就是为什么当浏览器发送大量数据时,会发出多次http请求
* */
pipeline.addLast(new HttpObjectAggregator(8192));
/*
* 对应socket,它的数据是以帧的形式传递
* WebSocketServerProtocolHandler的核心功能就是将http协议升级为ws协议,保持长连接
*这里需要注意的是WebSocketServerProtocolHandler参数和ws请求参数是相对应的
* */
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
//自定义handler
pipeline.addLast(new WebSocketTestHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
}
}
WebSocketTestHandler:
//TextWebSocketFrame 表示处理的数据是文本帧
public class WebSocketTestHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("收到的数据为: " + msg.text());
//回复浏览器
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器已经接收到消息: "+msg.text()));
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("浏览器连接上服务器"+ctx.channel().id().asLongText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("浏览器断开和服务器的连接"+ctx.channel().id().asLongText());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.channel().close();
}
}
hello.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form onsubmit="return false">
<textarea name="message" style="height: 300px;width: 300px"></textarea>
<input type="button" value="send info" onclick="send(this.form.message.value)">
<textarea id="response" style="height: 300px;width: 300px"></textarea>
<input type="button" value="clear" onclick="document.getElementById('response').value=''">
</form>
</body>
<script>
var socket;
if(window.WebSocket){
socket=new WebSocket("ws://localhost:8888/hello");
//相当于channelRead0,ev收到服务器端送回的消息
socket.onmessage=function (ev){
let rt = document.getElementById('response');
rt.value=rt.value+"\n"+ev.data;
}
//相当于连接开启(感知到连接开启)
socket.onopen=function (ev){
let rt = document.getElementById('response');
rt.value="连接已经开启";
}
socket.onclose=function (ev){
let rt = document.getElementById('response');
rt.value=rt.value+"\n"+"连接已经关闭了";
}
}else {
alert("当前游览器不支持websocket")
}
function send(message){
if(!window.WebSocket){return ;}
if (socket.readyState==WebSocket.OPEN){
socket.send(message);
}else {
alert("连接没有开启");
}
}
</script>
</html>
编码和解码
基本介绍
- 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
- codec(编解码器)的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder负责把业务数据转换成字节码数据,decoder负责把字节码数据转化成业务数据
netty提供的编码器
- StringEncoder,对字符串数据进行编码
- ObjectEncoder,对java对象进行编码
netty提供的解码器
- StringDecoder:对字符串数据进行解码
- ObjectDecoder:对java对象进行解码
问题:
netty本身自带的ObjectEncoder和ObjectDecoder可以用来实现pojo对象和各种业务对象的编码和解码,底层使用的仍是Java序列化技术,而Java序列化技术本身效率就不高,存在以下问题
ProtoBuf:
针对上述问题,提出解决方案—>protoBuf
protoBuf是google发布的开源项目,全称google protocol buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。很适合做数据存储和RPC数据交换格式。支持跨平台,跨语言