ServerBootstrap创建服务端的源码分析

0

Posted by agilejava | Posted in 未分类 | Posted on 16-12-2015

serverbootstrappng

要想通过ServerBootstrap启动一个netty的服务端就要调用bind()方法去绑定并监听一个端口上的请求。

ServerBootstrap的主要的方法都在AbstractBootstrap中定义,bind()方法有五个重载的方法,最终都会调到doBind()方法

clipboard

clipboard[1]

clipboard[2]

clipboard[3]

clipboard[4]

ServerBootstrap的doBind()方法:

  • 调用initAndRegister()方法,如果注册完成并没有错误就调用doBind0()方法。

clipboard[5]

initAndRegister()方法:

通过channelFactory().newChannel()方法创建ServerSocketChannel并调用init()完成初始化,并把channel注册到接收客户端TCP连接的NioEventLoopGroup线程池上。

clipboard[6]

init()主要完成channel对象的初始化,包括:options、attrs、pipeline、childGroup、childHander、childOpetion、childAttrs等

void init(Channel channel) throws Exception {
final Map<ChannelOption<?>, Object> options = options();
synchronized (options) {
channel.config().setOptions(options);
}

final Map<AttributeKey<?>, Object> attrs = attrs();
synchronized (attrs) {
for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
@SuppressWarnings("unchecked")
AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
channel.attr(key).set(e.getValue());
}
}

ChannelPipeline p = channel.pipeline();
if (handler() != null) {
p.addLast(handler());
}

final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions;
final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}

p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}

接收客户端TCP连接的NioEventLoopGroup线程池会分配一个SingleThreadEventExecutor线程对象来处理注册事件

clipboard[7]

SingleThreadEventExecutor的register方法中channel.unsafe() 注册了这个线程对象。

clipboard[8]

通过channel的unsafe()方法访问到AbstractUnsafe对象,调用AbstractUnsafe对象的register()方法

@Override
public final void register(EventLoop eventLoop, final ChannelPromise promise) {
if (eventLoop == null) {
throw new NullPointerException("eventLoop");
}
if (isRegistered()) {
promise.setFailure(new IllegalStateException("registered to an event loop already"));
return;
}
if (!isCompatible(eventLoop)) {
promise.setFailure(
new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
return;
}

AbstractChannel.this.eventLoop = eventLoop;

if (eventLoop.inEventLoop()) {
register0(promise);
} else {
try {
eventLoop.execute(new OneTimeTask() {
@Override
public void run() {
register0(promise);
}
});
} catch (Throwable t) {
logger.warn(
"Force-closing a channel whose registration task was not accepted by an event loop: {}",
AbstractChannel.this, t);
closeForcibly();
closeFuture.setClosed();
safeSetFailure(promise, t);
}
}
}

调用register0()方法

clipboard[9]

调用AbstractNioChannel的doRegister()方法,该方法使用jdk的channel对象注册到selector对象,事件为0,并把自己作为附件关联到selectionKey上。

clipboard[10]

注册方法完成后,开始执行doBind0()方法

clipboard[11]

clipboard[12]

clipboard[13]

AbstractChannelHandlerContext

clipboard[14]

clipboard[15]

DefaultChannelPipeline

clipboard[16]

AbstractChannel的AbstractUnsafe的bind()方法:

  • 调用jdk的ServerSocketChannel的bind方法
  • 调用pipeline的fireChannelActive()方法,触发
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}

// See: https://github.com/netty/netty/issues/576
if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
localAddress instanceof InetSocketAddress &&
!((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&
!PlatformDependent.isWindows() && !PlatformDependent.isRoot()) {
// Warn a user about the fact that a non-root user can't receive a
// broadcast packet on *nix if the socket is bound on non-wildcard address.
logger.warn(
"A non-root user can't receive a broadcast packet if the socket " +
"is not bound to a wildcard address; binding to a non-wildcard " +
"address (" + localAddress + ") anyway as requested.");
}

boolean wasActive = isActive();
try {
doBind(localAddress);
} catch (Throwable t) {
safeSetFailure(promise, t);
closeIfClosed();
return;
}

if (!wasActive && isActive()) {
invokeLater(new OneTimeTask() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}

safeSetSuccess(promise);
}

clipboard[17]

NioServerSocketChannel的doBind()方法:

  • 完成了JKD ServerSocketChannel绑定本地端口
  • 同时设置了backlog参数

clipboard[18]

图解NIO中的Buffer(缓冲区)

0

Posted by agilejava | Posted in JAVA | Posted on 08-12-2015

概述:

Buffer(缓冲区),是NIO中的重要对象,它用来保存读入或者写出的数据,在NIO中所有数据都是用缓冲区处理的。在读取数据时,它是直接读取到缓冲区中的;在写入数据时,写入到缓冲区中。

缓冲区实质上是一个数组。通常它是一个字节数组(ByteBuffer),也可以使用其他种类的数组。但一个缓冲区不仅仅是一个数组,缓冲区提供了对数据的结构化访问以及维护读写位置等信息。

缓冲区可以从两个方面提高I/O操作的效率:

  • 减少实际的物理读写次数
  • 缓冲区在创建是分配内存,这块内存区域一直被重用,这可以减少动态内存分配和回收内存区域的次数。

缓冲区的种类:

image

其中ByteBuffer按照内存的使用方式可以分为两种Non-direct ByteBuffer与Direct ByteBuffer。

Non-direct ByteBuffer内存是分配在堆上的,直接由Java虚拟机负责垃圾回收,你可以把它想象成一个字节数组的包装类。

Direct ByteBuffer是通过JNI在Java虚拟机外的内存中分配了一块内存(该内存可以大于-Xmx指定的堆最大内存),该内存块并不直接由Java虚拟机负责垃圾回收,但是在Direct ByteBuffer的包装类被回收时,会通过Java Reference机制来释放该块内存

缓冲区的类继承关系如下:

clipboard

缓冲区的内部数据结构

clipboard[1]

容量 capacity : 表示该缓冲区可以保存多少数据。

极限 limit:表示缓冲区的当前终点,不能对缓冲区中超过极限的区域进行读写操作。极限是可以修改的,这有利于缓冲区的重用。极限是一个非负整数,不应该大于容量。

位置 position:表示缓冲区中下一个读写单元的位置,每次读写缓冲区的数据时,都会改变该值,为下一次读写数据做准备,位置是一个非负整数,不应该大于极限。默认值0.

标记 mark:保存某个时刻的position的值,通过调用mark()实现;当mark标记被置为负值时,表示废弃标记。默认值-1.

图解ByteBuffer的主要方法:

创建一个8字节长度的ByteBuffer对象。

ByteBuffer byteBuffer = ByteBuffer.allocate(8);

clipboard[2]

调用put方法写入hello后

byteBuffer.put("hello".getBytes());

clipboard[3]

clipboard[4]

flip
public final Buffer flip()
反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。

在一系列通道读取或放置 操作之后,调用此方法为一系列通道写入或相对获取 操作做好准备。例如:

 buf.put(magic);    // Prepend header
 in.read(buf);      // Read data into rest of buffer
 buf.flip();        // Flip buffer
 out.write(buf);    // Write header + data to channel

当将数据从一个地方传输到另一个地方时,经常将此方法与 compact 方法一起使用。

返回:
此缓冲区

执行flip方法后

byteBuffer.flip();

clipboard[5]

compact
public abstract ByteBuffer compact()
压缩此缓冲区 (可选操作)

将缓冲区的当前位置和界限之间的字节(如果有)复制到缓冲区的开始处。即将索引 p = position() 处的字节复制到索引 0 处,将索引p + 1 处的字节复制到索引 1 处,依此类推,直到将索引 limit() – 1 处的字节复制到索引 n = limit()1p 处。然后将缓冲区的位置设置为 n+1,并将其界限设置为其容量。如果已定义了标记,则丢弃它。

将缓冲区的位置设置为复制的字节数,而不是零,以便调用此方法后可以紧接着调用另一个相对 put 方法。

从缓冲区写入数据之后调用此方法,以防写入不完整。例如,以下循环语句通过 buf 缓冲区将字节从一个信道复制到另一个信道:

 buf.clear();          // Prepare buffer for use
  while (in.read(buf) >= 0 || buf.position != 0) {
     buf.flip();
     out.write(buf);
     buf.compact();    // In case of partial write
 }
返回:
此缓冲区
抛出:
ReadOnlyBufferException – 如果此缓冲区是只读缓冲区

执行compact方法的效果如下:

clipboard[6]

clipboard[7]

rewind
public final Buffer rewind()
重绕此缓冲区。将位置设置为 0 并丢弃标记。

在一系列通道写入或获取 操作之前调用此方法(假定已经适当设置了限制)。例如:

 out.write(buf);    // Write remaining data
 buf.rewind();      // Rewind buffer
 buf.get(array);    // Copy data into array
返回:
此缓冲区

执行rewind()方法后效果如下:

byteBuffer.rewind();

clipboard[8]

clear
public final Buffer clear()
清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。

在使用一系列通道读取或放置 操作填充此缓冲区之前调用此方法。例如:

 buf.clear();     // Prepare buffer for reading
 in.read(buf);    // Read data

此方法不能实际清除缓冲区中的数据,但从名称来看它似乎能够这样做,这样命名是因为它多数情况下确实是在清除数据时使用。

返回:
此缓冲区

执行clear方法后:

byteBuffer.clear();

clipboard[9]

clipboard[10]

JVM飙高排查脚本-结构分析

0

Posted by agilejava | Posted in JAVA | Posted on 07-01-2013

本文转自 http://yq.aliyun.com/articles/55?spm=5176.100240.searchblog.23

大家都有过遇到线上程序LOAD突然狂飙的场景,要排查到为何狂飙,我们当务之急就是要找到导致CPU飙升的原因。如果是进程级的应用,如Nginx、Apache等都还比较容易排查,但如果是JVM中的某个线程导致的,估计有人就要开始抓瞎了。

很多人都或多或少的知道有这么一个脚本,能帮你大致定位到现场导致LOAD飙升的JVM线程,脚本大概如下。

#!/bin/ksh

# write by    : oldmanpushcart@gmail.com
# date        : 2014-01-16
# version     : 0.07

typeset top=${1:-10}
typeset pid=${2:-$(pgrep -u $USER java)}
typeset tmp_file=/tmp/java_${pid}_$$.trace

$JAVA_HOME/bin/jstack $pid > $tmp_file
ps H -eo user,pid,ppid,tid,time,%cpu --sort=%cpu --no-headers\
        | tail -$top\
        | awk -v "pid=$pid" '$2==pid{print $4"\t"$6}'\
        | while read line;
do
        typeset nid=$(echo "$line"|awk '{printf("0x%x",$1)}')
        typeset cpu=$(echo "$line"|awk '{print $2}')
        awk -v "cpu=$cpu" '/nid='"$nid"'/,/^$/{print $0"\t"(isF++?"":"cpu="cpu"%");}' $tmp_file
done

rm -f $tmp_file

现在我们就来拆解其中的原理,以及说明下类似脚本的适用范围。

步骤1:dump当前JVM线程,保存现场
$JAVA_HOME/bin/jstack $pid > $tmp_file

​保存现场是相当的重要,因为问题转瞬之间就会从手中溜走(但其实LOAD的统计机制也决定了,事实也并不是那么严格)

​步骤2:找到当前CPU使用占比高的线程
ps H -eo user,pid,ppid,tid,time,%cpu --sort=%cpu

alt

列说明

USER:进程归属用户

PID:进程号

PPID:父进程号

TID:线程号

%CPU:线程使用CPU占比(这里要提醒下各位,这个CPU占比是通过/proc计算得到,存在时间差)

步骤3:合并相关信息

我们需要关注的大概是3列:PID、TID、%CPU,我们通过PS拿到了TID,可以通过进制换算10-16得到jstack出来的JVM线程号​

typeset nid="0x"$(echo "$line"|awk '{print $1}'|xargs -I{} echo "obase=16;{}"|bc|tr 'A-Z' 'a-z')

最后再将ps和jstack出来的信息进行一个匹配与合并。终于,得到我们最想要的信息

alt

适用范围说明

看似这个脚本很牛X的样子,能直接定位到最耗费CPU的线程,开发再也不用担心找不到线上最有问题的代码~但,且慢,姑且注意下输出的结果,State: WAITING 这是这个啥节奏~

alt

这是因为ps中的%CPU数据统计来自于/proc/stat,这个份数据并非实时的,而是取决于OS对其更新的频率,一般为1S。所以你看到的数据统计会和jstack出来的信息不一致也就是这个原因~但这份信息对持续LOAD由少数几个线程导致的问题排查还是非常给力的,因为这些固定少数几个线程会持续消耗CPU的资源,即使存在时间差,反正也都是这几个线程所导致。

visualVM通过JMX监控远程Tomcat服务器

0

Posted by agilejava | Posted in JAVA | Posted on 07-01-2013

下载catalina-jmx-remote.jar

不同版本Tomcat有不同的catalina-jmx-remote.jar,在tomcat的下载页(我用的tomcat版本是7.0.62)面http://tomcat.apache.org/download-70.cgi,找到以下JMX Remote jar,把这个文件放到tomcat安装目录的lib子目录下

      修改Tomcat安装目录conf子目录下的server.xml配置文件

Xml代码  icon_star

  1. 省略…
  2. <Server port=”8005″ shutdown=”SHUTDOWN”>
  3. <Listener className=”org.apache.catalina.startup.VersionLoggerListener” />
  4.   <!– Security listener. Documentation at /docs/config/listeners.html
  5. <Listener className=”org.apache.catalina.security.SecurityListener” />
  6.   –>
  7. <!–APR library loader. Documentation at /docs/apr.html –>
  8. <Listener className=”org.apache.catalina.core.AprLifecycleListener” SSLEngine=”on” />
  9. <!–Initialize Jasper prior to webapps are loaded. Documentation at /docs/jasper-howto.html –>
  10. <Listener className=”org.apache.catalina.core.JasperListener” />
  11. <!– Prevent memory leaks due to use of particular java/javax APIs–>
  12. <Listener className=”org.apache.catalina.core.JreMemoryLeakPreventionListener” />
  13. <Listener className=”org.apache.catalina.mbeans.GlobalResourcesLifecycleListener” />
  14. <Listener className=”org.apache.catalina.core.ThreadLocalLeakPreventionListener” />
  15. <Listener className=”org.apache.catalina.mbeans.JmxRemoteLifecycleListener”
  16. rmiRegistryPortPlatform=”8100″ rmiServerPortPlatform=”8101″ />
  17. 省略…
修改Tomcat安装目录conf子目录下的catalina.sh配置文件

在JAVA_OPTS=”-Xms128m -Xmx512m -XX:PermSize=128m  -XX:MaxPermSize=256m -Dcom.sun.management.jmxremote.port=8166  -Dcom.sun.management.jmxremote.ssl=false  -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=10.116.94.251  $JAVA_OPTS”

在启动脚本中去掉端口配置,端口配置只在server.xml配置,这样就不会启动随机端口导致防火墙拦截此随机端口了。

image

服务器硬盘的性能与稳定性指标

0

Posted by agilejava | Posted in 硬件性能 | Posted on 22-03-2011

标签:

大型互联网企业保存着大量的用户数据,这些数据都是存储在服务器硬盘上的,那么服务器硬盘的性能与稳定性对于一个互联网服务的质量起着相当重要的作用。

下面我们就来了解一下服务器硬盘的性能指标:

image

  • 硬盘的转速(Spindle Speed):硬盘转速就是指硬盘主轴电机的转动速度,也就是硬盘盘片在一分钟内所能完成的最大转数(rpm)。转速的快慢是标示硬盘档次的重要参数之一,它是决定硬盘内部传输率的关键因素之一,在很大程度上直接影响到硬盘的速度。硬盘的转速越快,硬盘寻找文件的速度也就越快,相对的硬盘的传输速度也就得到了提高。硬盘转速以每分钟多少转来表示,单位表示为RPM,RPM是Revolutions Per minute的缩写,是转/每分钟。RPM值越大,内部传输率就越快,访问时间就越短,硬盘的整体性能也就越好。 然而,转速的提高也带来了磨损加剧、温度升高、噪声增大等一系列负面影响。
  • 硬盘数据传输率:的英文拼写为Data Transfer Rate,简称DTR。硬盘数据传输率表现出硬盘工作时数据传输速度,是硬盘工作性能的具体表现,它并不是一成不变的而是随着工作的具体情况而变化的。在读取硬盘不同磁道、不同扇区的数据;数据存放的是否连续等因素都会影响到硬盘数据传输率。因为这个数据的不确定性,所以厂商在标示硬盘参数时,更多是采用外部数据传输率(External Transfer Rate)和内部数据传输率(Internal Transfer Rate)。
  • 内部数据传输率(internal data transfer rate):也叫持续数据传输率(sustained transfer rate),单位Mbits/S,这是兆位/秒的意思(注意与MB/S(兆字节/秒)之间的差别:MB/S=Mbits/S除以8)。它指磁头至硬盘缓存间的最大数据传输率,一般取决于硬盘的盘片转速和盘片数据线密度(指同一磁道上的数据间隔度)。内部传输率可以明确表现出硬盘的读写速度,它的高低才是评价一个硬盘整体性能的决定性因素,它是衡量硬盘性能的真正标准。
  • 接口访问速度(Gb/秒):该指标也称为突发数据传输率(Burst data transfer rate)外部数据传输率(External Transfer Rate),它是指从硬盘缓冲区读取数据的速率,也就是计算机通过硬盘接口从缓存中将数据读出交给相应的控制器的速率。在广告或硬盘特性表中常以数据接口速率代替,单位为MB/s。ATA100中的100就代表着这块硬盘的外部数据传输率理论最大值是100MB/s;ATA133则代表外部数据传输率理论最大值是133MB /s;SATA1.0接口的硬盘外部理论数据最大传输率可达150MB/s,而SATAII接口的硬盘外部理论数据最大传输率可达300MB/s。这些只是硬盘理论上最大的外部数据传输率,在实际的日常工作中是无法达到这个数值的,而是更多的取决于内部数据传输率。
  • 寻道时间,平均读/写时间(毫秒):平均寻道时间的英文拼写是Average Seek Time,它是了解硬盘性能至关重要的参数之一。它是指硬盘在接收到系统指令后,磁头从开始移动到移动至数据所在的磁道所花费时间的平均值,它一定程度上体现硬盘读取数据的能力,是影响硬盘内部数据传输率的重要参数,单位为毫秒(ms)。不同品牌、不同型号的产品其平均寻道时间也不一样,但这个时间越低,则产品越好,现今主流的硬盘产品平均寻道时间都在在9ms左右。
  • 平均延迟时间(毫秒):它指的是磁头移动到指定磁道后,还需要多少时间指定的(即要读取或者写入的)扇区才会转到磁头下进行读取或者写入的相关操作,很明显这个时间和盘片的转速有关,平均延迟时间一般指盘片旋转一周所用时间的一半,单位为毫秒(ms)。这样我们就可以很轻松地换算出硬盘转速和平均潜伏期的一一对应关系。               换算公式为:(60*1000)/ 硬盘转速 * 0.5=平均延迟时间                              可以计算出来,5400转 5.556ms,7200转 4.167ms和10000转 3ms
  • 平均访问时间(Average Access Time):这项指标在官方技术文档中一般不会出现,它指的是从相应的读或者写指令发出开始到指定的扇区转到磁头下等待进行读取或者写入(也有的称为从读/写指令发出到第一笔数据读/写所用的时间)为止的这段时间。一般情况下,平均访问时间约等于平均寻道时间和平均延迟时间之和(严格定义中还包括一些指令处理时间,但一般忽略不计)。其单位也为毫秒(ms),它的值我们可以利用Hdtach和Winbench 99v2.0测试出来。
  • 缓存(Cache memory):缓存是硬盘与外部总线交换数据的场所是硬盘控制器上的一块内存芯片,具有极快的存取速度。硬盘读数据的过程是将要读取的资料存入缓存,等缓存中填充满数据或者要读取的数据全部读完后再从缓存中以外部传输率传向硬盘外的数据总线。它是硬盘内部存储和外部接口之间的缓冲器。由于硬盘的内部数据传输率和接口访问速度不同,缓存在其中起到一个缓冲的作用。缓存的大小与速度是直接关系到硬盘的传输速度的重要因素,能够大幅度地提高硬盘整体性能。当硬盘存取零碎数据时需要不断地在硬盘与内存之间交换数据,有大缓存,则可以将那些零碎数据暂存在缓存中,减小外系统的负荷,也提高了数据的传输速度。
  • 单碟容量除了对于容量增长的贡献之外,单碟容量的另一个重要意义在于提升硬盘的数据传输速度。单碟容量的提高得益于磁道数的增加和磁道内线性磁密度的增加。磁道数的增加对于减少磁头的寻道时间大有好处,因为磁片的半径是固定的,磁道数的增加意味着磁道间距离的缩短,而磁头从一个磁道转移到另一个磁道所需的就位时间就会缩短。这将有助于随机数据传输速度的提高。而磁道内线性磁密度的增长则和硬盘的持续数据传输速度有着直接的联系,磁道内线性密度的增加使得每个磁道内可以存储更多的数据,从而在碟片的每个圆周运动中有更多的数据被从磁头读至硬盘的缓冲区里。

可靠性与数据完整性指标:

  • 平均无故障时间(MTBF,小时):平均无故障时间(Mean Time Between Failures,简称MTBF)是指硬盘平均能够正常运行多长时间,才发生一次故障。这是衡量硬盘可靠性的重要参数,平均无故障时间越长,硬盘的可靠性就越高。目前主流产品的平均无故障时间(MTBF)达到了一百万小时以上。
  • 全天候不间断运行的可靠性级别 (AFR)
  • 不可恢复读错误/被读数据(位)
  • 每年运行小时数
  • 字节数/扇区

参考:最大内部数据传输率   硬盘转速 硬盘  服务器硬盘的选择

大型互联网架构资料整理

0

Posted by agilejava | Posted in 大型网站架构 | Posted on 15-03-2011

标签:

1 新浪微博架构与平台安全

2 人人网技术架构介绍

3 Facebook网站的前端性能优化

IE浏览器对HTTP状态码的特殊处理

0

Posted by agilejava | Posted in WEB开发 | Posted on 15-03-2011

标签:

今天打算复习一下http协议,写的第一个程序就遇到了麻烦,程序很简单就是展示错误页面,在firefox测试是好的,但是IE中有个小问题,把QQ的错误页面与我的错误页面比较了一下,发现原来不是程序的问题,而是IE浏览器对404、500等状态码有特殊的处理:

如果你的错误页面过于简单,比如你的404页面上只有一句话“404”,那么IE将显示默认的友好错误信息,如下图:image

问题原因:

Internet Explorer 5和更高版本为以下友好错误信息提供了HTML模板替换项:400.403.404.405.406.408.409.410.500.501和505。每个错误都有一个名称值对(例如“404”,512)。第一个值为错误代码。第二个值是表示字节大小的值,Internet Explorer 5或更高版本使用该值来检测何时用自己的信息替换错误信息。因此,当Internet Explorer 5版的Wininet.dll文件得到HTTP错误信息时,Wininet.dll文件将确定HTML错误中附带的HTTP内容是否为正确设计的Web 页。这是在页面大小的基础上进行的。它在注册表中对每个错误的阈值进行评估。如果Web页太小,该Web页将被拒绝,并显示友好HTTP状态Web页

在Internet Explorer 4中,当Wininet.dll无法解决请求时,它显示一个嵌入式HTML错误信息和一个非描述性定义,例如:“Navigation Canceled”(导航已取消)或“Unable to retrieve Webpage in Offline mode”(在脱机模式下无法检索网页)。这些错误信息是Shdocvw.dll文件的资源(res:)。资源是指程序出于显示目的所使用的HTML代码,它嵌入在动态链接库(DLL)文件中。在Internet Explorer 5或更高版本中,这些错误信息是作为Shdoclc.dll文件的资源存储的。当收到错误信息时,它被相应的HTML模板取代,此模板可能包含以下任意类型的信息:

  •有关此问题的信息。
  •有关如何更正或解决此问题的信息。
  •指向前一页的链接。
  •指向Internet Explorer支持页的链接。
  •指向同一页的链接,以便您能尝试重新连接到该页。
  •指向缓存中的页面副本的链接(如果创建了应用程序编程接口API)。

  友好HTTP状态错误信息存储在以下注册表项中:HKEY_LOCAL_MACHINE\Software\Microsoft\Internet Explorer\Main\ErrorThresholds。

image

解决此问题的办法有:

1 将页面的内容做的丰富一些,内容多一些(加一些table或者div之类的)!

JAVA代码

2 在404页面中设置状态码为200,response.setStatus(200);

3  设置IE的选项   工具–>Internet选项–>高级—>显示友好http错误信息(取消选择) 。

ps:在firefox下如果用了google的工具条也会有一样的问题,当然解决办法也是一样的!

分享微软的云计算视频

0

Posted by agilejava | Posted in 云计算 | Posted on 12-03-2011

标签:,

微软最新技术演示–云计算与自然用户界面

 

未来"云–端"精彩生活畅想

微软愿景:未来健康

OAuth与OpenID介绍

0

Posted by agilejava | Posted in 开放平台 | Posted on 28-02-2011

标签:,

SLF4J的静态日志系统绑定机制

0

Posted by agilejava | Posted in JAVA | Posted on 02-02-2011

标签:

SLF4J是一个用于日志系统的简单Facade,允许最终用户在部署其应用时使用其所希望的日志系统。

SLF4J库类似于Apache Commons-Logging,但Apache Commons-Logging采用的是动态绑定机制,使用ClassLoader寻找和载入底层的日志库,而在OSGI中,不同的插件使用自己的ClassLoader,OSGI的这种机制保证了插件互相独立,然而却使得Apache Commons-Logging无法在OSGI的环境下工作了。SLF4J却没有这个问题,它是如何做到的呢?

一个简单的例子:

package chapters.introduction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class HelloWorld1 {

  public static void main(String[] args) {

    Logger logger = LoggerFactory.getLogger("chapters.introduction.HelloWorld1");
    logger.debug("Hello world.");
  }
}

SLF4J与日志系统的绑定过程是在getLogger方法中完成的!下面介绍整个绑定过程。Business Process Model

 

通过上图大家可以发现整个静态绑定机制的核心是通过查找类路径下面的org/slf4j/impl/StaticLoggerBinder.class,所以类路径下只能有一个这个类。看到这里大家可能会问了,这个类的包名是org.slf4j.impl,找这个类如何与其他的日志系统连接呢?呵呵,这个很简单,其实slf4j的发布包中包含了为多种日志系统写好的绑定代理,每个日志系统都写好了一个独立的StaticLoggerBinder,由这个类调用相关日志系统的对象!

下图是slf4j发布包中的为各个日志系统写好的绑定代理。

image

为什么没有logback的绑定代理?呵呵,因为logback中已经内置了slf4j的绑定代理。

image