Java网关服务-AIO(二)
Java网关服务-AIO(二)
概述
AIO的特点就是用户程序注册一个事件后就可以做其他事情,当事件被内核执行并得到结果后,我们的CompletionHandler会在I/O回调线程中被自动调用,有点类似观察者模式;因此我们的服务端会有很多个CompletionHandler
Handlers
接口定义
为了区别一个Handler是从接收缓冲区读入数据到ByteBuffer,还是将ByteBuffer中的内容写入发送缓冲区,我们定义了两个接口
InboundHandler
/**
* server端处理请求的handler
*/
public interface InboundHandler {
/**
* 从通道接收缓冲区读取数据
* @param socketChannel client的channel
* @param in 分配的byteBuffer缓冲
* @return
*/
Object read(AsynchronousSocketChannel socketChannel, ByteBuffer in);
}
OutboundHandler
/**
* 对外输出的handler
*/
public interface OutboundHandler {
/**
* 向通道写输出的数据
*
* @param socketChannel client对应的channel
* @param out 向通道输出的数据
* @return
*/
Object write(AsynchronousSocketChannel socketChannel, ByteBuffer out);
}
ChannelAcceptedHandler
/**
* accept完成时的handler
* deocder: 4 + body
* 前4个字节为一个int,值为body.length,后面紧跟body
* BEFORE DECODE (16 bytes) AFTER DECODE (12 bytes)
* +--------+----------------+ +----------------+
* | Length | Actual Content |----->| Actual Content |
* | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
* +--------+----------------+ +----------------+
* <p/>
* 无论成功还是失败,都要继续使server accept请求
* 不要再AcceptHandler中同步的读取数据
*/
public class ChannelAcceptedHandler implements CompletionHandler<AsynchronousSocketChannel, AsynchronousServerSocketChannel>, InboundHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(ChannelAcceptedHandler.class);
/**
* 仅接受localhost请求
*/
private boolean onlyAcceptLocalhost;
public ChannelAcceptedHandler(boolean onlyAcceptLocalhost) {
this.onlyAcceptLocalhost = onlyAcceptLocalhost;
}
public void completed(AsynchronousSocketChannel socketChannel, AsynchronousServerSocketChannel attachment) {
//继续接受其他客户端的连接
attachment.accept(attachment, this);
try {
InetSocketAddress inetSocketAddress = (InetSocketAddress) socketChannel.getRemoteAddress();
String host = inetSocketAddress.getAddress().getHostAddress();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("remote host:{}", host);
}
if (onlyAcceptLocalhost && !"127.0.0.1".equals(host)) {
LOGGER.warn("拒绝ip:{}的连接", host);
socketChannel.close();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
//读取前4个字节,也就是body的长度
ByteBuffer buffer = ByteBuffer.allocate(4);
read(socketChannel, buffer);
}
@Override
public Object read(AsynchronousSocketChannel socketChannel, ByteBuffer in) {
//开始监听读头部buffer
socketChannel.read(in, in, new ChannelReadHeadHandler(socketChannel));
return null;
}
public void failed(Throwable exc, AsynchronousServerSocketChannel attachment) {
attachment.accept(attachment, this);
LOGGER.error("连接错误", exc);
}
}
ChannelAcceptedHandler实现了InboundHandler,表明它是接收请求信息的CompletionHandler
onlyAcceptLocalhost
onlyAcceptLocalhost是用来进行ip过滤的,是否只接受127.0.0.1的连接,如果连接应该被拒绝,则将socketChannel.close()调,不再向下继续处理
继续接受其他客户端的连接
attachment.accept(attachment, this);
我们在AsyncServer执行了serverChannel.accept方法,为什么这里要继续执行呢?
答:执行serverChannel.accept仅注册了一次监听连接的事件,当服务端在accept完一个连接后,如果不继续注册,则服务端就无法继续接受请求了
需要非常注意的是,很多人只知道accept方法需要在每次accept成功后都要接着调用一次,却忽略了在accept失败时也需要继续调用。试想如果一个客户端在connect到server后,立即将连接close掉,而这个时候,server正准备处理这个尝试建立连接的请求,却发现已经不能用了,此时会触发failed方法,而非completed方法。网络情况是及其复杂的,这样的情况不可谓不常见。在实际测试中,我有过这样的测试用例,这也是在被坑后才注意到的。
协议
如果继续网下面讲,就需要聊到协议了。通过查看socketChannel.read方法,你会发现AIO是先声明一个ByteBuffer,让内核往里面读入对应个数的byte,也就是说,需要先确定缓冲区大小,再调用read方法,注册回调事件。
纳尼,为什么不让我有机会先知道一下总的长度呢,例如Http协议有一个请求头,可以通过请求头中的信息,知道整个请求的大小。
这也是AIO看似编码简单,但是却很麻烦的地方。
我们采用了一个极其简单的协议:int(4 byte) + realbody的协议,也就是说先读到4个字节,解析成int类型,就知道后面的realbody有多少个字节,和客户端提前约定好;
ByteBuffer buffer = ByteBuffer.allocate(4);
read(socketChannel, buffer);
先分配4个字节到buffer中,再继续注册read事件
socketChannel.read(in, in, new ChannelReadHeadHandler(socketChannel));
ChannelReadHeadHandler
ChannelReadHeadHandler将会成功读到4个字节,从而获取body的长度
/**
* 读取请求的内容,业务处理,取前4个字节,获取body的具体长度
*/
public class ChannelReadHeadHandler implements CompletionHandler<Integer, ByteBuffer>, InboundHandler {
private final static Logger LOGGER = LoggerFactory.getLogger(ChannelReadHeadHandler.class);
private AsynchronousSocketChannel channel;
private static int BODY_PAYLOAD = Constants.MB_1;
static {
String maxBodySize = System.getProperty("max_body_size");
if (maxBodySize != null && maxBodySize.trim().equals("")) {
try {
BODY_PAYLOAD = Integer.valueOf(maxBodySize);
} catch (Exception e) {
LOGGER.warn("环境变量max_body_size转换int失败:{}", maxBodySize);
}
}
if (BODY_PAYLOAD < 1) {
BODY_PAYLOAD = Constants.MB_1;
}
LOGGER.info("body-payload:{}", BODY_PAYLOAD);
}
public ChannelReadHeadHandler(AsynchronousSocketChannel channel) {
this.channel = channel;
}
public void completed(Integer result, ByteBuffer attachment) {
//如果条件成立,说明客户端主动终止了TCP套接字,这时服务端终止就可以了
if (result == -1 || result < 4) {
System.out.println("remote is close");
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
attachment.flip();
//获取实际body的长度
Integer bodyLen = attachment.getInt();
LOGGER.info("bodyLen:" + bodyLen);
if (bodyLen > BODY_PAYLOAD) {
LOGGER.warn("请求体过大,直接丢弃,currentBodyLen:{}, maxLen:{}", bodyLen, BODY_PAYLOAD);
// result.read()
releaseConnection(channel);
return;
}
//为body生成一个buffer
ByteBuffer bodyBuffer = ByteBuffer.allocate(bodyLen);
read(channel, bodyBuffer);
}
@Override
public Object read(AsynchronousSocketChannel socketChannel, ByteBuffer in) {
//开始监听读buffer
socketChannel.read(in, in, new ChannelServerHandler(socketChannel));
return null;
}
public void failed(Throwable exc, ByteBuffer attachment) {
releaseConnection(this.channel);
}
private void releaseConnection(Channel channel) {
try {
channel.close();
LOGGER.warn("server close client channle");
} catch (IOException e) {
e.printStackTrace();
}
}
}
BODY_PAYLOAD
约定一个body的最大长度,我们的服务端不可能接受无止境的body,一定需要设置一个最大长度,超过这个长度的,就直接拒绝,如1MB
长度判断
//如果条件成立,说明客户端主动终止了TCP套接字,这时服务端终止就可以了
if (result == -1 || result < 4) {
System.out.println("remote is close");
try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
return;
}
result是成功读取到的byte个数
- 如果为-1,标识没有读到任何东西,这次socket传输已经到达了end of stream,这个我们预期或者约定的有出入,直接关闭连接
- 如果<4,异常情况,server无法处理一个3个字节的长度,这也和约定不符合,关闭连接,结束
获取body长度
attachment.flip();
//获取实际body的长度
Integer bodyLen = attachment.getInt();
LOGGER.info("bodyLen:" + bodyLen);
if (bodyLen > BODY_PAYLOAD) {
LOGGER.warn("请求体过大,直接丢弃,currentBodyLen:{}, maxLen:{}", bodyLen, BODY_PAYLOAD);
releaseConnection(channel);
return;
}
ByteBuffer的常规操作,要读时先调用flip()方法,切换为读模式
attachment.getInt()从buffer中读出一个int,此处一共4个字节,恰好可以读到一个int,也就是body的长度,成功获取长度后立即判断长度是否过大
继续读body内容
socketChannel.read(in, in, new ChannelServerHandler(socketChannel));
既然已经确定了长度,那么我们可以分配指定长度大小的Buffer继续从通道里面取数据了
ChannelServerHandler
下一节将讲解具体处理请求Handler
Java网关服务-AIO(二)的更多相关文章
- Java网关服务-AIO(三)
Java网关服务-AIO(三) 概述 前两节中,我们已经获取了body的总长度,剩下的就是读出body,处理请求 ChannelServerHandler ChannelServerHandler即从 ...
- Java网关服务-AIO(一)
Java网关-AIO(一) aio:声明一个byteBuffer,异步读,读完了之后回调,相比于Future.get(),可以减少阻塞.减少线程等待,充分利用有限的线程 nio:声明一个byteBuf ...
- Java微服务(二):负载均衡、序列化、熔断
本文接着上一篇写的<Java微服务(二):服务消费者与提供者搭建>,上一篇文章主要讲述了消费者与服务者的搭建与简单的实现.其中重点需要注意配置文件中的几个坑. 本章节介绍一些零散的内容:服 ...
- Spring Cloud 网关服务 zuul 二
有一点上篇文章忘了 讲述,nacos的加载优先级别最高.服务启动优先拉去配置信息.所以上一篇服务搭建我没有讲述在nacos 中心创建的配置文件 可以看到服务端口和注册中心都在配置文件中配置化 属性信息 ...
- Java微服务(二):服务消费者与提供者搭建
本文接着上一篇写的<Java微服务(一):dubbo-admin控制台的使用>,上篇文章介绍了docker,zookeeper环境的安装,并参考dubbo官网演示了dubbo-admin控 ...
- Spring Cloud gateway 网关服务二 断言、过滤器
微服务当前这么火爆的程度,如果不能学会一种微服务框架技术.怎么能升职加薪,增加简历的筹码?spring cloud 和 Dubbo 需要单独学习.说没有时间?没有精力?要学俩个框架?而Spring C ...
- Spring Cloud 网关服务 zuul 三 动态路由
zuul动态路由 网关服务是流量的唯一入口.不能随便停服务.所以动态路由就显得尤为必要. 数据库动态路由基于事件刷新机制热修改zuul的路由属性. DiscoveryClientRouteLocato ...
- Java进阶(五十二)利用LOG4J生成服务日志
Java进阶(五十二)利用LOG4J生成服务日志 前言 由于论文写作需求,需要进行流程挖掘.前提是需要有真实的事件日志数据.真实的事件日志数据可以用来发现.监控和提升业务流程. 为了获得真实的事件日志 ...
- 服务网关zuul之二:过滤器--请求过滤执行过程(源码分析)
Zuul的核心是一系列的过滤器,这些过滤器可以完成以下功能: 身份认证与安全:识别每个资源的验证要求,并拒绝那些与要求不符的请求. 审查与监控:在边缘位置追踪有意义的数据和统计结果,从而带来精确的生成 ...
随机推荐
- 网络爬虫:利用selenium,pyquery库抓取并处理京东上的图片并存储到使用mongdb数据库进行存储
一,环境的搭建已经简单的工具介绍 1.selenium,一个用于Web应用程序测试的工具.其特点是直接运行在浏览器中,就像真正的用户在操作一样.新版本selenium2集成了 Selenium 1.0 ...
- 快速上手开发——JFinal配置(全步骤图文解析)
摘要: 因为发现官网上只有Eclipse的配置文档,就写了这篇基于IDEA+maven的配置流程.本文使用安装了maven插件的IDEA进行配置,为了照顾IDEA新手,几乎每个步骤都截了图. 环境说明 ...
- 一文了解Zookeeper
Zookeeper是Apache开源的一个分布式框架,它主要为分布式应用提供协调服务. Zookeeper主要负责存储和管理大家都关心的数据,一旦这些数据的状态发生变化,Zookeeper就会通知那些 ...
- 国际化的实现i18n--错误码国际化以及在springboot项目中使用
国际化 ,英文叫 internationalization 单词太长 ,又被简称为 i18n(取头取尾中间有18个字母); 主要涉及3个类: Locale用来设置定制的语言和国家代码 Resource ...
- getchar()与putchar()
getchar() 用于读取用户从键盘输入的单个字符,它有一个整型的返回值,当发生读取错误的时候,返回整型值-1,当读取正确的时候,它会返回用户从键盘输的第一个字符的ASCII码, 当程序调用getc ...
- c语言的变量,常量及作用域等
1.const定义常量 在C语言中,const可以用来定义的一个常量,在变量名前加上const即可. int const a: 定义了一个a的整数常量,且a的值不能被修改.如果要修改a的值,有以下两种 ...
- arduino中驱动 步进电机
参考:https://www.arduino.cn/thread-75936-1-1.html 知识点: 步进电机是一种将电脉冲转化为角位移的执行机构.通俗一点讲:当步进驱动器接收到一个脉冲信号 ...
- matlab中figure创建图窗窗口
来源:https://ww2.mathworks.cn/help/matlab/ref/figure.html?searchHighlight=figure&s_tid=doc_srchtit ...
- Java 客户端操作 FastDFS 实现文件上传下载替换删除
FastDFS 的作者余庆先生已经为我们开发好了 Java 对应的 SDK.这里需要解释一下:作者余庆并没有及时更新最新的 Java SDK 至 Maven 中央仓库,目前中央仓库最新版仍旧是 1.2 ...
- 【LGR-070】洛谷 3 月月赛-官方题解
本次免费为大家提供[LGR-070]洛谷 3 月月赛的官方题解,点个赞再走呗! 代码就不上了,大家可以到别的博客上去找找!希望这篇博客能对你有所帮助!