什么是Netty?
Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。
Netty 是一个广泛使用的 Java 网络编程框架(Netty 在 2011 年获得了Duke’s Choice Award,见https://www.java.net/dukeschoice/2011)。它活跃和成长于用户社区,像大型公司 Facebook 和 Instagram 以及流行 开源项目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其强大的对于网络抽象的核心代码。
以上是摘自《Essential Netty In Action》这本书。
初步认识后,可以通俗的这样介绍Netty,Netty是封装了socket通信的基于nio的jar包,maven中引入后直接使用。
Netty的高级特性
独特的bufferAPI
使用自建的bufferAPI,而不是NIO的ByteBuffer来表示连续字节序列。
1>.基于ByteBuf实现自定义缓冲类型
2>.开箱即用的动态缓冲,如StringBuffer
3>.不再需要flip()
4>.一般比ByteBuffer具有更快的响应速度
Netty有强大的可扩展性
ByteBuf 具有丰富的操作集,可以快速的实现协议的优化。例如,ByteBuf 提供各种操作用于访问无符号值和字符串,以及在缓冲区搜索一定的字节序列。你也可以扩展或包装现有的缓冲类型用来提供方便的访问。自定义缓冲仍然实现自 ByteBuf 接口,而不是引入一个不兼容的类型。
透明零拷贝
在开发中可能会遇到一个缓冲区的数据是由两个不同组件生产而来,如果使用ByteBuffer就需要新建一个缓冲区,然后将两个缓冲区的数据拷贝到其中或先收集其中数据再写入新的缓冲区,但Netty提供的ByteBuf允许直接:
// 复合类型与组件类型是兼容的。
ByteBuf message = Unpooled.wrappedBuffer(header, body);
// 因此,你甚至可以通过混合复合类型与普通缓冲区来创建一个复合类型。
ByteBuf messageWithFooter = Unpooled.wrappedBuffer(message, footer);
// 由于复合类型仍是 ByteBuf,访问其内容很容易,
//并且访问方法的行为就像是访问一个单独的缓冲区,
//即使你想访问的区域是跨多个组件。
//这里的无符号整数读取位于 body 和 footer
messageWithFooter.getUnsignedInt(
messageWithFooter.readableBytes() - footer.readableBytes() - 1);
自动容量扩展
容量自动扩展,说白了就是ByteBuf实现了懒加载,在其中写入第一个字节时申请空间,当写入的字节数超过缓冲区设定大小时,能自动扩大缓冲区的容量。
高性能ByteBuf
最频繁使用的缓冲区 ByteBuf 的实现是一个非常薄的字节数组包装器(比如,一个字节)。与 ByteBuffer 不同,它没有复杂的边界和索引检查补偿,因此对于 JVM 优化缓冲区的访问更加简单。更多复杂的缓冲区实现是用于拆分或者组合缓存,并且比 ByteBuffer 拥有更好的性能。
统一的异步I/O API
传统的 Java I/O API 在应对不同的传输协议时需要使用不同的类型和方法。例如:java.net.Socket 和 java.net.DatagramSocket 它们并不具有相同的超类型,因此,这就需要使用不同的调用方式执行 socket 操作。
当你试图在不修改网络传输层的前提下增加多种协议的支持,这时便会产生问题。让这种情况变得更糟的是,Java 新的 I/O(NIO)API与原有的阻塞式的I/O(OIO)API 并不兼容,NIO.2(AIO)也是如此。
Netty 有一个叫做 Channel 的统一的异步 I/O 编程接口,这个编程接口抽象了所有点对点的通信操作。也就是说,如果你的应用是基于 Netty 的某一种传输实现,那么同样的,你的应用也可以运行在 Netty 的另一种传输实现上。Netty 提供了几种拥有相同编程接口的基本传输实现:
* 1>.基于 NIO 的 TCP/IP 传输 (见 io.netty.channel.nio),
* 2>.基于 OIO 的 TCP/IP 传输 (见 io.netty.channel.oio),
* 3>.基于 OIO 的 UDP/IP 传输, 和
* 4>.本地传输 (见 io.netty.channel.local).
切换不同的传输实现通常只需对代码进行几行的修改调整,例如选择一个不同的 ChannelFactory 实现。
此外,你甚至可以利用新的传输实现没有写入的优势,只需替换一些构造器的调用方法即可,例如串口通信。而且由于核心 API 具有高度的可扩展性,你还可以完成自己的传输实现。
基于拦截链模式的事件模型
在一个 ChannelPipeline 内部一个 ChannelEvent 被一组ChannelHandler 处理。这个管道是 Intercepting Filter (拦截过滤器)模式的一种高级形式的实现,因此对于一个事件如何被处理以及管道内部处理器间的交互过程,你都将拥有绝对的控制力。例如,你可以定义一个从 socket 读取到数据后的操作:
public class MyReadHandler implements SimpleChannelHandler {
public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the received message.
...
// And forward the event to the next handler.
ctx.sendUpstream(evt);
}
}
同时你也可以定义一种操作响应其他处理器的写操作请求:
public class MyWriteHandler implements SimpleChannelHandler {
public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
Object message = evt.getMessage();
// Do something with the message to be written.
...
// And forward the event to the next handler.
ctx.sendDownstream(evt);
}
}
使用快速开发的高级组件
SSL/TLS
在 Netty 内部,SslHandler 封装了所有艰难的细节以及使用 SSLEngine 可 能带来的陷阱。你所做的仅是配置并将该 SslHandler 插入到你的 ChannelPipeline 中。同样 Netty 也允许你实现像 StartTlS 那样所拥有的高级特性,这很容易。
HTTP
与现有的 HTTP 实现相比 Netty 的 HTTP 实现是相当与众不同的。在HTTP 消息的低层交互过程中你将拥有绝对的控制力。这是因为 Netty 的HTTP 实现只是一些 HTTP codec 和 HTTP 消息类的简单组合,这里不存在任何限制——例如那种被迫选择的线程模型。你可以随心所欲的编写那种可以完全按照你期望的工作方式工作的客户端或服务器端代码。这包括线程模型,连接生命期,快编码,以及所有 HTTP 协议允许你做的,所有的一切,你都将拥有绝对的控制力。
由于这种高度可定制化的特性,你可以开发一个非常高效的HTTP服务器,例如:
- 1>.要求持久化链接以及服务器端推送技术的聊天服务(如,Comet )
- 2>.需要保持链接直至整个文件下载完成的媒体流服务(如,2小时长的电影)
- 3>.需要上传大文件并且没有内存压力的文件服务(如,上传1GB文件的请求)
- 4>.支持大规模混合客户端应用用于连接以万计的第三方异步 web 服务。
Netty入门,如何使用
自己动手编写聊天服务器
- a>.编写handler类,用于处理从建立连接到连接销毁过程中的事件。
- b>.编写initializer初始化类,用于初始化管道,在管道中加入编码、解码器和handler类。
- c>.编写主类,用于初始化连接监听等配置。
Message封装类
package com.nettytest;
import java.io.Serializable;
/**
*Created by XCXCXCXCX on 2018/7/27.
*/
public class Message implements Serializable{
private String username;
private String message;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Server
- a,handler类
package com.nettytest;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
*Created by XCXCXCXCX on 2018/7/27.
*/
public class NettyServerHandler extends SimpleChannelInboundHandler {
//初始化channels,用来统一操作所有连接到服务器的客户端channels列表。
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
System.out.println(“服务器收到消息了!”);
return super.acceptInboundMessage(msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//貌似是传入msg类型如果不是指定类型,会重新触发channelRead,直到获取到所需的类型(这里是Message)
System.out.println(“开始读来自”+ctx.channel().remoteAddress()+”消息喽!”);
super.channelRead(ctx,msg);
// ByteBuf buf = (ByteBuf) msg;
// buf.writeBytes(buf); // (2)
// buf.release();
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelRegistered”);
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
// System.out.println(“Execute:channelUnregistered”);
// super.channelUnregistered(ctx);
// ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”在线”);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println(“服务器处理完这次消息喽!”);
super.channelReadComplete(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println(“Execute:userEventTriggered”);
super.userEventTriggered(ctx, evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelWritabilityChanged”);
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(“Execute:exceptionCaught”);
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”异常”);
cause.printStackTrace();
System.out.println(ctx.isRemoved());
ctx.close();
System.out.println(ctx.isRemoved());
channels.remove(incoming);
System.out.println(channels.size());
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”连接成功”);
channels.add(incoming);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message message) throws Exception {
for(Channel channel:channels){
System.out.println(channel.remoteAddress());
channel.writeAndFlush(message);
}
System.out.print(“[“+message.getUsername()+”]:”+message.getMessage());
}
}
- b,initializer
package com.nettytest;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
*Created by XCXCXCXCX on 2018/7/27.
*/
public class NettyServerInitializer extends ChannelInitializer{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(“decoder”, new ObjectDecoder(ClassResolvers.softCachingConcurrentResolver(Message.class.getClassLoader())));
pipeline.addLast(“handler”, new NettyServerHandler());
pipeline.addLast(“encoder”, new ObjectEncoder());
System.out.println(“Client:[“+socketChannel.remoteAddress()+”]:”+”连接上”);
}
}
- c,ChatServer主类
package com.nettytest;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* Created by XCXCXCXCX on 2018/7/27.
*/
public class ChatServer {
private int port;
public ChatServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new NettyServerInitializer())
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println(“ChatServer启动成功1”);
ChannelFuture channelFuture = b.bind(port).sync();
channelFuture.channel().closeFuture().sync();
channelFuture.channel().writeAndFlush(“ChatServer已关闭”);
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println(“ChatServer关闭成功”);
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8000;
}
new ChatServer(port).run();
}
}
Client
- a,handler类
package com.client;
import com.nettytest.Message;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
/**
* Created by XCXCXCXCX on 2018/7/27.
*/
public class NettyClientHandler extends SimpleChannelInboundHandler {
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
System.out.println(“Execute:acceptInboundMessage”);
return super.acceptInboundMessage(msg);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//貌似是传入msg类型如果不是指定类型,会重新触发channelRead,直到获取到所需的类型(这里是Message)
System.out.println(“Execute:channelRead”);
super.channelRead(ctx, msg);
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelRegistered”);
super.channelRegistered(ctx);
}
@Override
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelUnregistered”);
super.channelUnregistered(ctx);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”在线”);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”离线”);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelReadComplete”);
super.channelReadComplete(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
System.out.println(“Execute:userEventTriggered”);
super.userEventTriggered(ctx, evt);
}
@Override
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
System.out.println(“Execute:channelWritabilityChanged”);
super.channelWritabilityChanged(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println(“Execute:exceptionCaught”);
Channel incoming = ctx.channel();
System.out.println(“Client:”+incoming.remoteAddress()+”异常”);
cause.printStackTrace();
ctx.close();
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//Channel incoming = ctx.channel();
//System.out.println(“Client:”+incoming.remoteAddress()+”连接成功”);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//Channel incoming = ctx.channel();
//System.out.println(“Client:”+incoming.remoteAddress()+”取消连接”);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Message message) throws Exception {
System.out.println(“[“+message.getUsername()+”]:”+message.getMessage());
}
}
- b,initializer
package com.client;
import com.nettytest.Message;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
*Created by XCXCXCXCX on 2018/7/27.
*/
public class NettyClientInitializer extends ChannelInitializer{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(“decoder”, new ObjectDecoder(ClassResolvers.softCachingConcurrentResolver(Message.class.getClassLoader())));
pipeline.addLast(“encoder”, new ObjectEncoder());
pipeline.addLast(“handler”, new NettyClientHandler());
}
}
- c,ChatClient主类
package com.client;
import com.nettytest.Message;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* Created by XCXCXCXCX on 2018/7/27.
*/
public class NettyClientInitializer extends ChannelInitializer{
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(“decoder”, new ObjectDecoder(ClassResolvers.softCachingConcurrentResolver(Message.class.getClassLoader())));
pipeline.addLast(“encoder”, new ObjectEncoder());
pipeline.addLast(“handler”, new NettyClientHandler());
}
}
小结
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)//绑定多线程事件循环器,通常分boss和woker,boss用于接受,worker用于处理
.channel(NioServerSocketChannel.class)//设置channel类型
.childHandler(new NettyServerInitializer())//绑定管道
.option(ChannelOption.SO_BACKLOG, 128)//用于支持一些协议,如HTTP协议中的keepalive是否存在
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelPipeline pipeline = socketChannel.pipeline();//建立管道对象
pipeline.addLast("decoder", new ObjectDecoder(ClassResolvers.softCachingConcurrentResolver(Message.class.getClassLoader())));//创建解码器,这里使用了Message封装类,另外注意soft/weak区别,引用强弱性。
pipeline.addLast("handler", new NettyServerHandler());//注意管道中需要掌握正确的顺序,当执行handler时还未执行到后面的编码器!所以,如果decoder放在handler后面的话,将会导致handler中的message无法正常解码。
pipeline.addLast("encoder", new ObjectEncoder());//ps:Message对象需要实现Serializable,是因为io操作中需要对对象反序列化后传输
执行顺序:1-->2-->3-->4-->5-->6-->7-->10-->11
1.handlerAdded//建立连接时触发(先建立连接再注册通道)
2.channelRegistered/通道注册时触发,即建立连接时
3.channelActive//检测到通道变为活跃状态时触发
4.acceptInboundMessage//通过selectkey从channel收到消息时触发
5.channelRead//读取消息时触发
6.channelRead0//读取消息时触发(基于channelRead来运作的,适用于需要固定对象的场景)
7.channelReadComplete//读取消息完成时触发
*8.channelInactive//检测到通道变为不活跃状态时触发
*9.exceptionCaught//连接异常时触发,需要开发者记得进行处理异常,但需要注意的是channelHandlerContext.close()无法直接移除该对象,最终还是交由jvm进行回收。
10.channelUnregistered//通道注销时触发,即取消连接时
11.handlerRemoved//连接销毁时触发
channelWritabilityChanged//检测到通道可读性变化时触发
userEventTriggered//用户事件触发器,没有测试该功能,初步估计是用于长连接时客户端发起响应时的事件触发器
thanks for Netty 实现聊天功能