在写代码之前 先了解下Reactor模型:

Reactor单线程模型就是指所有的IO操作都在同一个NIO线程上面完成的,也就是IO处理线程是单线程的。NIO线程的职责是:

(1)作为NIO服务端,接收客户端的TCP连接;

(2)作为NIO客户端,向服务端发起TCP连接;

(3)读取通信对端的请求或者应答消息;

(4)向通信对端发送消息请求或者应答消息。

模型图如下:

Reactor模式使用的是同步非阻塞IO(NIO),所有的IO操作都不会导致阻塞,理论上一个线程可以独立的处理所有的IO操作(selector会主动去轮询哪些IO操作就绪)。从架构层次看,一个NIO线程确实可以完成其承担的职责,比如上图的Acceptor类接收客户端的TCP请求消息,当链路建立成功之后,通过Dispatch将对应的ByteBuffer转发到指定的handler上,进行消息的处理。

对于一些小容量的应用场景下,可以使用单线程模型,但是对于高负载、大并发的应用场景却不适合,主要原因如下: 

(1)一个NIO线程处理成千上万的链路,性能无法支撑,即使CPU的负荷达到100%;

(2)当NIO线程负载过重,处理性能就会变慢,导致大量客户端连接超时然后重发请求,导致更多堆积未处理的请求,成为性能瓶颈。

(3)可靠性低,只有一个NIO线程,万一线程假死或则进入死循环,就完全不可用了,这是不能接受的。

Reactor多线程模型与单线程模型最大的区别在于,IO处理线程不再是一个线程,而是一组NIO处理线程。

模型图:

Reactor多线程模型的特点如下: 

(1)有一个专门的NIO线程—-Acceptor线程用于监听服务端,接收客户端的TCP连接请求。

(2)网络IO操作—-读写等操作由一个专门的线程池负责,线程池可以使用JDK标准的线程池实现,包含一个任务队列和N个可用的线程,这些NIO线程就负责读取、解码、编码、发送。

(3)一个NIO线程可以同时处理N个链路,但是一个链路只对应一个NIO线程。

(4)虽然采用了多线程,虽然仍旧是单个selector线程,但是请求的处理大头交给了线程池异步执行。

Reactor多线程模型可以满足绝大多数的场景,除了一些个别的特殊场景:比如一个NIO线程负责处理客户所有的连接请求,但是如果连接请求中包含认证的需求(安全认证),在百万级别的场景下,就存在性能问题了,因为认证本身就要消耗CPU,为了解决这种情景下的性能问题,产生了第三种线程模型:Reactor主从线程模型。

主从Reactor线程模型的特点是:服务端用于接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO的线程池。Acceptor接收到客户端TCP连接请求并处理完成后(可能包含接入认证),再将新创建的SocketChannel注册到IO线程池(sub reactor)的某个IO处理线程上并处理编解码和读写工作。Acceptor线程池仅负责客户端的连接与认证,一旦链路连接成功,就将链路注册到后端的sub Reactor的IO线程池中。 利用主从Reactor模型可以解决服务端监听线程无法有效处理所有客户连接的性能不足问题,这也是netty推荐使用的线程模型。

模型图:

TimeServer类(服务端)

public class TimeServer {

    public void bind(int port)throws Exception{
/**NioEventLoopGroup 是线程组 它包含了一组NIO线程 专门处理网络事件 实际上它们就是Reactor线程组
*Reactor线程是多面手,负责多路分离套接字,Accept新连接,并分派请求到处理器链中。
*/ /**
*用于服务端接受客户端的连接
*/
EventLoopGroup bossGroup = new NioEventLoopGroup();
/**
* 用于进行SocketChannel的网络读写
*/
EventLoopGroup workerGroup = new NioEventLoopGroup(); try{
/**
* 用于启动NIO服务端的辅助启动类 降低服务端的开发难度
*/
ServerBootstrap b = new ServerBootstrap();
/**
*1:将两个NIO线程组当作参数传到ServerBootstrap中
* 2:设置创建的Channel 为NioServerSocketChannel 它对应于JDK NIO类库中的ServerSocketChannel
* 3:配置NioServerSocketChannel的Tcp参数 BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
* 4:绑定I/O事件的处理类ChildChannelHandler 它的作用模式类似于Reactor模式中的handler类 主要处理网络I/O事件 列如记录日志 对消息进行编解码等
*/ b.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024).childHandler(new
ChildChannelHandler());
/**
* 服务端启动辅助类配置完成后 调用它的bind方法绑定监听端口 随后调用它的同步阻塞方法sync等待绑定操作完成 完成后Netty会返回一个ChannelFuture 类似于jdk中
* 的java.util.concurrent.Future包 主要用于异步操作的通知回调
*/
ChannelFuture f = b.bind(port).sync();
/**
* 进行阻塞 等待服务端链路关闭之后 main函数才退出
*/
f.channel().closeFuture().sync();
}finally {
/**
* 优雅退出 相关资源
*/
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} } private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch){
ch.pipeline().addLast(new TimeServerHandler());
}
} public static void main(String[] args) throws Exception {
int port = 8080;
if(args!=null&&args.length>0){
try {
port = Integer.valueOf(args[0]);
}catch (NumberFormatException e){
e.printStackTrace();
}
}
new TimeServer().bind(port);
} }

TimeServerHandler类(服务端处理类)

public class TimeServerHandler extends ChannelInboundHandlerAdapter {
/**
* TimeServerHandler继承ChannelInboundHandlerAdapter 用于对网络事件进行读写操作 通常我们只关注channelRead方法和exceptionCaught方法
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/**
* 做类型转换 将msg转化为netty的ByteBuf对象
*/
ByteBuf buf = (ByteBuf) msg;
/**
* 获取缓冲区可读的字节数 根据可读字节数创建byte数组
*/
byte[] req = new byte[buf.readableBytes()];
/**
* 将缓冲区的字节数复制到新建的byte数组
*/
buf.readBytes(req);
/**
*对请求消息进行判断 如果是"QUERY TIME ORDER" 则创建应答消息 通过ChannelHandlerContext的write方法异步发送应答消息给客户端
*/
String body = new String(req,"UTF-8");
System.out.println("The time server receive order: "+body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)?new Date(
System.currentTimeMillis()).toString():"BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
/**
* 将消息发送队列中的消息写入到SocketChannel中发送给对方 通过调用write方法将消息发送到缓冲数组中 再调用flush方法将
* 缓冲区的消息全部写入SocketChannel
*/
ctx.flush(); } /**
* 发生异常时关闭资源 打印异常信息
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

TimeClient类(客户端)

public class TimeClient {

    public void connect(int port,String host){
/**
* 创建客户端处理的I/O读写的NioEventLoopGroup线程组
*/
EventLoopGroup group = new NioEventLoopGroup();
try{
/**
* 创建客户端辅助启动类
*/
Bootstrap b = new Bootstrap();
/**
* 配置
*/
b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY,true).handler(
/**
* 再初始化化的时候将它的ChannelHandler设置到ChannelPipeline中 用于处理网络I/O事件
*/
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch){
ch.pipeline().addLast(new TimeClientHandler());
}
}
);
/**
* 发起异步连接操作
*/
ChannelFuture f = b.connect(host,port);
/**
* 等待客户端链路关闭
*/
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
} }
public static void main(String[] args){ int port = 8080;
if(args!=null&&args.length>0){
try {
port=Integer.valueOf(args[0]);
}catch (NumberFormatException e){
e.printStackTrace();
}
}
new TimeClient().connect(port,"127.0.0.1");
} }

TimeClientHandler(客户端处理类)

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private final ByteBuf fristMessage; public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
fristMessage = Unpooled.buffer(req.length);
fristMessage.writeBytes(req);
} /**
* 当客户端和服务端的TCP链路建立成功后 Netty的NIO线程会调用channelActive方法 发送查询事件的指令给服务端 调用writeAndFlush方法
* 将请求发送给服务端
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(fristMessage);
} /**
* 党服务端返回应答消息 channelRead 方法被调用
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/**
* 读取并打印应答消息
*/
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req,"UTF-8");
System.out.println("Now is:"+body); } /**
* 发生异常 释放客户端资源
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}

运行结果:

github项目地址:https://github.com/INGUCoder/learning/tree/master/Netty%E5%AD%A6%E4%B9%A0

来源:华为云社区  作者:INGUCoder

基于netty4.x开发时间服务器的更多相关文章

  1. SYN2306型 北斗串口时间服务器

    SYN2306型  北斗串口时间服务器  北斗授时设备北斗时钟同步系统使用说明视频链接: http://www.syn029.com/h-pd-108-0_310_36_-1.html 请将此链接复制 ...

  2. Apache Solr采用Java开发、基于Lucene的全文搜索服务器

    http://docs.spring.io/spring-data/solr/ 首先介绍一下solr: Apache Solr (读音: SOLer) 是一个开源.高性能.采用Java开发.基于Luc ...

  3. 【原创】NIO框架入门(一):服务端基于Netty4的UDP双向通信Demo演示

    申明:本文由作者基于日常实践整理,希望对初次接触MINA.Netty的人有所启发.如需与作者交流,见文签名,互相学习. 学习交流 更多学习资料:点此进入 推荐 移动端即时通讯交流: 215891622 ...

  4. Comet:基于 HTTP 长连接的“服务器推”技术

    “服务器推”技术的应用 请访问 Ajax 技术资源中心,这是有关 Ajax 编程模型信息的一站式中心,包括很多文档.教程.论坛.blog.wiki 和新闻.任何 Ajax 的新信息都能在这里找到. c ...

  5. 转载:Comet:基于 HTTP 长连接的“服务器推”技术

    转自:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无须客 ...

  6. [转载] Comet:基于 HTTP 长连接的“服务器推”技术

    转载自http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工 ...

  7. Comet:基于 HTTP 长连接的“服务器推”技术(转载)

    “服务器推”技术的应用 传统模式的 Web 系统以客户端发出请求.服务器端响应的方式工作.这种方式并不能满足很多现实应用的需求,譬如: 监控系统:后台硬件热插拔.LED.温度.电压发生变化: 即时通信 ...

  8. 基于Java语言开发jt808、jt809技术文章精华索引

    很多技术开发人员喜欢追逐最新的技术,如Node.js, go等语言,这些语言只是解决了某一个方面,如只是擅长异步高并发等等,却在企业管理后台开发方面提供的支持非常不够,造成项目团队技术选项失败,开发后 ...

  9. 【转】Comet:基于 HTTP 长连接的“服务器推”技术

    原文链接:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/ 很多应用譬如监控.即时通信.即时报价系统都需要将后台发生的变化实时传送到客户端而无 ...

随机推荐

  1. 「考试」weight

    正解是树剖. 首先Kru求最小生成树. 然后分别考虑树边和非树边的答案. 首先是非树边,非树边链接的两个点在MST上能够构成一条链. 这条链上最大的那条边-1就是这条边的答案. 为什么. 模拟Kru的 ...

  2. Oracle“ORA-00979:不是GROUP BY 表达式”解决方式

    今天在工作中碰到一个问题,用group by 语句进行分组时出现ORA-00979错误. 代码如下: select R.ORDER_NO, R.PRODUCT_CODE, R.REGION_NO, R ...

  3. 欧拉路&&欧拉回路

    T1是欧拉路板子,但我不会,直接爆炸.. 这玩意就是个dfs,但我以前一直以为欧拉路只能$O(nm)$求 今天才知道可以$O(n+m)$ 欧拉路判定: 无向:起点终点为奇度点,其余偶度 有向:起点终点 ...

  4. P3128 [USACO15DEC]最大流

    秒切树上查分....(最近一次集训理解的东西) 但是,我敲了半小时才切掉这道题.... 我一直迷在了“边差分”和“点差分”的区别上. 所以,先说一下此题,再说一下区别. 首先,想到差分很容易. 然后, ...

  5. Opencv-Python项目(1) | 基于meanshiftT算法的运动目标跟踪技术学习

    目标跟踪(object tracking)就是在连续的视频序列中,建立所要跟踪物体的位置关系,得到物体完整的运动轨迹. 目标跟踪分为单目标跟踪和多目标跟踪.本文如无特别指出,均指单目标跟踪. 通常的做 ...

  6. Abp vNext 自定义 Ef Core 仓储引发异常

    问题 在使用自定义 Ef Core 仓储和 ABP vNext 注入的默认仓储时,通过两个 Repository 进行 Join 操作,提示 Cannot use multiple DbContext ...

  7. Win7安装pyenchant

    pip3 install pyenchant==1.6.6 单纯的 pip3 install pyenchant报错

  8. 深入理解计算机系统 第三章 程序的机器级表示 part2

    这周由于时间和精力有限,只读一小节:3.4.4  压入和弹出栈数据 栈是一种特殊的数据结构,遵循“后进先出”的原则,可以用数组实现,总是从数组的一端插入和删除元素,这一端被称为栈顶. 栈有两个常用指令 ...

  9. 深入理解计算机系统 第二章 信息的表示和处理 part1

    欣哥划的重点: 第二章比较难,建议至少掌握下面几个知识点: 1. 字节顺序 : 大端和小端 2. 运行 图2-24, 图2-25程序 show-bytes.c 观察结果,看看有什么问题 3. 理解布尔 ...

  10. 生信 - 从repeatmasker传送门过来的 blast

    以前有的是非完整时间写的博客,抽时间需要统一整理一下. 今天在重新装repeatmasker. 整个过程是这样的,有关联的事情有两个. 1. 装repeatmasker需要各种Prerequisite ...