我们从名字上就能看出这是一个NIO思想为基础的IO框架,X是指这个框架可以有多种实现,我们可以从代码库 https://github.com/xnio 中发现一个项目xnio-native,里面有用C实现的nio层,就能体会到这个X的含义,可以直接基于操作系统C库。目前在Xnio中默认的实现是nio-impl,也就是JDK的NIO。我们可以认为xnio是在JDK的NIO之上,进行了扩展,融入了一些JBoss开发人员对于并发访问异步通信的理解和思考,总结出一套API,并用基于JDK NIO进行实现。

要学习XNIO,必须得有JDK NIO的基础知识,本文假设读者已经学习过NIO,如果还没有可以阅读参考书籍[1],[2]。另外对Netty有所了解的话,就会融会贯通,比较容易的理解XNIO的基本原理,因为两个项目有很多相似之处。

XNIO有两个重要的概念:
1. Channel,是传输管道的抽象概念,在NIO的Channel上进行的扩展加强,使用ChannelListener API进行事件通知。在创建Channel时,就赋予IO线程,用于执行所有的ChannelListener回调方法。
2. 区分IO线程和工作线程,创建一个工作线程池可以用来执行阻塞任务。一般情况下,非阻塞的Handler由IO线程执行,而阻塞任务比如Servlet则被调度到工作线程池执行。这样就很好的区分了阻塞和非阻塞的两种情形。

我们知道NIO的基本要求是不阻塞当前线程的执行,对于非阻塞请求的结果,可以用两种方式获得:一种是对于请求很快返回一个引用(如JDK中Future,XNIO中称为IoFuture,其中很多方法是类似的),过一段时间再查询结果;还有一种是当结果就绪时,调用事先注册的回调方法来通知(如NIO2的CompletionHandler,XNIO的ChannelListener)。显而易见后者效率更高一些,避免了数据未就绪情景下的无用处理过程。但JDK7之前无法将函数作为方法参数,所以只能用Java的匿名内部类来模拟函数式方法,造成代码嵌套层次过多,难以理解和维护,所以Netty和XNIO这样的框架通过调度方法调用过程,简化了编程工作。

XNIO和Netty的最主要的一个区别是,XNIO继承重用了JDK NIO的ByteBuffer类,而不像Netty另起炉灶,完全重建自己的ByteBuf体系。我们知道NIO的ByteBuffer使用时有个状态切换的过程,读和写要显式的通过调用slice, reset等方法切换,就和unix使用vi编辑器编辑和处理文本需要用'i'和Esc切换状态类似。Netty通过读写指针索引值移除了这个“不便操作”,但XNIO保留了和JDK NIO一致的做法。

无论NIO还是Netty,都有heap buffer和direct buffer的概念,前者可以认为是byte数组的封装,缓冲区存放在堆上,后者可以直接通过调用操作系统的系统调用在内存上分配缓冲,这样在一些IO操作时,比如从网卡上读出大量数据,再写到硬盘文件中,就不必拷贝数据到应用层,直接在操作系统内核或者驱动上进行数据复制。ByteBuffer的管理和应用是NIO最核心的思想,开发人员应该根据应用类型,对其反复调优,做到占用资源最少和效率最大。

XNIO和Netty都对ByteBuffer进行池化管理,简单来说就是开发者在程序开始时就计划好读写缓存区大小,统一分配好放到池中,Xnio中有Pool和Pooled接口用来管理池化缓存区。开发过高并发应用就知道,JVM GC经常出现并难以控制是很头疼的问题。我们通常在接收网络数据时,往往简单的new出一块数据区,填充,解析,使用,最后丢弃,这种方法随着大量的数据读入,必然造成GC反复出现。重用缓存区就可以在这个方面解决一部分问题。

和Netty的ChannelHandler不同,XNIO对应的ChannelListener只有一个方法handleEvent(),也就意味着所有的事件都要经由这个方法。在实际实行过程中,会进行若干状态机的转变,比如在服务器端,开始时accept状态就绪,当连接建立后转变为可读或者可写状态。请参见下面的例子。

在一些情况下,阻塞的IO调用也是很有用的,比如事务过程中。XNIO也提供了阻塞方法awaitReadable()和awaitWritable()。

利用Stream channel,可以在数据源头和目的地之间直接读写数据,有一种zero-copy(零拷贝)的方式,即读写过程使用同一块缓存区,这样就不必进行数据拷贝移动过程。XNIO通过封装NIO FileChannel中的方法transferTo和transferFrom来实现。

还有一种Messgae channel,用来传输帧数据,因为有些报文格式是固定长度或者按照某种已知帧式格式定义的,缓冲区的长度可以按照报文帧来定义,当数据填满后就及时发送,减少了数据长度的计算工作,代码逻辑简洁很多,所以这种channel用于传递'消息',websocket就是这样的。

阅读XNIO时,有几个词出现频率很高,Source表示信息源头,Sink是信息目的地,Conduit是源头到目的地管道的抽象。

前面提过,XNIO中有两类线程:
WORKER_IO_THREADS, IO thread处理非阻塞任务,要保证不做阻塞操作,因为很多连接同时用到这类线程,类似于nodejs中的loop,这个线程只要有任务就去执行,实际配置时每个CPU一个线程比较好。
WORKER_TASK_CORE_THREADS,用于执行阻塞任务,从线程池中获得,任务完成后返回到线程池中。因为不同应用对应的服务器负载不同,所以不易给出具体数值,一般建议每个CPU core设置10个。

代码分析,摘自
https://github.com/ecki/xnio-samples/blob/master/src/main/java/org/xnio/samples/SimpleEchoServer.java

服务器:

  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import org.xnio.ChannelListener;
  5. import org.xnio.IoUtils;
  6. import org.xnio.OptionMap;
  7. import org.xnio.Xnio;
  8. import org.xnio.XnioWorker;
  9. import org.xnio.channels.AcceptingChannel;
  10. import org.xnio.channels.Channels;
  11. import org.xnio.channels.ConnectedStreamChannel;
  12. public final class SimpleEchoServer {
  13. public static void main(String[] args) throws Exception {
  14. // 定义读数据listener
  15. final ChannelListener<ConnectedStreamChannel> readListener =
  16. new ChannelListener<ConnectedStreamChannel>() {
  17. public void handleEvent(ConnectedStreamChannel channel) {
  18. //分配缓冲
  19. final ByteBuffer buffer = ByteBuffer.allocate(512);
  20. int res;
  21. try {
  22. while ((res = channel.read(buffer)) > 0) {
  23. //切换到写的状态并用阻塞的方式写回
  24. buffer.flip();
  25. Channels.writeBlocking(channel, buffer);
  26. }
  27. // 保证全部送出
  28. Channels.flushBlocking(channel);
  29. if (res == -1) {
  30. channel.close();
  31. } else {
  32. channel.resumeReads();
  33. }
  34. } catch (IOException e) {
  35. e.printStackTrace();
  36. IoUtils.safeClose(channel);
  37. }
  38. }
  39. };
  40. // 创建接收 listener.
  41. final ChannelListener<AcceptingChannel<ConnectedStreamChannel>> acceptListener =
  42. new ChannelListener<AcceptingChannel<ConnectedStreamChannel>>() {
  43. public void handleEvent(
  44. final AcceptingChannel<ConnectedStreamChannel> channel) {
  45. try {
  46. ConnectedStreamChannel accepted;
  47. // channel就绪,准备接收连接请求
  48. while ((accepted = channel.accept()) != null) {
  49. System.out.println("accepted " + accepted.getPeerAddress());
  50. // 已经连接,设置读数据listener
  51. accepted.getReadSetter().set(readListener);
  52. // 恢复读的状态
  53. accepted.resumeReads();
  54. }
  55. } catch (IOException ignored) {
  56. }
  57. }
  58. };
  59. //创建Xnio实例,并构造XnioWorker
  60. final XnioWorker worker = Xnio.getInstance().createWorker(OptionMap.EMPTY);
  61. // 创建server,在本地12345端口上侦听
  62. AcceptingChannel<? extends ConnectedStreamChannel> server = worker
  63. .createStreamServer(new InetSocketAddress(12345),
  64. acceptListener, OptionMap.EMPTY);
  65. // 开始接受连接
  66. server.resumeAccepts();
  67. System.out.println("Listening on " + server.getLocalAddress());
  68. }
  69. }
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer; import org.xnio.ChannelListener;

import org.xnio.IoUtils;

import org.xnio.OptionMap;

import org.xnio.Xnio;

import org.xnio.XnioWorker;

import org.xnio.channels.AcceptingChannel;

import org.xnio.channels.Channels;

import org.xnio.channels.ConnectedStreamChannel; public final class SimpleEchoServer {
public static void main(String[] args) throws Exception {

    // 定义读数据listener
final ChannelListener&lt;ConnectedStreamChannel&gt; readListener =
new ChannelListener&lt;ConnectedStreamChannel&gt;() {
public void handleEvent(ConnectedStreamChannel channel) {
//分配缓冲
final ByteBuffer buffer = ByteBuffer.allocate(512);
int res;
try {
while ((res = channel.read(buffer)) &gt; 0) {
//切换到写的状态并用阻塞的方式写回
buffer.flip();
Channels.writeBlocking(channel, buffer);
}
// 保证全部送出
Channels.flushBlocking(channel);
if (res == -1) {
channel.close();
} else {
channel.resumeReads();
}
} catch (IOException e) {
e.printStackTrace();
IoUtils.safeClose(channel);
}
}
};
// 创建接收 listener.
final ChannelListener&lt;AcceptingChannel&lt;ConnectedStreamChannel&gt;&gt; acceptListener =
new ChannelListener&lt;AcceptingChannel&lt;ConnectedStreamChannel&gt;&gt;() {
public void handleEvent(
final AcceptingChannel&lt;ConnectedStreamChannel&gt; channel) {
try {
ConnectedStreamChannel accepted;
// channel就绪,准备接收连接请求
while ((accepted = channel.accept()) != null) {
System.out.println("accepted " + accepted.getPeerAddress());
// 已经连接,设置读数据listener
accepted.getReadSetter().set(readListener);
// 恢复读的状态
accepted.resumeReads();
}
} catch (IOException ignored) {
}
}
}; //创建Xnio实例,并构造XnioWorker
final XnioWorker worker = Xnio.getInstance().createWorker(OptionMap.EMPTY);
// 创建server,在本地12345端口上侦听
AcceptingChannel&lt;? extends ConnectedStreamChannel&gt; server = worker
.createStreamServer(new InetSocketAddress(12345),
acceptListener, OptionMap.EMPTY);
// 开始接受连接
server.resumeAccepts();
System.out.println("Listening on " + server.getLocalAddress());
}

}

客户端:

  1. import java.net.InetSocketAddress;
  2. import java.nio.ByteBuffer;
  3. import java.nio.CharBuffer;
  4. import java.nio.charset.Charset;
  5. import org.xnio.IoFuture;
  6. import org.xnio.IoUtils;
  7. import org.xnio.OptionMap;
  8. import org.xnio.Xnio;
  9. import org.xnio.XnioWorker;
  10. import org.xnio.channels.Channels;
  11. import org.xnio.channels.ConnectedStreamChannel;
  12. public final class SimpleHelloWorldBlockingClient {
  13. public static void main(String[] args) throws Exception {
  14. final Charset charset = Charset.forName("utf-8");
  15. //创建Xnio实例,并构造XnioWorker
  16. final Xnio xnio = Xnio.getInstance();
  17. final XnioWorker worker = xnio.createWorker(OptionMap.EMPTY);
  18. try {
  19. //连接服务器,本地12345端口,注意返回值是IoFuture类型,并不阻塞,返回后可以做些别的事情
  20. final IoFuture<ConnectedStreamChannel> futureConnection = worker.connectStream(
  21. new InetSocketAddress("localhost", 12345), null, OptionMap.EMPTY);
  22. final ConnectedStreamChannel channel = futureConnection.get(); // get是阻塞调用
  23. try {
  24. // 发送消息
  25. Channels.writeBlocking(channel, ByteBuffer.wrap("Hello world!\n".getBytes(charset)));
  26. // 保证全部送出
  27. Channels.flushBlocking(channel);
  28. // 发送EOF
  29. channel.shutdownWrites();
  30. System.out.println("Sent greeting string! The response is...");
  31. ByteBuffer recvBuf = ByteBuffer.allocate(128);
  32. // 接收消息
  33. while (Channels.readBlocking(channel, recvBuf) != -1) {
  34. recvBuf.flip();
  35. final CharBuffer chars = charset.decode(recvBuf);
  36. System.out.print(chars);
  37. recvBuf.clear();
  38. }
  39. } finally {
  40. IoUtils.safeClose(channel);
  41. }
  42. } finally {
  43. worker.shutdown();
  44. }
  45. }
  46. }
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset; import org.xnio.IoFuture;

import org.xnio.IoUtils;

import org.xnio.OptionMap;

import org.xnio.Xnio;

import org.xnio.XnioWorker;

import org.xnio.channels.Channels;

import org.xnio.channels.ConnectedStreamChannel; public final class SimpleHelloWorldBlockingClient {
public static void main(String[] args) throws Exception {
final Charset charset = Charset.forName("utf-8");
//创建Xnio实例,并构造XnioWorker
final Xnio xnio = Xnio.getInstance();
final XnioWorker worker = xnio.createWorker(OptionMap.EMPTY); try {
//连接服务器,本地12345端口,注意返回值是IoFuture类型,并不阻塞,返回后可以做些别的事情
final IoFuture&lt;ConnectedStreamChannel&gt; futureConnection = worker.connectStream(
new InetSocketAddress("localhost", 12345), null, OptionMap.EMPTY);
final ConnectedStreamChannel channel = futureConnection.get(); // get是阻塞调用
try {
// 发送消息
Channels.writeBlocking(channel, ByteBuffer.wrap("Hello world!\n".getBytes(charset)));
// 保证全部送出
Channels.flushBlocking(channel);
// 发送EOF
channel.shutdownWrites();
System.out.println("Sent greeting string! The response is...");
ByteBuffer recvBuf = ByteBuffer.allocate(128);
// 接收消息
while (Channels.readBlocking(channel, recvBuf) != -1) {
recvBuf.flip();
final CharBuffer chars = charset.decode(recvBuf);
System.out.print(chars);
recvBuf.clear();
}
} finally {
IoUtils.safeClose(channel);
}
} finally {
worker.shutdown();
}
}

}

[1]: JavaNIO http://www.amazon.com/Java-Nio-Ron-Hitchens/dp/0596002882
[2]: Pro Java 7 NIO.2 http://www.amazon.com/Pro-Java-NIO-2-Anghel-Leonard/dp/1430240113

Undertow服务器基础分析 - XNIO的更多相关文章

  1. undertow服务器

    参考地址:http://undertow.io/undertow-docs/undertow-docs-1.3.0/index.html 1.引入相关jar <dependencies> ...

  2. 高性能非阻塞 Web 服务器 Undertow

    Undertow 简介 Undertow是一个用java编写的.灵活的.高性能的Web服务器,提供基于NIO的阻塞和非阻塞API. Undertow的架构是组合式的,可以通过组合各种小型的目的单一的处 ...

  3. SpringBoot使用Undertow做服务器

    说明 undertow,jetty和tomcat可以说是javaweb项目当下最火的三款服务器,tomcat是apache下的一款重量级的服务器,不用多说历史悠久,经得起实践的考验.然而:当下微服务兴 ...

  4. Spring Cloud 升级之路 - 2020.0.x - 2. 使用 Undertow 作为我们的 Web 服务容器

    本项目代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 在我们的项目中,我 ...

  5. SpringCloud升级之路2020.0.x版-12.UnderTow 简介与内部原理

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 在我们的项目中,我 ...

  6. springboot undertow替换tomcat方式

    版权声明: https://blog.csdn.net/weixin_38187317/article/details/81532560说明        undertow,jetty和tomcat可 ...

  7. Spring Boot 揭秘与实战(五) 服务器篇 - 其他内嵌服务器 发表于 2017-01-03 | Spring框架 | Spri

    文章目录 1. Jetty 的切换 2. Undertow的使用 Spring Boot 可选择内嵌 Tomcat.Jetty 和 Undertow,因此我们不需要以 war 包形式部署项目.< ...

  8. Undertow,Tomcat和Jetty服务器配置详解与性能测试

    undertow,jetty和tomcat可以说是javaweb项目当下最火的三款服务器,tomcat是apache下的一款重量级的服务器,不用多说历史悠久,经得起实践的考验.然而:当下微服务兴起,s ...

  9. 将SpringBoot默认使用的tomcat替换为undertow

    随着微服务的兴起,越来越多的互联网应用在选择web容器时使用更加轻量的undertow或者jetty.SpringBoot默认使用的容器是tomcat,如果想换成undertow容器,只需修改pom. ...

随机推荐

  1. The linux command 之引用

    [me@linuxbox ~]$ echo this is a test this is a test shell 会对echo进行单词分割(word splitting)去除多余的空白. [me@l ...

  2. vue+ElementUI——表格分页(前端实现方法)

    1.使用ElementUI中的<el-table></el-table>和 <el-pagination></el-pagination>组件来实现 2 ...

  3. Linux的命令提示符 修改

    Linux的命令提示符可按个人喜好随意更改,修改PS1的值即可: 在Ubuntu下若只是个别用户下修改~/.profile文件就好,所有用户统一则修改/etc/profile: 加入: export ...

  4. Eclipse中如何使用Hibernate

    首先创建一个java web项目,其目录如下: (1)创建文件夹hibernate4(用于存放下载的hibernate工具包lib/required文件夹下所有的jar包),jar包目录如下: (2) ...

  5. 互联网金融ABS为何遭遇急刹车?

    互联网金融ABS为何遭遇急刹车?   今年以来,互联网金融ABS迎来爆发式增长,已逐渐成为平台融资的重要渠道.近期有媒体称,监管方面已叫停审批,原因何在? 本期看点: 互联网金融ABS与传统ABS有何 ...

  6. VS2010-MFC(图形图像:CDC类及其屏幕绘图函数)

    转自:http://www.jizhuomi.com/software/244.html 上一节讲了文本输出的知识,本节的主要内容是CDC类及其屏幕绘图函数. CDC类简介 CDC类是一个设备上下文类 ...

  7. ASP.NET的一些概念

    简述实体框架(EF): EF是一种ORM工具,ORM表示对象关联映射. 在RDMS中,对象称为表格和列对象,而在.net中(面向对象)称为类,对象以及属性. EF提供了三种方式来实现项目: l 数据库 ...

  8. Jan&Feb 工作总结

    一.工作任务: 熟悉dcbi项目(运维经分系统),完成指定需求. 熟悉bi项目(数据分析平台),完成指定需求. steel circus 主机游戏官网制作. 一拳超人游戏预注册页wap手机端制作. 二 ...

  9. IMS Call中的SS

    1Hold procedure:对于每一个被HOLD的媒体流,SDP包含: 如果流之前被设置为“recvonly”媒体流则是一个“不活动”的SDP属性: 如果先前将流设置为“sendrecv”媒体流则 ...

  10. <Django> MVT三大块之Models(模型)

    1.ORM(对象-关系-映射)---面向对象,不需要面向SQL语句 根据对象的类型生成表结构 将对象.列表的操作,转化成SQL语句 将SQL语句查询的结果转化成对象.列表 目的:实现数据模型与数据库的 ...