在写代码之前 先了解下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. 深度解密Go语言之 pprof

    目录 什么是 pprof pprof 的作用 pprof 如何使用 runtime/pprof net/http/pprof pprof 进阶 Russ Cox 实战 查找内存泄露 总结 参考资料 相 ...

  2. NOIP模拟 3

    序列 以为自己很对然后光荣T20 (路丽姐姐原谅我吧)果然是把等比数列的定义记错了,一直没发现等比数列里的项是互成倍数的 正解首先就跟据上点初步判断两项能否成为子段的开头 然后处理出可能的最小公比(用 ...

  3. ASP.NET Core 3.0 gRPC 拦截器

    目录 ASP.NET Core 3.0 使用gRPC ASP.NET Core 3.0 gRPC 双向流 ASP.NET Core 3.0 gRPC 拦截器 一. 前言 前面两篇文章给大家介绍了使用g ...

  4. 使用Typescript重构axios(一)——写在最前面

    0.系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三)- ...

  5. 易初大数据 2019年10月24日 spss笔记 王庆超

    数据文件的重置结构:横向结构(个案组),纵向结构,不符合分析方法的时候就需要重组,选定变量重组为个案,数据—重构,重构数据向导,选定变量重组为个案,将选定个案重构位变量,转置所有数据,变量组数目,一个 ...

  6. [LINQ2Dapper]最完整Dapper To Linq框架(三)---实体类关系映射

    此特性需要安装Kogel.Dapper.Mssql或者Oracle 3.06及以上版本,实体类层需要安装Kogel.Dapper.Extension 3.06及以上版本 目录 [LINQ2Dapper ...

  7. 极光推送(JPush)开篇

    Date:2019-11-11 读前思考: 极光推送是什么? 极光推送是能做什么?有什么优势? 怎么根据业务需求来实现极光推送服务呢? 简介 极光推送(JPush)是独立的第三方云推送平台,致力于为全 ...

  8. SqlServer设置特定用户操作特定表(插入、删除、更新、查询 的权限设置)

    目录 一.需求场景: 二.操作步骤: 表上右键选择[属性],选择[权限]选项卡: 点击[搜索],在弹出的框中点击[浏览],选择需要设置的用户: 在上面点击[确定]后,就可以在[权限]选项卡中看到权限列 ...

  9. ActiveMQ消息队列从入门到实践(1)—JMS的概念和JMS消息模型

    1. 面向消息的中间件 1.1 什么是MOM 面向消息的中间件,Message Oriented Middleware,简称MOM,中文简称消息中间件,利用高效可靠的消息传递机制进行平台无关的数据交流 ...

  10. 【最新发布】最新Python学习路线,值得收藏

    随着AI的发展,Python的薪资也在逐年增加,但是很多初学者会盲目乱学,连正确的学习路线都不清楚,踩很多坑,为此经过我多年开发经验以及对目前行业发展形式总结出一套最新python学习路线,帮助大家正 ...