简介
Netty 封装了 JDK 的 NIO,让你用得更爽,你不用再写一大堆复杂的代码了。 用官方正式的话来说就是:Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。
- 使用 JDK 自带的NIO需要了解太多的概念,编程复杂,一不小心 bug 横飞
- Netty 底层 IO 模型随意切换,而这一切只需要做微小的改动,改改参数,Netty可以直接从 NIO 模型变身为 IO 模型
- Netty 自带的拆包解包,异常检测等机制让你从NIO的繁重细节中脱离出来,让你只需要关心业务逻辑
- Netty 解决了 JDK 的很多包括空轮询在内的 Bug
- Netty 底层对线程,selector 做了很多细小的优化,精心设计的 reactor 线程模型做到非常高效的并发处理
- 自带各种协议栈让你处理任何一种通用协议都几乎不用亲自动手
- Netty 社区活跃,遇到问题随时邮件列表或者 issue
- Netty 已经历各大 RPC 框架,消息中间件,分布式通信中间件线上的广泛验证,健壮性无比强大
下面是netty的服务端启动流程
包含了端口占用后自动递增尝试端口
/**
* @Author: xuxu
* @Date: 2019/12/26 9:08
*/
public class NettyServer {
public static void main(String[] args) {
//表示监听端口,接收新连接的线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//表示处理每一条连接的数据读写的线程组
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
//服务端启动引导类
ServerBootstrap serverBootstrap = new ServerBootstrap();
//引导类配置线程组
serverBootstrap.group(bossGroup, workerGroup);
//指定通道的io模型 nio 或者 bio 使用netty当然一般使用nio
serverBootstrap.channel(NioServerSocketChannel.class);
//定义每条连接的数据读写,业务逻辑
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
System.out.println("处理当前连接数据");
}
});
//===给服务端channel设置一些属性
//表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,
//服务器处理创建新连接较慢,可以适当调大这个参数
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
//===给每条连接设置一些TCP底层相关的属性
//表示是否开启TCP底层心跳机制,true为开启
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
//表示是否开启Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,
//有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启
serverBootstrap.childOption(ChannelOption.TCP_NODELAY, false);
//自动递增绑定端口 从8000开始如果8000连接失败则尝试8001...
autoBind(serverBootstrap, 8000);
//-----------------其他不常用配置---------------
//定义服务端启动中逻辑一般很少用
serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {
@Override
protected void initChannel(NioServerSocketChannel nioServerSocketChannel) throws Exception {
System.out.println("服务端启动中逻辑");
}
});
//给服务端channel指定属性 相当于map 很少用
serverBootstrap.attr(AttributeKey.newInstance("serverName"),"testServer");
//和上面类似 给单条连接指定属性
serverBootstrap.childAttr(AttributeKey.newInstance("childName"), "childName");
}
//方法1判断端口是否绑定成功 异步判断
private static void bind(ServerBootstrap serverBootstrap){
serverBootstrap.bind(8000).addListener(new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) throws Exception {
if(future.isSuccess()){
System.out.println("绑定成功");
}else{
System.out.println("绑定失败");
}
}
});
}
//方法2自动递增绑定端口号,如果端口被占用则继续找下一个端口,下一个端口为在上一个端口上加1
private static void autoBind(final ServerBootstrap serverBootstrap, final int port){
serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) throws Exception {
if(future.isSuccess()){
System.out.println("当前端口号"+port+"绑定成功");
}else{
System.out.println("当前端口号"+port+"绑定失败");
autoBind(serverBootstrap, port+1);
}
}
});
}
}
创建一个引导类,然后给他指定线程模型,IO模型,连接读写处理逻辑,绑定端口之后,服务端就启动起来了.
其中bossGroup可以理解成老板对外谈义务接单(接收新的客户端连接).
workerGroup代表工人,处理生产某个用户发来的订单(客户端发送消息,然后读取并响应逻辑).
如果端口被占用会自动尝试绑定下一个端口
netty客户端代码
包含了常见流程设置,以及连接失败后指定次数重连
/**
* @Author: xuxu
* @Date: 2019/12/26 10:19
*/
public class NettyClient {
//设置连接失败最大尝试次数
private static final int MAX_RETRY=5;
public static void main(String[] args) {
NioEventLoopGroup clientGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
//指定线程模型
bootstrap.group(clientGroup);
//指定IO类型为nio
bootstrap.channel(NioSocketChannel.class);
//处理IO逻辑
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
System.out.println("处理IO逻辑");
}
});
//给连接设置一些 TCP 底层相关的属性
//连接的超时时间,超过这个时间还是建立不上的话则代表连接失败
bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
//是否开启 TCP 底层心跳机制,true 为开启
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
//是否开始 Nagle 算法,true 表示关闭,false 表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,
// 就设置为 true 关闭,如果需要减少发送次数减少网络交互,就设置为 false 开启
bootstrap.option(ChannelOption.TCP_NODELAY, true);
//连接
connect(bootstrap, "127.0.0.1",8000,MAX_RETRY);
}
private static void connect(final Bootstrap bootstrap, final String host, final int port, final int retry){
//因为内部类使用外部变量必须用final修饰,无法改变局部变量retry值,所以采用lambda表达式写法
/*bootstrap.connect("127.0.0.1", 8000).addListener(new GenericFutureListener<Future<? super Void>>() {
public void operationComplete(Future<? super Void> future) throws Exception {
if(future.isSuccess()){
System.out.println("连接成功");
}else if(retry==0){
System.out.println("重连次数已经耗尽");
}else{
System.out.println("连接失败");
//失败后递归调用重连
}
}
});*/
bootstrap.connect(host, port).addListener(future->{
if(future.isSuccess()){
System.out.println("连接成功!");
}else if(retry==0){
System.out.println("重连已达最大次数");
}else{
//当前尝试重连次数
int num = MAX_RETRY-retry+1;
//本次重试时间间隔(指数次 2,4,8...)
int interval = 1<<num;
new Date();
System.out.println("连接失败,第"+num+"次尝试重连,时间:"+(new Date()));
//group组定时任务
bootstrap.config().group().schedule(()->{connect(bootstrap, host, port,retry-1);}, interval, TimeUnit.SECONDS);
}
});
}
}
创建一个引导类,然后给他指定线程模型,IO 模型,连接读写处理逻辑,连接上特定主机和端口,客户端就启动起来了
connect
方法是异步的,我们可以通过这个异步回调机制来实现指数退避重连逻辑。
如果连接失败如下