这是一个简单的netty demo,这里不讲原理,这个demo是根据这位大神的文章摸索出来,感谢大神的无私分享 https://www.cnblogs.com/TomSnail/
首先放一张netty的主从模式的线程模型图,也是出自大神的文章
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190624171202732.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpZWxpbnpl,size_16,color_FFFFFF,t_70)
图中又有个NioEventLoop。
Acceptor中的NioEventLoop用于接收TCP连接,初始化参数。
I/O线程池中的NioEventLoop异步读取通信对端的数据,发送读事件到channel
类似与java nio的selector。netty的主从模式就是多个selector 客户端接入线程,然后将接入的链接在分发给第二个NioEventLoop负责从端口读写数据。单线程模式就是一个selector 。
下面上代码,这是一个netty 服务端的 spring boot demo
@Component
public class NettyServer implements ApplicationRunner {
private static Logger logger = LoggerFactory.getLogger(NettyServer.class);
//初始化通道
@Autowired
private NettyServerChannelInitializer nettyServerChannelInitializer;
@Value("${netty.server.port}")
private Integer port;
@Value("${netty.server.ip}")
private String ip;
@Value("${netty.server.bossGroup}")
private Integer bossGroup;
@Value("${netty.server.workerGroup}")
private Integer workerGroup;
private Integer bufferSize = 500;
/**
* ApplicationRunner 启动类
* 重写run 可随应用一起启动
* */
@Override
public void run(ApplicationArguments args){
start();
}
/**
* netty start
* */
public void start() {
InetSocketAddress address = new InetSocketAddress(ip,port);
//Netty内部都是通过线程在处理各种数据,EventLoopGroup就是用来管理调度他们的,注册Channel,管理他们的生命周期。
//NioEventLoopGroup是一个处理I/O操作的多线程事件循环
//bossGroup仅接收客户端连接,不做复杂的逻辑处理,为了尽可能减少资源的占用,取值越小越好
//就是线程模型中的主Reactor,只负责接入链接,再分发给子Reactor进行读写
EventLoopGroup bossGroup = new NioEventLoopGroup(this.bossGroup);
//workerGroup作为worker,处理boss接收的连接的流量和将接收的连接注册进入这个worker,负责读写
//就是子Reactor
EventLoopGroup workerGroup = new NioEventLoopGroup(this.workerGroup);
try {
//ServerBootstrap负责建立服务端
ServerBootstrap bootstrap = new ServerBootstrap()
// 绑定线程池
.group(bossGroup, workerGroup)
//指定NioServerSocketChannel类生成channel来接收连接
//指定为服务端
.channel(NioServerSocketChannel.class)
//绑定地址
.localAddress(address)
//ChannelInitializer用于配置一个新的Channel
//用于向你的Channel当中添加ChannelInboundHandler的实现
.childHandler(nettyServerChannelInitializer)
//BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
//Option是为了NioServerSocketChannel设置的,用来接收传入连接的
.option(ChannelOption.SO_BACKLOG, bufferSize)
//FixedRecvByteBufAllocator:固定长度的接收缓冲区分配器,由它分配的ByteBuf长度都是固定大小的,并不会根据实际数据报的大小动态收缩。但是,如果容量不足,支持动态扩展。动态扩展是Netty ByteBuf的一项基本功能,与ByteBuf分配器的实现没有关系;
//AdaptiveRecvByteBufAllocator:容量动态调整的接收缓冲区分配器,它会根据之前Channel接收到的数据报大小进行计算,如果连续填充满接收缓冲区的可写空间,则动态扩展容量。如果连续2次接收到的数据报都小于指定值,则收缩当前的容量,以节约内存。
.option(ChannelOption.RCVBUF_ALLOCATOR, AdaptiveRecvByteBufAllocator.DEFAULT)
//是否启用心跳保活机制。在双方TCP套接字建立连接后(即都进入ESTABLISHED状态)并且在两个小时左右上层没有任何数据传输的情况下,这套机制才会被激活。
//childOption是用来给父级ServerChannel之下的Channels设置参数的
.childOption(ChannelOption.SO_KEEPALIVE, true);
// 绑定端口,开始接收进来的链接
ChannelFuture future = bootstrap.bind(address).sync();
logger.info("=========================netty服务器启动,端口:"+address.getPort()+"=========================");
//closeFuture()当Channel关闭时返回一个ChannelFuture,用于链路检测
//sync()会同步等待连接操作结果,用户线程将在此wait(),直到连接操作完成之后,线程被notify(),用户代码继续执行
future.channel().closeFuture().sync();
} catch (Exception e) {
logger.error("netty服务端启动异常:" + e.getMessage());
throw new RuntimeException("netty服务端启动异常:" + e.getMessage());
} finally {
// 释放资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
/**
自定义通道初始化
*/
public class NettyServerChannelInitializer extends ChannelInitializer {
@Autowired
private NettyServerHandler nettyServerHandler;
/**
* 初始化channel 设置调用链
* socket编程一般的处理流程如下
* 接收: 接收数据>>解析数据(解码)>>业务处理>>回写数据(编码)
* 发送: 与接收相反
* 所以,这个类主要就是设置channel 的处理链路
* */
@Override
protected void initChannel(SocketChannel channel) throws Exception {
//DelimiterBasedFrameDecoder 根据包尾解决粘包问题,半包会丢弃
//这个是处理粘包 或者半包 的问题
ByteBuf delimiter = Unpooled.copiedBuffer(DefaultConstants.TAIL_DATA);
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(DefaultConstants.MAX_LENGTH,false,delimiter));
//编码
//channel.pipeline().addLast(new MessageEncoder());
//解码
//channel.pipeline().addLast(new MessageDecoder());
//心跳设置
channel.pipeline().addLast("idleState", new IdleStateHandler(DefaultConstants.READER_IDLE, DefaultConstants.WRITER_IDLE, DefaultConstants.ALL_IDLE));
channel.pipeline().addLast(nettyServerHandler);
}
}
/**
-
此类处理读写操作
-
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {/**
-
有客户端接入时触发这个方法
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
log.info(“客户端【” + clientIp + “】发起了一个链接”);
}
-
/**
* 有客户端中止链接时候触发
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) {
try {
//远程地址信息
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
//客户端id
String clientIp = insocket.getAddress().getHostAddress();
log.info("客户端链接中断[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
}catch (Exception e){
log.error("客户端链接中断处理异常!!" + e);
}
}
/**
* @DESCRIPTION: 有客户端发消息会触发此函数
* @return: void
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try {
//数据包
byte data[] = (byte[])msg;
log.info("开始处理消息,消息为:" + Arrays.toString(data));
}catch (Exception e){
log.error(Thread.currentThread().getName() + "消息处理异常:" + e);
}
}
/**
* @DESCRIPTION: 服务端给客户端发送消息
* @return: void
*/
public void channelWrite(ChannelId channelId, Object msg) {
/**
* 数据回写
* */
}
/**
* 检测指定时间内无读写操作时触发,此设置在pipeline链路中设置,其实就是心跳设置
* */
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
// //远程地址信息
// InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
// //客户端id
// String clientIp = insocket.getAddress().getHostAddress();
// //通道id
// ChannelId channelId = ctx.channel().id();
// if (evt instanceof IdleStateEvent) {
// IdleStateEvent event = (IdleStateEvent) evt;
// if (event.state() == IdleState.READER_IDLE) {
// log.info(“未在指定时间内接收到客户端【”+clientIp+"】的数据,关闭此链接!");
// ctx.disconnect();
// } else if (event.state() == IdleState.WRITER_IDLE) {
log.info(“Client: " + channelId + " WRITER_IDLE 写超时”);
ctx.disconnect();
// } else if (event.state() == IdleState.ALL_IDLE) {
log.info(“Client: " + channelId + " ALL_IDLE 总超时”);
ctx.disconnect();
// }
// }
}
/**
* 再通道出现异常时候触发
* @return: void
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
//远程地址信息
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
//客户端id
String clientIp = insocket.getAddress().getHostAddress();
ctx.close();
log.info("通道号【" + ctx.channel().id() + "】" + "发生了错误,此连接被关闭!客户端ip=【" + clientIp + "】");
log.info("通道号【" + ctx.channel().id() + "】异常信息为:" + cause.getMessage());
}
}
大概就这么多,理解有误的地方,请指正。