Undertow服务器基础分析 - XNIO

标签: undertow 服务器 基础分析 | 发表时间:2014-04-03 10:50 | 作者:
出处:http://www.iteye.com

我们从名字上就能看出这是一个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

服务器:

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<ConnectedStreamChannel> readListener = 
            new ChannelListener<ConnectedStreamChannel>() {
            public void handleEvent(ConnectedStreamChannel channel) {
                //分配缓冲
                final ByteBuffer buffer = ByteBuffer.allocate(512);
                int res;
                try {
                    while ((res = channel.read(buffer)) > 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<AcceptingChannel<ConnectedStreamChannel>> acceptListener = 
            new ChannelListener<AcceptingChannel<ConnectedStreamChannel>>() {
            public void handleEvent(
                    final AcceptingChannel<ConnectedStreamChannel> 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<? extends ConnectedStreamChannel> server = worker
                .createStreamServer(new InetSocketAddress(12345),
                        acceptListener, OptionMap.EMPTY);
        // 开始接受连接
        server.resumeAccepts();
        System.out.println("Listening on " + server.getLocalAddress());
    }
}

 
客户端:

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<ConnectedStreamChannel> 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



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [undertow 服务器 基础分析] 推荐:

Undertow服务器基础分析 - Undertow

- - 开源软件 - ITeye博客
Undertow是一个Web服务器,那么它就需要具备的现代Web服务器的基本特性,比如Servlet,JSP,文件服务器,代理服务器,安全认证等. undertow目前已经实现了绝大多数功能,并且因为wildfly通过了JavaEE7 TCK认证,所以可以说Undertow是一个通过Servlet 3.1认证的Web服务器和容器.

Undertow服务器基础分析 - XNIO

- - ITeye博客
我们从名字上就能看出这是一个NIO思想为基础的IO框架,X是指这个框架可以有多种实现,我们可以从代码库 https://github.com/xnio 中发现一个项目xnio-native,里面有用C实现的nio层,就能体会到这个X的含义,可以直接基于操作系统C库. 目前在Xnio中默认的实现是nio-impl,也就是JDK的NIO.

高性能非阻塞Web 服务器Undertow

- - 互联网 - ITeye博客
WildFly 8 包含了一个全新的Web服务器(Undertow),WildFly 8 默认的Web服务器为Undertow. 一句话概括什么是Undertow - 高性能非阻塞 Web 服务器. Undertow 主要有以下几个特点:. 轻量化 - Undertow 是一个Web 服务器,但它不像传统的Web 服务器有容器的概念,它由两个核心jar包组成,使用API加载一个Web应用可以使用小于10MB的内存.

kernel.org服务器遭入侵

- Lamo - Solidot
kernel.org网站首页发布公告,声称多台服务器在本月初(8月12日前)遭黑客攻击,他们在8月28日发现了入侵. 入侵者利用一位用户凭证获得了服务器根访问权限,他们正在调查黑客是如何提升权限的;系统启动脚本被加入了一个木马启动文件;ssh相关文件被修改. kernel.org声称,他们相信Linux kernel源代码库未受影响,因为git分布式版本控制系统的特性决定了它可以很容易注意到代码变化.

Ubuntu下赌ARM服务器

- Tim - Solidot
今日无数手机平板使用的低能耗处理器能否撑起未来的服务器市场. Canonical计划推出支持ARM架构的Ubuntu服务器版本. Ubuntu Linux并不是x86服务器市场的重量级选手,Red Hat才是. 但通过与ARM合作打造ARM服务器,Canonical正努力赢得更多市场份额. 计划于2011年10月发布的Ubuntu Server 11.10,将同步推出支持x86、x86-64和ARM架构的版本.

Windows 搭建VPN服务器

- 洋白菜 - iGFW
Windows XP搭建PPTP VPN. 普通用户,在家里也可以搭建自己的VPN. 需要将服务器端的电脑直接放置于外网访问下,若是ADSL拨号的话,可以用此电脑直接拨号,中间不接路由器. 若是接路由器的话,可以启用NAT(网络地址转换)中的DMZ,将该服务器IP地址填入此处,那么外网访问到本路由Wan口时,就会直接转到该服务器上.

浅谈web服务器—Nginx

- - CSDN博客推荐文章
常见的web服务器有apache,Nginx,lighttpd等. 但Nginx作为一款高性能的Http和反向代理服务器,由于其高效率、简配置等优势在业内被广泛使用. 目前Taobao、新浪、赶集网、金山、豆瓣网、网易新闻等众多知名互联网企业的服务器都是采用Nginx. 根据url的不同,将HTTP请求转发到后端的应用服务器集群.

centos linux 服务器安全

- - 操作系统 - ITeye博客
我们必须明白:最小的权限+最少的服务=最大的安全. 所以,无论是配置任何服务器,我们都必须把不用的服务关闭、把系统权限设置到最小话,这样才能保证服务器最大的安全. 下面是CentOS服务器安全设置,供大家参考. 一、注释掉系统不需要的用户和用户组. 注意:不建议直接删除,当你需要某个用户时,自己重新添加会很麻烦.

Java NIO服务器实例

- - ImportNew
我一直想学习如何用Java写一个 非阻塞IO服务器,但无法从网上找到一个满足要求的服务器. 我找到了 这个示例,但仍然没能解决我的问题. 还可以选择 Apache MINA框架. 但我的要求相对简单,MINA对我来说还稍微有点复杂. 所以在MINA和一些教程(参见 这篇和 这篇)的帮助下,我自己写了一个非阻塞IO服务器.

angularjs与服务器交互

- - CSDN博客Web前端推荐文章
真正的应用需要和真实的服务器进行交互,移动应用和新兴的Chrome桌面应用可能是个例外,但是对于此外的所有应用来说,无论你是想把数据持久化到云端,还是需要与其他用户进行实时交互,都需要让应用与服务器进行交互. 为了实现这一点,Angular提供了一个叫做$http的服务. 它提供了一个可扩展的抽象方法列表,使得与服务器的交互更加容易.