Netty简介
Netty是由JBOSS提供的基于Java NIO的开源框架,Netty提供异步非阻塞、事件驱动、高性能、高可靠、高可定制性的网络应用程序和工具,可用于开发服务端和客户端。
netty 可以实现:
- HTTP服务器
- FTP服务器
- UDP服务器
- RPC服务器
- WebSocket服务器
- Redis的Proxy服务器
- MySQL的Proxy服务器
Netty 功能特性如下:
- 传输服务:支持 BIO 和 NIO;
- 容器集成:支持 OSGI、JBossMC、Spring、Guice 容器;
- 协议支持:HTTP、Protobuf、二进制、文本、WebSocket 、MQTT等一系列常见协议都支持。还支持通过实行编码解码逻辑来实现自定义协议;
- Core 核心:可扩展事件模型、通用通信 API、支持零拷贝的 ByteBuf 缓冲对象。
不选择java原生NIO编程的原因
- 1、API复杂
- 2、要对多线程很熟悉:因为NIO涉及到Reactor模式
- 3、高可用的话:需要处理断连重连、网络闪断、半包读写、失败缓存、网络拥堵和异常码流等问题
- 4、JDK NIO的bug 例如: epoll bug,它会导致Selector 空轮询,最终导致CPU 100%
为什么选择Netty
- API使用简单,更容易上手,开发门槛低
- 功能强大,预置了多种编解码功能,支持多种主流协议
- 定制能力高,可以通过ChannelHandler对通信框架进行灵活地拓展
- 高性能,与目前多种NIO主流框架相比,Netty综合性能最高
- 高稳定性,解决了JDK NIO的BUG
- 经历了大规模的商业应用考验,质量和可靠性都有很好的验证
示例代码
maven引用
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
服务器端代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* Created by lipine on 2019-12-05.
*/
public class Server {
private int port;
public Server(int port){this.port = port;}
public void run()throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup(); //用来接收进来的连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); //用来处理已经被接收的连接
try {
ServerBootstrap bootstrap = new ServerBootstrap(); //启动NIO服务的辅助启动类
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class) //服务端
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//心跳机制 参数:1.读空闲超时时间 2.写空闲超时时间 3.所有类型的空闲超时时间(读、写) 4.时间单位
//在Handler需要实现userEventTriggered方法,在出现超时事件时会被触发
socketChannel.pipeline().addLast("idleStateHandler", new IdleStateHandler(60, 0, 0,TimeUnit.SECONDS));
//设置解码器
socketChannel.pipeline().addLast("decoder", new ByteArrayDecoder());
socketChannel.pipeline().addLast("channelHandler", new ServerHandler());
//设置编码器
socketChannel.pipeline().addLast("encoder",new ByteArrayEncoder());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture cf = bootstrap.bind(port).sync(); //绑定端口,开始接收进来的连接
cf.channel().closeFuture().sync(); //等待服务器socket关闭
}catch (Exception e){
e.printStackTrace();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args)throws Exception{
new Server(8081).run();
}
}
服务端消息处理代码
mport io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.ssl.SslHandshakeCompletionEvent;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.ReferenceCountUtil;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by lipine on 2019-12-05.
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {
private AtomicInteger channelCount = new AtomicInteger(0); //通道数量
/**
* 读数据
* @param ctx
* @param msg
* @throws Exception
*/
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("read channel=" + ctx.channel() + ", total channel=" + channelCount);
try {
byte[] bytes = (byte[])msg;
System.out.println("hex message:"+BytesHexStrTranslate.bytesToHexString(bytes));
System.out.println(new String(bytes));
String returnMsg = "hi , Im is server...";
Channel channel = ctx.channel();
channel.writeAndFlush(returnMsg.getBytes());
} finally {
// 抛弃收到的数据
ReferenceCountUtil.release(msg);
}
}
/**
* 心跳检测的超时时会触发
* @param ctx
* @param evt
* @throws Exception
*/
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent e = (IdleStateEvent) evt;
if (e.state() == IdleState.READER_IDLE) {
System.out.println("trigger channel =" + ctx.channel());
ctx.close(); //如果超时,关闭这个通道
}
} else if (evt instanceof SslHandshakeCompletionEvent) {
System.out.println("ssl handshake done");
//super.userEventTriggered(ctx,evt);
}
}
/**
* 当通道活动时
* @param ctx
* @throws Exception
*/
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channelCount.incrementAndGet();//当有新通道连接进来时,将通道数+1
System.out.println("active channel=" + ctx.channel() + ", total channel=" + channelCount + ", id=" + ctx.channel().id().asShortText());
}
/**
* 当通道不活动时
* @param ctx
* @throws Exception
*/
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//当通道关闭时,将通道数-1
ctx.close();
channelCount.decrementAndGet();
System.out.println("inactive channel,channel=" + ctx.channel() +", id=" + ctx.channel().id().asShortText());
}
/**
* 异常获取
* @param ctx
* @param cause
* @throws Exception
*/
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exception channel=" + ctx.channel() + " cause=" + cause); //如果不活跃关闭此通道
ctx.close();
}
}
客户端代码
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
/**
* Created by lipine on 2019-12-05.
*/
public class Client {
private String host;
private int port;
public Client(String host,int port){
this.host = host;
this.port = port;
}
public void run() throws Exception {
// 配置客户端NIO线程组
EventLoopGroup group = new NioEventLoopGroup();
try {
// 客户端辅助启动类 对客户端配置
Bootstrap b = new Bootstrap();
b.group(group)//
.channel(NioSocketChannel.class)//
.option(ChannelOption.TCP_NODELAY, true)//
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("decoder",new ByteArrayDecoder());//new MessageProtocolDecoder());
socketChannel.pipeline().addLast("channelHandler",new ClientHandler()); // 处理网络IO
socketChannel.pipeline().addLast("encoder",new ByteArrayEncoder());//new MessageProtocolEncoder());
}
});
// 异步链接服务器 同步等待链接成功
ChannelFuture f = b.connect(host, port).sync();
// 等待链接关闭
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
System.out.println("client release resource...");
}
}
public static void main(String[] args) throws Exception {
new Client("127.0.0.1",8081).run();
}
}
客户端消息处理代码
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
/**
* Created by lipine on 2019-12-05.
*/
public class ClientHandler extends ChannelInboundHandlerAdapter {
// 客户端与服务端,连接成功的售后
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("active channel:" + ctx.channel());
// 发送SmartCar协议的消息
// 要发送的信息
String data = "I am client ...";
// 获得要发送信息的字节数组
byte[] content = data.getBytes();
Channel channel = ctx.channel();
channel.writeAndFlush(content);
}
// 只是读数据,没有写数据的话
// 需要自己手动的释放的消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("read channel:" + ctx.channel());
try {
// 用于获取客户端发来的数据信息
System.out.println("Client receive message:" + new String((byte[])msg));
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
}
传统HTTP服务器的实现
- 1、创建一个ServerSocket,监听并绑定一个端口
- 2、一系列客户端来请求这个端口
- 3、服务器使用Accept,获得一个来自客户端的Socket连接对象
- 4、启动一个新线程处理连接
- 4.1、读Socket,得到字节流
- 4.2、解码协议,得到Http请求对象
- 4.3、处理Http请求,得到一个结果,封装成一个HttpResponse对象
- 4.4、编码协议,将结果序列化字节流 写Socket,将字节流发给客户端
- 5、继续循环步骤 3
HTTP服务器之所以称为HTTP服务器,是因为编码解码协议是HTTP协议。如果协议是Redis协议,那它就成了Redis服务器,如果协议是WebSocket,那它就成了WebSocket服务器,等等。
使用Netty你就可以定制编解码协议,实现自己的特定协议的服务器